Security controls in Android – SSL pinning

Je hebt vast weleens van de term SSL-pinning gehoord. Maar wat is dat nou precies? En hoe doe je dat? In dit artikel hopen we deze vragen te beantwoorden door te kijken naar de verschillende pinning-methodieken. Daarnaast zullen we een voorbeeld geven van een implementatie en laten we zien hoe je kunt testen of je pinning strategie werkt.

Nanne Baars & Jeroen Willemsen 

Even opfrissen: SSL en pinning

In een eerdere editie van Java Magazine is er een artikel geschreven over SSL/TLS. In dit artikel is onder andere uitgelegd dat dit werkt middels certificaten. Toch nog even een korte samenvatting.

 

In afbeelding 1 is op versimpelde wijze te zien hoe een Android-applicatie informatie ophaalt via HTTPS. Tijdens de handshake met de server van https://uwdomein.nl wordt er een servercertificaat aangeboden waar de public key van de server op staat. Deze kan de Android client dan weer gebruiken om een symmetrische sleutel mee te versleutelen, die later gebruikt wordt voor het daadwerkelijke transport van de informatie.

Om er zeker van te zijn dat het servercertificaat klopt, is deze ondertekend met een handtekening. Deze handtekening is te controleren met de publieke sleutel van het intermediate/rootcertificaat. Het rootcertificaat is vertrouwd door het Android-toestel, waardoor je enige garantie hebt over de mate waarin je de publieke sleutel van de https://uwdomein.nl server kunt vertrouwen.

Dit is natuurlijk een heel mooi principe, alleen zijn we er inmiddels achter gekomen dat alle Certificate Authorities (CA) die we vertrouwen, niet altijd te vertrouwen zijn. Zo werd DigiNotar (een Nederlandse CA) gehackt. Hierdoor konden aanvallers certificaten uitgeven om man-in-the-middle aanvallen mee uit te voeren. Overigens kan het probleem ook “dichter bij huis” zijn: zo kun je zelf (per ongeluk) verkeerde certificaten op je Android-apparaat zetten.

Daarom bestaat er de vraag: hoe verzeker je jezelf ervan dat je Android-applicatie alleen met de juiste server praat?

Waar pin je op?

Om er zeker van te zijn dat je met de juiste server praat, kun je vastleggen waar het certificaat van het TLS endpoint aan moet voldoen. Pinnen noemen we dat. Er zijn een aantal varianten:

 

 

Certificate pinning

Bij certificate pinning kijk je naar het gehele certificaat van het endpoint: alles moet hetzelfde zijn, zoals jij dat in je applicatie hebt vastgelegd. Het nadeel hieraan is dat je applicatie niet verder kan als het certificaat vervangen of verlopen is. Je zult dan een backup-strategie moeten hebben. Je kunt een backup certificaat klaarzetten in je applicatie of een update uitrollen. Tevens kun je dit nadeel enigszins tegengaan door op een intermediate- of rootcertificaat te pinnen, maar dit heeft weer zijn eigen nadelen.

Public key pinning

Door de levenscyclus van de certificaten, blijf je aardig wat onderhoud houden. Het belangrijkste cryptografische onderdeel is de public key in het certificaat. Interessanter is dus ook om daar op te pinnen. Dit is wat je doet met public key pinning. Helemaal onderhoudsvrij ben je dan natuurlijk nog niet: je moet je er namelijk wel van verzekeren dat je bij het vernieuwen van het certificaat dezelfde publieke sleutel gebruikt en/of anders ook je publieke sleutel in je app vernieuwd. Hierbij zijn er twee varianten: public key pinning (je pint direct op de public key value) en subject public key pinning (je pint op de hashes over de public key en de algorithm identifier). Zie hiervoor Afbeelding 2.

Waarom zou je wel of niet pinnen?

Er zijn een aantal redenen om wel of niet te pinnen. Laten we beginnen met “waarom niet”:

Niet: Ondermijning van trust-relaties

De puristen onder ons vinden pinning vreselijk! Waarom zou je het trust-model ondermijnen waar onze PKI/CA-infrastructuur op gebaseerd is?

Niet: Apps kunnen niet meer functioneren na verandering van de certificate/public key

