OAuth 2.0

Recently I was pentesting a complex API which used the OAuth 2.0 framework for authentication. Each API call needed an Authorization: Bearer header, containing a valid JSON Web Token (JWT).

To access the API I needed a lot of JWT tokens, as the tokens had a very short expiry time. To facilitate the quick generation of tokens I created a basic script that automated the OAuth authorization: It logs on to a domain, requests an authorization code, and converts that token to an authorization token.

One or more of these steps can be circumvented by command line options (e.g. by specifying valid cookies), to speed up the process.

Another feature of the script is that it automatically performs GET, POST, PUTs and DELETEs with valid tokens against a list of API endpoints (URLs). This preloads all API calls into a(n) (attacking) proxy, and helped the pentest speed up tremendously.

JSON Web Tokens

A JSON Web Token (JWT) is basically a string, representing a collection of one or more claims. Claims are name/value pairs which state information about a user or subject. The claims are either signed using a JSON Web Signature (JWS) or encrypted using JSON Web Encryption (JWE). JSON Web Tokens serve as self-contained authorization tokens, represented using JWS or JWE compact serialization.

A token string consists of a header, payload and optional signature, all base-64 encoded and separated by a dot. An example in python using python-jwt is

# The namespace has changed from jwt to python_jwt from version 2.0.1
import python_jwt as jwt
token = jwt.generate_jwt({'name':'value'}, None, 'none')
print(token)

Output:

eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJpYXQiOiAxNDY2NDY5NDU5LCAianRpIjogIkE2RDVMOGRyV0VjREdhMTNnRW5lU3c9PSIsICJuYW1lIjogInZhbHVlIiwgIm5iZiI6IDE0NjY0Njk0NTl9.

The first part contains the header, declaring that the encoded object is a JWT, and that the payload is a JWT having no encryption/signature (note that I add some base64 padding for null bytes, as the base64 library mandates this):

import base64
header = 'eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0'
padded_header = header + '=' * (-len(header) % 4)
print(base64.b64decode(padded_header))

Output:

{"alg": "none", "typ": "JWT"}

The payload decodes to another JSON object, containing the claims, and some other items (issue time iat , unique identifier jti and not before time nbf ):

import base64
payload = 'eyJpYXQiOiAxNDY2NDY5NDU5LCAianRpIjogIkE2RDVMOGRyV0VjREdhMTNnRW5lU3c9PSIsICJuYW1lIjogInZhbHVlIiwgIm5iZiI6IDE0NjY0Njk0NTl9'
padded_payload = payload + '=' * (-len(payload) % 4)
print(base64.b64decode(padded_payload))
{"iat": 1466469459, "jti": "A6D5L8drWEcDGa13gEneSw==",
"name": "value", "nbf": 1466469459}

In this instance, the claim consists of 'name' = 'value'.

The last part, the signature is empty, as I chose 'none' as signature algorithm, which resulted in an unsecured JWT.

OAuth 2.0

OAuth is a framework used for authentication: Say user U wants to use API A. To use the API a user has to be authorized by domain D, the so-called Authorization Endpoint.

First the user logs onto the Authorization Endpoint using valid credentials (for example a valid username/password), and asking permission to access API A. If the domain authorizes user U to use the API, it can hand out an authorization code to the user. The user is then redirected to the Redirect Endpoint (usually the API).

This again redirects the user to the Token Endpoint (usually again Domain D), after which the authorization server hands out the token, containing a claim that user U is validated by domain D to use API A.

Now when user U wants to access API A, it sends along a Authorization: Bearer HTTP header, accompanied by the token. In this case, the token was a JSON Web Token.

The next automation step for the script could be that valid tokens are automatically generated, used or renewed by the attack proxy (Burp Suite Pro in my case). Due to a lack of time this hasn't been done during that particular pentest: It's always a fine line when automating pentests still makes sense versus 'manual' testing.

You can find the script at https://github.com/radicallyopensecurity/pentesting-scripts/blob/master/apiborker.py.

Feedback, improvements and especially pull requests are always welcome.

See https://tools.ietf.org/html/rfc7519 for more information on JSON Web Tokens, https://tools.ietf.org/html/rfc7797 for more information on JSON Web Signatures (JWS) and http://oauth.net/2/ on OAuth2.


Comments

comments powered by Disqus