Home
Writeups Misc About
RSA or HMAC

RSA or HMAC

This covers the two parts of the challenge. The challenge is related to the CVE-2017-11424 key confusion vulnerability in pyJWT.

This is somewhat hinted by the challenge's description of a "patch" to enable the exploit. Looking at the commit, we see that one of the newly added invalid_strings is '-----BEGIN RSA PUBLIC KEY-----'. This implies that we can leverage the public key for some forging. Combined with this line from the source code:

we can see that the PUBLIC_KEY can be used for verification of the HS256 generated token. Applied the patch, which involves removing the binary string '-----BEGIN RSA PUBLIC KEY-----' from _PEMS in file utils.py of pyjwt library, we can sign using the HS256 algorithm

For the second part, we are not given the public key, but this can be easily retrieved by calculating the GCD from the signature and signed message. I refer to this link about the technique used.

We can sign the dictionary where the admin value is True, using HS256 algorithm, with the key as the binary representation of the public key. The following is the code to do so

For the first challenge, as we know the public key, generating the JWT token is trivial - just supply the PUBLIC_KEY given from GET_PUBKEY() functionality on the CryptoHack website. For the second challenge, the public key is not given, hence we need a way to retrieve the public key.

We have full information of the signature, but the message, which is padded using EMSA-PKCS1-v1_5, needs a bit of reverse engineering to check out how everything works. I did try to do this, but information on the emLen (referring to RFC8017) is nowhere to be found in the source code. Apparently the value in use is 256, which can be derived from the length of the signature, but I was not exactly confident on this when I try to implement the script.

Hence, I use the script from JWT-Key-Recovery to retrieve the public key from two JWT token from two different username. The script is nice enough to supply the pem version after running.

This output from the above script is also the result of the export_key function of Pycryptodome. However, as observed from the command used to generate the public and private keys, and the output from the first challenge, the public key is not in PKCS#1 format, instead in PKCS#8. We can use the command from this link, we can use the command openssl rsa -pubin -in <filename> -RSAPublicKey_out to convert from PKCS#8 to PKCS#1 format.

That's not the end of the challenge. Doing the conversion on Windows will always run into the classic issue of DOS line endings \r\n versus UNIX line endings \n. Again, from checking the output returned in the first challenge, the public key file uses UNIX line endings. Hence, we have to do a conversion from DOS line endings to UNIX line endings before passing the public key to encode.

RSA public key in PKCS#1:

Python Implementation:

The following is the way to solve the challenge without involving the use of external script. Kudos to maple3142 for this solution, I add some comments for places that the Python function is based on. We still need to do the PKCS#8 to PKCS#1 conversion though.