Een uitdaging met certifcate- en public key pinning is dat je eigenlijk alleen een goede pinning strategie kunt opbouwen, waarbij de definities waarop je pint, vastzitten in de code. Hard coded noemen we dat. Dit betekent dus ook dat als een certificaat of public key vervangen wordt, je dus ook je app moet vervangen. Gebruikers kunnen dan de oude versie van de app niet meer goed gebruiken. Een alternatieve oplossing voor dit probleem is dat je toekomstige public keys alvast in je lijst opneemt om op te gaan pinnen op termijn.

Wel: Meer zekerheid over het endpoint waar je mee verbindt

Door certificate pinning heb je wel meer zekerheid over het endpoint waar je mee communiceert. Je kunt alleen met het endpoint communiceren, dat in bezit is van het desbetreffende certificaat of de public key waar je op pint.

Wel: Het trust-model van SSL/TLS geeft geen garanties: DigiNotar

Zoals eerder in het artikel al besproken, kan een CA met beveiligingsproblemen geconfronteerd worden, waardoor er ongeldige certificaten uitgegeven kunnen worden. Daarnaast kunnen aanvallers soms ook malafide CA-certificaten op apparaten installeren om zo hun eigen certificaten aan te bieden. Om er zeker van te zijn dat je met het juiste endpoint verbindt, moet je niet vertrouwen op wat een CA je aanbiedt, maar vertrouwen op hetgeen wat je in de app verwacht.

En dan de browsers? Waarom apps wel?

Een ander vaak gehoord argument is: waarom zou je het op mobiele apparaten wel doen, terwijl de browsers het lang niet allemaal ondersteunen? Een antwoord op deze vraag kan zijn: “laten we het gelijk trekken”. Een ander antwoord kan zijn: je kunt het gemakkelijk realiseren op mobiele apparaten. Dus een betere vraag is dan: waarom niet?

 

(Subject) Public key pinning met okhttp 3.0

Okhttp3 is een van de meest populaire HTTP clients op Android. Je vindt deze client vaak terug in combinatie met populaire libraries, zoals Retrofit. Onderstaande code laat zien hoe je pint op bijvoorbeeld www.google.nl.

 

CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(“google.nl”, “sha256/hBdbADOPuEzNHupXeQ3Bmfxlt7cdyu12731ZNW7Wkb4=”)
.build();
return new OkHttpClient.Builder().certificatePinner(certificatePinner).build();

 

 

Hoe test je dit?

Om er zeker van te zijn dat het pinnen gelukt is, probeer je het verkeer af te luisteren door er een proxy tussen te zetten, zoals de OWASP Zed Attack Proxy. Natuurlijk kun je ook andere proxies gebruiken.

  1. Allereerst stel je ZAP in om de locale proxy te zijn op het IP-adres van het netwerk, waarmee je zowel je computer als je Android-apparaat verbindt.
  2. Vervolgens genereer je een certificaat.

 

  1. Dit certificaat sla je op en stuur je naar het Android-apparaat via ADB.

 

adb push owasp_zap_root_ca.cer sdcard/

 

  1. Stel je Android-toestel in om je proxy te gebruiken.
  2. Doe een request naar google.nl via je applicatie en zie dat het request niet doorkomt.

Hoe breek je dit?

Er zijn een aantal dingen die je kunt doen om SSL-pinning tegen te gaan:

  1. Het aanpassen en repacken van de applicatie: je past de applicatie aan om de pinner uit te zetten. Hiervoor heb je tools nodig, zoals APK-tool (https://ibotpeaches.github.io/Apktool/) .
  2. Het gebruik van Xposed modules op het Android-toestel. Een goed voorbeeld is https://github.com/ac-pm/SSLUnpinning_Xposed waarmee je de pinning ongedaan kunt maken.

 

Je hoeft niet altijd de verbinding te verbreken

Mocht de pinner niet meer werken, dan kun je er altijd voor kiezen om een nieuwe verbinding op te zetten zonder pinning. Met deze verbinding kun je de server informeren over het feit dat je pinning gebroken is. Op die manier hoeft de continuïteit van je applicatie niet in het gedrang te komen en kan je ook het gedrag van de backend aanpassen.

 

What’s next?

Nu heb je gezien hoe pinning werkt. Uiteraard is hier veel meer over te schrijven! Vind je dit soort onderwerpen interessant? Laat het ons weten via info@nljug.org en dan komen we terug met meer beveiligingsonderwerpen of gaan we de diepte in met SSL-pinning.