Are You Sure That You’re Not Leaking User’s Auth Tokens To Third Parties?

The behaviour of retaining Auth headers by OkHttp during redirection

Shiv Sahni
Jun 30 · 6 min read
Image for post
Image for post

Are you an Android developer who is inclined towards security or an application security guy who’s keen to connect multiple dots to identify a cool security bug? Were you oblivious that OkHttp, the Friend of Android developers retains auth headers during redirection? If yes, then this story would be interesting for you. It talks about the behaviour of OkHttp to retain auth headers during redirection to third-party domains.

To better understand this, let us first brush up some fundamental things around the issue. If you are already aware of OkHttp and concept of application and network Interceptors, you may skip the following sections and directly start from The Problem!

What is OkHttp?

OkHttp is an HTTP client, it is a third-party library developed by Square for sending and receiving HTTP-based network requests. It is also the underlying library for Retrofit which is another HTTP client used widely these days.

OkHttp Interceptors

Interceptors are a powerful mechanism that can monitor, rewrite, and retry network calls. For large-scale applications where we have numerous APIs communicating with our backend, it can be tedious and super-repetitive to write common logics such as encrypting the request body, decrypting the response body, attaching access tokens to request, handling specific responses(e.g. HTTP-401-Unauthorized/HTTP-403-Forbidden), etc. for each and every API call and this is where Interceptors helps us to rescue.

The way we have API Gateway at the backend which acts as an entry point from the backend side, we can consider Interceptors to be the gateway for all the request that our frontend makes.

Application Vs. Network Interceptors

The OkHttp Interceptors can be registered as either Application or Network interceptors as shown below:

Image for post
Image for post
Reference: Square
client = new OkHttpClient.Builder()
.addInterceptor(new SessionTokenInterceptor())

Network Interceptor

client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())

When we attach an Interceptor as a Network interceptor the interception logic is applied at the intermediate request and response as well, for example, redirects and retries. Whereas, in the case of Application interceptors the logic is only applied to the first request and the last response in the case of redirects.

Let us understand it through the example quoted in the official docs wherein we have written a LoggingInterceptor which logs the request and responses and we make a network call to the URL http://www.publicobject.com/helloworld.txtwhich redirects to https://publicobject.com/helloworld.txtby returning a HTTP-3XX response.

In the case the LoggingInterceptor is attached as an application interceptor we get the following in the Logs:

INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example

INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

And when it is attached as a network interceptor the following is pushed to the logs:

INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt

INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

I think you should be having enough understanding of the fundamentals to properly understand and identify the security issue.

The Problem!

The security issue arises when we use an Interceptor for attaching the access token to the HTTP request and this Interceptor is added to the OkHttp client as an application interceptor.

The application is safe if any of this stands true:

  • You are not using Interceptors for adding access tokens
  • You pass access tokens to backend using an HTTP header named Authorization
  • You are using Interceptors but your backend never redirects the users to any third-party domains

Since OkHttp Application Interceptors automatically attaches the headers to the redirects even if the redirection is to external domains if the access token is passed using any name except Authorization. I tested this behaviour on OkHttp v3.5

Demo Time

Image for post
Image for post

For the demo purposes, I have created a sample Android application that makes an API call using OkHttp to backend and gets a redirection response. You can find the source code here.

I have also used two interceptors viz. SessionTokenInterceptor and LoggingInterceptor. The former one is used to attach the session token to the HTTP request and the latter one is used to log all the requests and response in order to determine unintended third-party data leakage. The following are the code snippets from the application’s code

SessionTokenInterceptor

public Response intercept(Chain chain) throws IOException {

Request request = chain.request();
Request newRequest=request.newBuilder().addHeader("Auth", sessionToken)
.build();
return chain.proceed(newRequest);

}

LoggingInterceptor

