M3: Insecure Communication
Currently, most mobile applications exchange data in a client-server fashion at some point. When these communications happen, data traverses either the mobile carrier’s network or between some Wi-Fi network and the internet.
Although exploiting the mobile carrier’s network is not an impossible task, exploiting a Wi-Fi network is usually much easier. If communications lack SSL/TLS, then an adversary will be able to not only steal the data, but also to execute Man-in-the-Middle (MitM) attacks.
The following video demonstrates Insecure Communications exploitation on Kotlin Goat mobile application. The movie shows network monitoring, what gives an adversary access to exchanged data nevertheless, due to insecure communication, Man-in-the-Middle (MitM) would also be possible.
Now that we have seen the exploitation taking place, it’s time to go back to the application source code and fix this issue. We will add SSL/TLS to all client-server communications and also implement Certificate Pinning to remove the “conference of trust” to no longer depend on Certificate Authorities or third-party agents regarding decisions on a server’s identity.
To enable SSL/TLS, we will need certificates to be available in the server. Nowadays you can get free certificates with Let’s Encrypt - a free, automated and open Certificate Authority. You’ll get the certificates deployed easily by following the documentation.
On Goatlin, we’ll go with a self-signed certificate. While this is a common practice during the development stage, it is not recommended for production systems. How to generate the certificate is out of scope for this guide.
With the certificate in hand, we should make a few changes on our back-end API to make it use HTTPS instead of HTTP:
- Put
server.key
andserver.crt
under thessl
directory - Replace
http
package withhttps
one - Load
server.key
andserver.crt
const https = require('https');
const fs = require('fs');
const path = require('path');
// ...
/**
* Create HTTP server.
*/
const sslDirectory = path.join(__dirname,'..','ssl');
const privateKey = fs.readFileSync(path.join(sslDirectory, 'server.key'), 'utf8');
const certficate = fs.readFileSync(path.join(sslDirectory, 'server.crt'), 'utf8');
const credentials = {key: privateKey, cert: certficate};
var server = https.createServer(credentials, app);
// ...
The following command line outputs our certificate fingerprint so that we can pin it on Goatlin:
openssl x509 -in server.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
OkHttp is one of the most commonly used libraries to manage network requests in Android.
Parameterising this library to use Certificate or Public Key Pinning is very simple. All we need to do is modify the create
method of our API service Client
interface, as shown below:
interface Client {
@POST("accounts")
fun signup (@Body data: Account): Call<Void>
companion object {
fun create(): Client {
val certificatePinner = CertificatePinner.Builder()
.add("192.169.1.87:8080", "sha256/5Kl14sIBRoArZ8ujwNLWoLOI1QmsvE58nmXTO/9GSJw=")
.build()
val client: OkHttpClient = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://192.168.1.87:8080")
.client(client)
.build()
return retrofit.create(Client::class.java)
}
}
}
You can test Certificate Pinning by switching to feature/m3-insecure-communication branch. Replacing the back-end API certificates or the fingerprint on Goatlin source code will break the signup feature.