Don’t forget to put on your black hoodie, it’s time to hack! I recently did a presentation about looking at code from the perspective of a hacker and I thought it would be interesting to also share here. In my opinion developers can write better and more secure code when they put of their developer cap and put on the black hoodie of a hacker (metaphorically of course). To illustrate what I mean, I’ll be taking a look at a technique that most developers probably know: JWT.

Developer perspective vs hacker perspective

Developers have a different goal for an application in comparison to the goal of a hacker. The developer wants to build an application with fulfills the requirements, while a hacker often simply has the goal to break in and steal something. A developer has thoughts like ‘Which solution is the cheapest?’, ‘Which solution is the most maintainable?’ and ‘Will this StackOverflow code? Let’s just copy it and see whether the tests are still green.’. Often the question the developer asks is ‘How can I get it working?’. A hacker has different thoughts, like ‘What technique is used?’, ‘Do I understand the technique that’s used?’ and ‘Is the technique applied properly everywhere?’. For a hacker it boils down to asking him/herself ‘Can I find a weakness?’ and ‘Do I understand how the application works?’. Resulting from this different mindset comes a different perspective at an application. I think by looking at an application from the perspective of the other, can be benefitial. If a developer looks at a piece of code from the perspective of an hacker, he/she might ask: ‘Do I actually understand what this piece does that I copied from StackOverflow?’. To give an example of this, I’ll dive into the JWT JKU exploit.

Authentication with JWT

A widely used way for authenticating is JSON Web Tokens (JWTs). Since implementing authentication can get pretty complex, it’s not uncommon to look online for examples of implementations. In the following repository there is a setup with an authentication service and greeting service: jwt-jku-attack. The user can register and login via the authentication service and will get back a JWT. The JWT can be used to get a greeting fromt the /greeting endpoint, but will get an ‘unauthenticated’ error when accessing the /secret_greeting endpoint. To make it a bit easier, here is some background information:

  • To access the /secret_greeting endpoint the rol admin is necessary
  • There is already an account named admin registered
  • Authentication is done via a JWT token in the Authorization header

Let’s take a look at some questions the hacker could think and which action it would lead to:

  • Is the authentication of /secret_greeting secured properly?
    • Send empty value for Authorization header
    • Don’t send Authorization header at all
  • Is the login secured properly? We could try to login as admin
    • Try empty password
    • Bruteforce password by trying common password (e.g. rockyou list)
    • Try social engineering (e.g. phishing)
  • Is the registration secured properly?
    • Try to register new account with name admin

All of the above are good things to try, but unluckily these won’t get us the secret greeting. It seems that everything is implemented properly, but of course we don’t give up that easily. There’s still the following question to answer: Is the JWT token secured properly? Let’s dive deeper into JWT.

The returned JWT looks as follows:

eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9wdWJsaWNfa2V5In0.eyJyb2xlIjoidXNlciJ9.GaUUwQR3RpXVFimlhxKdya1NCzORTpz0ytnTXyczNX4NLHx8HAGOzUwr3L9kcUFkQufweiyfgQ0dcfkH1Iu19xES8jj0NQbOf6Zs50PvNaDAny6KPhnofQw_cH3Et9fieec_zmF0ucXH9ztuQ2D0oCUSE9STU1TeCFR32QRl8t706mLIKXnfiAWHSEdAS5c-QL8oMs15jMY069Z2X3gDmYsWjh3DJVaT2tQ5-FVMrq19tbOvYLLSrJTDPgkJsrJjy9qV-J43dNm3DWej-Hoiv6T-M1PbMmEEKVyNfvoIZlz8zht9kbISbTiGRk4WYU4d7PFI6z11AeCkjX70X7YAGQ

Decoded from base64 we get three segments separated by dots:

// Header
{
  "alg": "RS256",
  "jku": "http://localhost:5000/public_key"
}
// Payload
{
  "role": "user"
}
// Signature
...

The first part, the header, contains metadata, like the algorithm used for the signature (in this case RS256). The second part, the payload, is a free format JSON object, where in this case a role property is set. The third part, the signature, uses the private key of the server to create a signature of the payload using the indicated algorithm. Now that we know this, there are some thoughts a hacker might have:

  • Is the algorithm implemented properly?
    • Use a different, less secure algorithm instead of RS256
  • Is the signature implemented properly?
    • Change the role in the payload segment to admin without changing the signature
    • Same as previous, but with empty signature