@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.i("Logging Interceptor",String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.i("Logging Interceptor",String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}

The SessionTokenInterceptor is added as an application interceptor and the LoggingInterceptor is added as a network interceptor because of the obvious reasons. The same is depicted through the below-mentioned code snippet.

client = new OkHttpClient.Builder()
.addInterceptor(new SessionTokenInterceptor())
.addNetworkInterceptor(new LoggingInterceptor())
.build();

You will observe that the access token attached through the SessionTokenInterceptor(added as an application interceptor) is sent to the redirects in the below-mentioned logs and this is how you might be leaking user’s access tokens to third-parties.

2020-02-27 16:35:17.143 9877-9953/com.example.okhttpinterceptorissue I/Logging Interceptor: Sending request http://192.168.1.70:5000/redirect on Connection{192.168.1.70:5000, proxy=DIRECT hostAddress=/192.168.1.70:5000 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Auth: ThisIsASampleSecretSessionToken
Host: 192.168.1.70:5000
Connection: Keep-Alive
Accept-Encoding: gzip
2020-02-27 16:35:17.152 9877-9953/com.example.okhttpinterceptorissue I/Logging Interceptor: Received response for http://192.168.1.70:5000/redirect in 8.2ms
Content-Type: text/html; charset=utf-8
Content-Length: 279
Location: http://goidirectory.nic.in/index.php
Server: Werkzeug/0.15.6 Python/2.7.16
Date: Thu, 27 Feb 2020 08:35:18 GMT
2020-02-27 16:35:17.163 9877-9953/com.example.okhttpinterceptorissue I/Logging Interceptor: Sending request http://goidirectory.nic.in/index.php on Connection{goidirectory.nic.in:80, proxy=DIRECT hostAddress=goidirectory.nic.in/164.100.58.217:80 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Auth: ThisIsASampleSecretSessionToken
Host: goidirectory.nic.in
Connection: Keep-Alive
Accept-Encoding: gzip
2020-02-27 16:35:17.367 9877-9953/com.example.okhttpinterceptorissue I/Logging Interceptor: Received response for http://goidirectory.nic.in/index.php in 202.6ms
Date: Thu, 27 Feb 2020 08:34:42 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Referrer-Policy: same-origin
Feature-Policy: microphone 'none'; payment 'none'; sync-xhr 'self'
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Set-Cookie: PHPSESSID=hdddiq459djqfadibfgjmattg6; path=/
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

Note: It may sound like the attack can not be directly exploited as the backend is supposed to send the redirects to external domains(third-parties) which is usually not expected. However, this can be clubbed with other attack vectors such as HTTP Request Smuggling wherein the attacker already poisoned the request queue and the backend servers sends the redirects to attacker’s domain.

Mitigation

As soon as I discovered this, I notified the OkHttp team and as per the OkHttp’s security team, the behaviour is intended and involves low risk.

I think our next steps should be to encourage developers to use the Authorization header when using HTTP headers that contain secrets against backends that may redirect to an untrusted server.

Hence I thought of writing this blogpost to let devs know about the current behaviour after the approval from the security team.

They suggest that devs should use standard headers(Authorization and Cookies) to send the session identifiers instead of non-standard header names

In case changing the name of auth header is difficult and the redirection is expected, I suggest disabling auto redirects usingfollowRedirects(false) and using an additional application interceptor to check if the response is a redirection response and continue the redirection if and only if the domain in the redirection response is an expected response.

InfoSec Write-ups

A collection of write-ups from the best hackers in the…

By InfoSec Write-ups

Newsletter from Infosec Writeups Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Shiv Sahni

Written by

Security Engineer |Security Consultant |Infosec Trainer | Author | Lecturer | Open Source Contributor | Learner https://www.linkedin.com/in/shivsahni/

InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. In a nutshell, we are the largest InfoSec publication on Medium. Maintained by Hackrew

Shiv Sahni

Written by

Security Engineer |Security Consultant |Infosec Trainer | Author | Lecturer | Open Source Contributor | Learner https://www.linkedin.com/in/shivsahni/

InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. In a nutshell, we are the largest InfoSec publication on Medium. Maintained by Hackrew

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store