The JWT and Base64 Secrets You Need To Know

Welcome to my guide to the JWT and Base 64 secrets you absolutely need to know if you hack.

The JWT and Base64 Secrets You Need To Know
Brazilian Skulls Special Operations & Commando Operators

After months of being busy with my work, studying new things and doing hacking challenges, I feel that this knowledge must be shared. Welcome to my guide to the JWT and Base 64 secrets you absolutely need to know.

If you want to know about JWT tokens and how to exploit them this isn't the article for you, and I refer you to the links below for proper guides to that.

1)https://medium.com/swlh/hacking-json-web-tokens-jwts-9122efe91e4a

2)https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/

Lets first jump right into some code!

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, request, jsonify
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, decode_token
import datetime
from apscheduler.schedulers.background import BackgroundScheduler
import threading
import jwt
from config import *

# Setup flask
app = Flask(__name__)

app.config['JWT_SECRET_KEY'] = SECRET
jwtmanager = JWTManager(app)
blacklist = set()
lock = threading.Lock()# Free memory from expired tokens, as they are no longer useful
def delete_expired_tokens():
   with lock:
       to_remove = set()
       global blacklist
       for access_token in blacklist:
           try:
               jwt.decode(access_token, app.config['JWT_SECRET_KEY'],algorithm='HS256')
           except:
               to_remove.add(access_token)
 
       blacklist = blacklist.difference(to_remove)
   
@app.route("/web-serveur/ch63/")
def index():
   eturn "POST : /web-serveur/ch63/login <br>\nGET : /web-serveur/ch63/admin"
   
# Standard login endpoint
@app.route('/web-serveur/ch63/login', methods=['POST'])
def login():
   try:
       username = request.json.get('username', None)
       password = request.json.get('password', None)
   except:
       return jsonify({"msg":"""Bad request. Submit your login / pass as {"username":"admin","password":"admin"}"""}), 400if username != 'admin' or password != 'admin':
       return jsonify({"msg": "Bad username or password"}), 401access_token = create_access_token(identity=username,expires_delta=datetime.timedelta(minutes=3))
   ret = {
      'access_token': access_token,
   }
     
   with lock:
       blacklist.add(access_token)
   
   return jsonify(ret), 200
   
# Standard admin endpoint
@app.route('/web-serveur/ch63/admin', methods=['GET'])
@jwt_required
def protected():
   access_token = request.headers.get("Authorization").split()[1]
   with lock:
       if access_token in blacklist:
           return jsonify({"msg":"Token is revoked"})
       else:
           return jsonify({'Congratzzzz!!!_flag:': FLAG})if __name__ == '__main__':
   scheduler = BackgroundScheduler()
   job = scheduler.add_job(delete_expired_tokens, 'interval', seconds=10)
   scheduler.start()
   app.run(debug=False, host='0.0.0.0', port=5000)


I have shown you the source code, what it does is … you need to obtain API token which is in JWT format and log in with it. But the real hurdle here is, when you generate a token,it automatically go to blacklist set. Then during login the action is performed, it checks our JWT with the blacklist set. Now its hands-on time.

Image 1 : two method is support GET and POST on the response.
Image 2: I try to request GET method from admin API. Now we follow the response and meet the requirement.
Image 2: I try to request GET method from admin API. Now we follow the response and meet the requirement.
Image 3: Authorization header is implemented on request, now the response want it in Bearer with JWT token.

Now our investigation on admin API is done.


Image 4: Now check on “login”, It need us to post credential in JSON format.
Image 5: We successfully obtain the access_token. But now, what if we change to difference username or password?
Image 6: try username with admin1, response is 401.
Image 7: It same goes to password with admin1.
Image 8: On line 45, there is “or” which mean if either one username or password is different from what has been set, response will be fail. On line 48–54. what ever username or password is put,token will automatically go to blacklist set. Now we know that this one is not our target anymore.
Image 9: Here the part is we enable to manipulate is Authorization header only. Now let us check which part we can inject.
Image 10: Now we know where to inject the code, but there is checking for JWT formats “@jwt_required”(line 60)and expired token.you can refer Image 11 and 12.
Image 11: main function schedule job to check delete_expired_tokens.
Image 12: Checking function.
Image 13: After few minutes, I try the token. Now the token is expired.Meaning , it successfully check the token.

Now we are on the edge of testing,So let us look on JWT token, if there some part we can manipulate.Just for the case of testing :D , just want to cover some JWT manipulation technique.

Image 14: Check algo for JWT token, and change to “none”.
Image 15: encoded again with base64.
Image 16: Change the JWT header with the new one. as response “The specified alg value is not allowed”

The second trick we can use is weak JWT secret key. you can try bruteforce with wordlist. If lucky, we can obtain the secret. On this case, I’m not successful in obtaining the secret using the rockyou wordlist.

https://github.com/aress31/jwtcat

Image 17 : jwtcat tool.

What if we put padding on the JWT token?. It can be pass?. let us take one examples.

Image 18: I put some padding “=” for proper payload output.
Image 19: Now it has been closed with “}” bracket.
Image 20: Now we got this message error, which mean our method is fail. :(

The reason why? , If we changes some part of header and payload, the signature will be changes also!

Image 21

Now we have done all the possible test case, none of these is working :( .What we can do now?.. it is RFC(Request for Comments)!!! by googling base64 RFC, I mean this one :D “rfc3548” . Part that we are interest is in section 8, security consideration.

Image 22: rfc3548 manual
Image 23

which mean, what if we put non-alphabet on our base64?.. it can be pass?..

image 24: we generate some Chinese character.
image 25: We put it on the end of our signature, “we can’t put on header or payload because the reason is it affected our signature”.

Now we success to bypass it!! Congratulations. But there is another method to bypass it?.. yes :D

First, you need to generate admin token until our signature have an underscore “_” , then replace with “/” as image 26 and 27.

Image 26
Image 27

Last method that I kept is because I want to show you different method. Actually you can solve by adding “=” to our signature.

Image 28

As for conclusion, you learn the methodology of testing, JWT “none” injection technique and JWT weak secret key cracking.Lastly, the behavior of base64 and it other security consideration. Thanks :D