Nice try again, but we still get a 401…

How about: What is this JKU property?

Exploiting JWT using JKU

When in doubt, read the docs here:

 The "jku" (JWK Set URL) Header Parameter is a URI [RFC3986] that
   refers to a resource for a set of JSON-encoded public keys, one of
   which corresponds to the key used to digitally sign the JWS.  The
   keys MUST be encoded as a JWK Set [JWK].  The protocol used to
   acquire the resource MUST provide integrity protection; an HTTP GET
   request to retrieve the JWK Set MUST use Transport Layer Security
   (TLS) [RFC2818] [RFC5246]; and the identity of the server MUST be
   validated, as per Section 6 of RFC 6125 [RFC6125].  Also, see
   Section 8 on TLS requirements.  Use of this Header Parameter is
   OPTIONAL.

Since the ‘Header’ part of the token is not part of the signature, we can change the value of the jku property to anything we want. This means we can let it point to our own hosted service with our generated public key. Under the hood the server should retrieve the public key from the specified jku location and validate the signature in the token using the public key. Since we also control the signature included in the token we have control over the the signature and validation of the signature. This means we could do the following:

  1. Create our own public/private key pair
  2. Craft our own payload for the token
  3. Create a signature of the payload using our own private key pair
  4. Host a server which returns our public key
  5. Set the jku field in the header of our token to the url where we host our public key
  6. Combine all of these into a token and send this token to the server
  7. The server will retrieve our public key
  8. The server will validate the signature signed using our private key with our public key
  9. The signature should be valid and the server should think this token is valid and use what is in the payload

The generated token would look as follows:

eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTMzNy9wdWJsaWNfa2V5In0.eyJyb2xlIjoiYWRtaW4ifQ.n9iH3wVkOJrA6TcW35AVP_rLmUn-IIR9FK8mlKEB7zdbCTTnqfMSjEjQDYqg1xhxNCwesSIzerylU4U1IX4lH-Krm54NYZd79x7j-Bae5TR9X2iPJlmCpgV60ZKk9uKiwRwfH0Pj4hSWT9NYcLzHHjdrE6wSmrMLUJADXWGPq7L28MWrOlUdZ65bDEH_Ju682a-sl_5G91hkNHas7IPhnJ7uC2kiM-t04W2ePSqWl5x1as7v6ywliFKrBc0L_UAyZlnClVvv5NB8ZUqaVz9IPdrl5Wc5rmE3ww1CA2n4e8isX89s1AjKOEi5IT8qoLvPWYPL3cSN818DimF8LNKauw

Decoded:


// Header
{
  "alg": "RS256",
  "jku": "http://localhost:1337/public_key"
}
// Payload
{
  "role": "admin"
}
// Signature
...

And… this works! With this token we are an admin and we can access to the /secret_greeting endpoint. Thus the server allows the user to supply their own jku value and doesn’t check the value.

Possible mitigations

For this specific there are multiple possible mitigations:

  • Don’t allow the user to specify the jku value, but set it within the service
  • Check the jku value based on a regex or whitelist
  • Don’t allow traffic from within the network of the company to addresses outside the network

I think also knowing how to prevent this issue and similar issues in the future is valuable:

  • Ask help of senior/security engineers
  • Add code reviews and when doing those don’t just look at the syntax of code, but ask questions about what’s in between the lines of code
  • For each service/component decide the impact if there are security issues with it, for critical ones spend extra attention to them when reviewing

And of course the point I’m trying to make:

  • Try to understand how the code you write works
  • When in doubt whether the code does what you think, don’t make assumptions
  • When you’re not sure, try to find out what it does or ask for help
  • Don’t think about just the happy-flow, but also other flows the code can go through (in the above JKU example: what if someone provides their own URL?)

Conclusion

I hope this post showed an interesting exploit, but also a different way to look at software. Understanding the code you write instead of just assuming you do can be very beneficial to the security, but also to your own technical development.