Nmap:
UDP Scan:
We can login with guest:guest
and also download the source code of the application.
src: http://10.10.10.195/src.zip
After logging in we are greeted with a friendly message telling us not to rely on automated tools ;)
We also see a page called "Submit" though it doesn't actually require any authentication. It is possible to submit message but we don't get any feedback or response.
Running snmpwalk with the public
community string we can see some information, though it's not what we would expect from snmp. Usually there is more information to be retrieved
Let's move on and keep this in mind
The webserver is a Flask python application and the folder structure looks like this:
In the source code we can see different functionalites like login, cookie handling, admin functions and the message submit.
Let's look at the submit function first.
Only messages up to 140 characters are allowed, then it's checking for some blacklisted words with badword_in_str(message)
and lastly running a database query which seems to simply insert our message into a table.
It's easy to spot that there is a SQLi vulnerability since it simply uses python string formatting and not a prepared statement.
We can see how it should be done in the try_login function
There are some things that can be done to exploit SQL injections in a sqlite3 database. One might stumble accross this handy cheat sheet: https://github.com/unicornsasfuel/sqlite_sqli_cheat_sheet
We can't do regular boolean blind based injection since we have no output if our query succeeded or not. We can't do stacked queries since they're also disabled by default. We can't do time-based extraction since the term "rand" is blacklisted along with a few others.
After some poking we figure out that we can't actually write anything to the sqlite3 db, because it's not commiting after our query. That means this insertion is useless anyway.
The load_extension() RCE is also disabled, or at least shows us an error.
I used string concatenation and used sub selects to issue other queries:
Why are we able to see an error? Let's look at the code again
If there is an error it returns it as a string to us, so we get a different output. That way we have a function to check if our query was successful or not. I figured that with load_extension() the error is raised on runtime, meaing we only see the error if we actually reach that part of the query.
Now it's possible to extract information by changing it to a boolean blind injection like this:
We already know there is a user with the username "guest". We also know some structures of the db by looking at the source code. The conditionals for sqlite are also covered in the cheat sheet.
If we input a wrong username, our conditional won't succeed and simply print an "OK" instead of the "not authorized" error that is caused by load_extension().
Now we can use the usual methods to extract information by using the SQL LIKE Operator
A query to extract the secret column step by step could look like this:
(While doing this box i set up a local environment and tested it against my own sqlite3 database. That way it's much easier to see what's working and what's not)
Finally I created a script to do the boring work for me. While writing that script I was reminded that the query must not exceed 140 characters. To bypass that I extracted the front part of the secret, then the back part and mashed them together.
There might be a smarter way, but it worked for me.
Looking at it now, I realize that the login step probably isn't needed after all.
The differences between sqli() and sqli2() are here:
[..] from users where secret like '{}%'))--
sqli()
[..] from users where secret like '%{}'))--
sqli2()
and here
The script is nice enough to present us with the admins password hash
Recovered Secret: f1fc12010c094016def791e1435ddfdcaeccf8250e36630c0bc93285c2971105
We can try to crack the sha256 hash but fast enough we come to the conclusion that it's not feasable to crack.
Now is the time where it's worth into looking how the application handles it's session cookies. You might have already noticed that the username and secret are actually stored inside the cookie
Decoded it will look like this
The cookie consists of two parts:
The user information looks like this:
So if we are able to fake or forge a signature, we should be able to authenticate as the admin user without knowing the plaintext password.
The signature is computed by this function in lwt.py:
The SECRET value is random and not known by the user
We only know the range of it's length, it's between 8 and 15 bytes long. The signature itself is also a sha256 hash, which is base64 encoded and appended to the User Information part.
The SHA256 hashing function aswell as other hashing function of the SHA-family are suspectible to a "Length Extension Attack".
Details about the attack itself can be found on wikipedia: https://en.wikipedia.org/wiki/Length_extension_attack
There are also writeups about CTF Challenges like this 2014 PlaidCTF one involving this kind of attack: https://conceptofproof.wordpress.com/2014/04/13/plaidctf-2014-web-150-mtgox-writeup/
With this attack we can alter an already valid cookie and forge a new valid signature for it.
We need some things for this to work:
Once we know these two things we can forge a cookie with a signature that will be valid without actually knowing the SECRET part.
From then on we can add something to our cookie (the admin user information) and forge a valid signature for it.
By studying (or debugging) the code enough, you will notice that the app will throw away additional user info parts and only use the ones at the end of it. So we can simply add the admin part like so:
In our case we don't actually know the exact length of secret, but we know the range it will be in. Since it's a very small range we can just guess it pretty quickly.
I used the same tool as in the PlaidCTF Writeup, though i used the python library instead of the cli tool. It's called hashpump
And sure enough we will receive a valid cookie that logs us in as the admin user!
The admin log functions are both suspectible to path traversal attack, meaning we can read any text file on the system and list all (for us) readable directories.
Please don't ever use code like this.
With this attack we can grab the user.txt file, but to proceed we must first get a shell. Remembering the Enumeration phase, we saw that the udp port 161 was open and had snmp running on it.
Time to look at the snmp configuration file in /etc/snmp/snmpd.conf
We don't actually care about what's being served to snmp, there is something else that looks suspicious.
A custom community with open read-write permissions:
rwcommunity SuP3RPrivCom90
This can be abused to get code execution on the system. For our convenience there is an easy to use metasploit module named exploit/linux/snmp/net_snmpd_rw_access
.
in the home directory of the user "user" we can find an application called "note_server" along with it's source code in "note_server.c"
This is only exploitable with a shell because the note_server is running on 127.0.0.1:5001 which isn't accessible otherwise. Now we can upload our exploit to the box, or better forward the port to our box with ssh etc.
HTB
PWN
CTF