The importance of protecting your APIs with SSL pinning
Tags: android, security, ssl, tls
Published: 2020-11-20 09:53:47

## Preface
Networking is a core part of most apps, passing information between the application client and backend system(s). A very standard approach to this communication is TLS, a protocol meant for message integrity and message privacy between communicating parties. When establishing a connection using the TLS protocol, the first step is the supplying and verification of certificates.

E.g. a client initiates a request and the server responds with one or more digital certificates, which are then verified (or not) by the client. This verification establishes trust between the communicating entities. Once trust has been established, the cryptograhpic step can commence and finally the information exchanges can take place, using said encryption/decryption schemes. 

So with SSL certificates set up and the TLS protocol invoked, our precious and secret information is securely communicated between front- and backend systems, right?

Many developers will assume so (or do not care about their network security), leaving handling of the TLS session to the OS. 

## Quick history tour
Any app will by default trust the system root certificate, but will on versions before Android 7 (Nougat), by default also trust user-defined certificates just as much. This being an obvious security issue, without developer intervention, was then changed in Android 7 and onwards to no longer accepting user-defined certificates by default. While this does make it so that the communication cannot be hijacked out-of-the-box, a very simple effort will open the security issue right back up.

Let's see how easy it is to listen in on a supposedly secure channel.

## Setting the stage
Taking a look at a real world example, I have chosen the official parking app from the Swedish municipality of Gothenburg, [Parkering Göteborg](https://play.google.com/store/apps/details?id=se.goteborg.pbolaget.android).

Being a parking app, the information flowing about includes the license plates of cars, credit cards for charging, along with personal user information etc. Information that *really* should be kept secure.

DISCLAIMER: *I have been in contact with the developers of this app roughly 6 months ago, and made them aware of the issues presented here. Since then, the application have received numerous updates - including its security aspects.*

## Exposing HTTPS traffic
Here is a short outline of how to expose TLS communication in Android apps which rely on the OS to handle everything about the session.

### Tools needed:
- [APKTool](https://ibotpeaches.github.io/Apktool/documentation/)
- [Charles Proxy](https://www.charlesproxy.com/) (Fiddler2 can be used alternatively)
- Java build tools 24.0.2+
- Android SDK

The Java build tools and Android SDK are only used for signing the apk.

### Make the app trust user Certificate Authorities
Since apps will not trust user certificate authorities (since Android 7) just because the device trusts them, a small modification to the app must be made.

#### Download apk of target app
Usually you can find the app at APKMirror or similar sites.
E.g. [APKPure](https://apkpure.com/parkering-g%C3%B6teborg/se.goteborg.pbolaget.android)

#### Decode apk file
`apktool d app.apk -o decoded_app`

#### Modify AndroidManifest.xml
Inside the `decoded_app` folder, find and open up the `AndroidManifest.xml` file.
Add `android:networkSecurityConfig="@xml/network_security_config"` to the `application` tag.

```xml
<!-- AndroidManifest.xml -->
...
<application 
	android:allowBackup="false" 
	android:appComponentFactory="androidx.core.app.CoreComponentFactory" 
	android:icon="@mipmap/ic_launcher" 
	android:label="@string/parking_application_name" 
	android:name="se.goteborg.pbolaget.android.ParkingApplication" 
	android:theme="@style/AppTheme"
	android:networkSecurityConfig="@xml/network_security_config"
	>
...
```

#### Trust user Certificate Authorities
To make the app trust user-defined certificates once again, the network security configuration file simply needs to add the user argument to a certificate tag.

The `network_security_config.xml` file

```xml
<!-- network_security_config.xml -->
<network-security-config>  
	<base-config>  
		<trust-anchors>
			<!-- Additionally trust user added CAs -->  
			<certificates src="user" />  
		</trust-anchors>  
	</base-config>  
</network-security-config>
```
Add `network_security_config.xml` to the `decoded_app/res/xml` folder.

#### Rebuild apk file
`apktool b decoded_app -o ca_modified_app.apk`

The new apk file can then be found at either `decoded_app/dist/ca_modified_app.apk` or as a sibling to the `decoded_app` folder.

#### Sign the modified app
Here, I am using the default created android debug keystore to sign the apk
`jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore {some_path_x}\.android\debug.keystore {some_path_y}\ca_modified_app.apk androiddebugkey -storepass android`

#### Install the modified app
Transfer the `ca_modified_app.apk` to an Android device and press to install.

You may have to trust installations from outside Google Play (should prompt).

We now effectively have a copy of the original app installed on our device, which trusts user-defined certificates. 
Now we just need to install one such certificate.

### Install Charles' SSL certificate
On your device visit https://chls.pro/ssl to download the Charles SSL certificate.

After the download completes, install the certificate.

### Man-in-the-middle
This step sets up your PC to act as an intermediary between the client and the server, using the HTTP(S) proxy.

Open network settings for the WiFi network your device is connected to.

Wi-Fi &rarr; Long press connected network &rarr; Modify network &rarr; Advanced options &rarr; Set Proxy to Manual &rarr; As hostname; enter the IPv4 address of your computer, which must be connected to the same network &rarr; As port, CharlesProxy defaults to 8888.

#### Enable SSL Proxy in Charles
Top menu &rarr; Proxy &rarr; SSL Proxying Settings &rarr; SSL Proxying &rarr; Check Enable SSL Proxying &rarr; Include location host = \* and port = \* (wildcards)

#### Reading the network traffic
Now, browsing the app will redirect all network traffic through the SSL proxy, which certificate is trusted by the device, making the traffic visible to the proxy (Charles), with exposed and unencrypted `https` requests and responses.

![Exposed API of Parkering Göteborg](https://dev-to-uploads.s3.amazonaws.com/i/diy8dqhpr4qqhy89qycq.png)

## SSL pinning
So what can we, as developers, do to avoid this issue? Que SSL pinning.

SSL pinning is a technique which entails embedding your server certificate into the app itself. Once networking happens, the app can simply compare the certificate of the responding entity to the embedded certificate. If they do not match, chances are the response is not coming from our server. For more information on SSL security implementations, check out [the docs](https://developer.android.com/training/articles/security-ssl).

### The but
Although SSL pinning is neat, this security installment can be bypassed. However it is a much more involved process, and requires rooting the device amongst other things. Alas, we can rarely achieve complete security.

## Updates
The most recent Android version, Android 11 (September 8. 2020), introduces further certificate installation restrictions, however seemingly only those prompted by applications. So while this prevents app automation of the man-in-the-middle attack presented here, the manual process will likely still work.

I hope you enjoyed this blog post. If you have any questions, feel free to comment below!