Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port MASTG-TEST-0022: Testing Custom Certificate Stores and Certificate Pinning (android) (by @guardsquare) #3035

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
56 changes: 53 additions & 3 deletions Document/0x05g-Testing-Network-Communication.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,17 @@ The default configuration for apps targeting Android 6.0 (API level 23) and lowe
</base-config>
```

#### Certificate Pinning
### Certificate Pinning

The Network Security Configuration can also be used to pin [declarative certificates](https://developer.android.com/training/articles/security-config.html#CertificatePinning "Certificate Pinning using Network Security Configuration") to specific domains. This is done by providing a `<pin-set>` in the Network Security Configuration, which is a set of digests (hashes) of the public key (`SubjectPublicKeyInfo`) of the corresponding X.509 certificate.
Certificate pinning is a critical security mechanism employed in Android applications to safeguard against man-in-the-middle (MITM) attacks by ensuring that the app communicates exclusively with servers possessing predefined cryptographic credentials.

While effective when implemented correctly, insecure implementations potentially enable attackers to read and modify all communication. See @MASWE-0047 for more details.

Various approaches exist, depending on the API level of the app, and on the used libraries. In the following, the most common ones are briefly highlighted.

#### Pinning via Network Security Configuration (API 24+)

The Network Security Configuration can be used to pin [declarative certificates](https://developer.android.com/training/articles/security-config.html#CertificatePinning) to specific domains. This is done by providing a `<pin-set>` in the Network Security Configuration, which is a set of digests (hashes) of the public key (`SubjectPublicKeyInfo`) of the corresponding X.509 certificate.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The Network Security Configuration can be used to pin [declarative certificates](https://developer.android.com/training/articles/security-config.html#CertificatePinning) to specific domains. This is done by providing a `<pin-set>` in the Network Security Configuration, which is a set of digests (hashes) of the public key (`SubjectPublicKeyInfo`) of the corresponding X.509 certificate.
The **Network Security Configuration (NSC)** is the preferred and recommended way to implement certificate pinning in Android, as it provides a declarative, maintainable, and secure approach without requiring code changes. It applies to all network traffic within the app, including `HttpsURLConnection`-based connections and `WebView` requests (unless a custom `TrustManager` is used).
To use [NSC for pinning](https://developer.android.com/training/articles/security-config.html#CertificatePinning), a `<pin-set>` is defined in the `network_security_config.xml` file. This `<pin-set>` contains the **hashes of the public key (`SubjectPublicKeyInfo`)** from the X.509 certificate of the trusted server.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It applies to all network traffic within the app

this is not completely true, the NSC only applies to connections done through the Android stack. Communication via low level APIs (e.g. from native code) do not take this into account.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to:

The Network Security Configuration (NSC) is the preferred and recommended way to implement certificate pinning in Android, as it provides a declarative, maintainable, and secure approach without requiring code changes. It applies to all network traffic managed by the Android framework within the app, including HttpsURLConnection-based connections and WebView requests (unless a custom TrustManager is used). For communication from native code, NSC does not apply, and other mechanisms need to be considered.


When attempting to establish a connection to a remote endpoint, the system will:

Expand All @@ -105,7 +113,7 @@ If at least one of the pinned digests matches, the certificate chain will be con
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
Use certificate pinning for OWASP website access including sub domains
<!-- Use certificate pinning for OWASP website access including sub domains -->
<domain includeSubdomains="true">owasp.org</domain>
<pin-set expiration="2018/8/10">
<!-- Hash of the public key (SubjectPublicKeyInfo of the X.509 certificate) of
Expand All @@ -119,6 +127,48 @@ If at least one of the pinned digests matches, the certificate chain will be con
</network-security-config>
```

!!! note "Backup Pin"
When implementing Certificate Pinning you should always include a backup pin to ensure that the app's connectivity is unaffected, in case you need update the certificate. A backup pin could be the intermediate certificate of the CA.

!!! note "Expiration dates"
If you [set an expiration date](https://developer.android.com/privacy-and-security/security-config#CertificatePinning), make sure to update your application in time. Otherwise pinning will **not** be performed at all after the configured date.

!!! warning "Technologies not using Network Security Configuration"
If your application uses low level networking APIs or SDKs like Flutter, the Network Security Configuration might not be used by default. In these cases, you will need to enable certificate pinning specifically for the technology used.
For example, applications based on Cordova do not support Certificate Pinning natively, so the plugin [PhoneGap SSL Certificate Checker](https://github.com/EddyVerbruggen/SSLCertificateChecker-PhoneGap-Plugin) can be used.

### Certificate Pinning using the OkHttp Library

The OkHttp library is widely used for certificate pinning in Android due to its built-in `CertificatePinner` class, which enables developers to pin public key hashes or certificate signatures. This provides robust protection against MITM attacks.

For example, [CertificatePinner](https://square.github.io/okhttp/features/https/#certificate-pinning-kt-java) can be set up as follows:

```java
val client = OkHttpClient.Builder()
.certificatePinner(
CertificatePinner.Builder()
.add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
.build())
.build()
```

### Certificate Pinning using TrustKit

For Android versions prior to API 24, the [TrustKit library](https://github.com/datatheorem/TrustKit-Android) offers a backward-compatible solution. TrustKit validates pins against the cleaned certificate chain. However, improper configuration — such as enabling non-default options like CA pinning — can reintroduce [vulnerabilities akin to custom implementations](https://www.blackduck.com/blog/ineffective-certificate-pinning-implementations.html).

### Custom Certificate Pinning

Certificate pinning can be implemented manually by overriding `TrustManager` or `HostnameVerifier` in [`HttpsURLConnection`](https://developer.android.com/reference/java/net/HttpURLConnection). This approach is highly error-prone, as it often involves directly inspecting server-sent certificates via `getPeerCertificates()`, which returns unvalidated chains that [be manipulated by attackers](https://www.blackduck.com/blog/ineffective-certificate-pinning-implementations.html).

!!! warning "Implementing Certificate Pinning Manually"
Implementing certificate pinning manually has a high risk of adding functionality to your application that makes the app even less secure. If you are adding this manually, take extreme care of implementing this correctly.

### Vulnerable Third-Party Libraries

Third-party libraries like older versions of the Secure-HTTP Cordova plugin or misconfigured PhoneGap plugins historically introduced vulnerabilities by [mishandling certificate chains](https://www.blackduck.com/blog/ineffective-certificate-pinning-implementations.html). These libraries often exposed non-default configuration options that, when enabled, allowed attackers to bypass pinning by injecting untrusted certificates into the chain.

Developers must audit third-party libraries for adherence to chain-validation best practices and prefer those leveraging the Android Keystore system for pinning.

### Security Provider

Android relies on a [security provider](https://developer.android.com/training/articles/security-gms-provider.html "Update your security provider to protect against SSL exploits") to provide SSL/TLS-based connections. The problem with this kind of security provider (one example is [OpenSSL](https://www.openssl.org/news/vulnerabilities.html "OpenSSL Vulnerabilities")), which comes with the device, is that it often has bugs and/or vulnerabilities.
Expand Down
38 changes: 38 additions & 0 deletions tests-beta/android/MASVS-NETWORK/MASTG-TEST-0240.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: Missing Certificate Pinning in Code
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to split the tests according to the sections we defined in 0x05g.

For example, here we can create another static test for the custom Trust Manager case. Does the app use this approach and if it does, is it correct? For example: uses a Trust Manager that does nothing and therefore trusts everything.

You can open a ticket for that if you prefer, so we can get this PR merged soon and keep this test focused on the NSC way. Please update the content below to be NSC specific only.

Suggested change
title: Missing Certificate Pinning in Code
title: Missing Certificate Pinning in Network Security Configuration

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a task, this PR is getting quite big.
#3183

platform: android
id: MASTG-TEST-0240
type: [static]
weakness: MASWE-0047
---

## Overview

Apps can configure certificate pinning using the [Network Security Configuration]("../../../Document/0x05g-Testing-Network-Communication.md#certificate-pinning"). For each domain, one or multiple digests can be pinned.

Certificate pinning can also be done manually in the code. Depending on the used technologies, this can be done for example by:

- Pinning a certificate with a custom `TrustManager`,
- configuring the used third party networking libraries to pin certificates,
- use plugins to achieve certificate pinning for hybrid apps.

Chapter [Certificate pinning without Android Network Security Configuration]("../../../Document/0x05g-Testing-Network-Communication.md#certificate-pinning-without-android-network-security-configuration") explains in more detail how this can be achieved in the app.

The goal of this test is to check if any certificate pinning exists.

!!! note "Limitations"
Since there are many different ways to achieve certificate pinning in the code, checking statically if the application performs pinning might not reveal all such locations. To make sure certificates are pinned for all relevant connections, additional dynamic analysis can be performed.

## Steps

1. Reverse engineer the app (@MASTG-TECH-0017).
2. Inspect the AndroidManifest.xml, and check if a `networkSecurityConfig` is set in the `<application>` tag. If yes, inspect the referenced file, and all domains which have a pinned certificate.
3. Run a static analysis tool such as @MASTG-TOOL-0011 or @MASTG-TOOL-0018 on the code and look for APIs or configurations performing certificate pinning (see above). Extract all domains for which the certificates are pinned.

## Observation

The output should contain a list of domains which enable certificate pinning.

## Evaluation

The test case fails if any relevant domain does not enable certificate pinning.
26 changes: 26 additions & 0 deletions tests-beta/android/MASVS-NETWORK/MASTG-TEST-0241.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: Expired Certificate Pins
platform: android
id: MASTG-TEST-0241
type: [static]
weakness: MASWE-0047
---

## Overview

Apps can configure expiration dates for pinned certificates in the [Network Security Configuration]("../../../Document/0x05g-Testing-Network-Communication.md#certificate-pinning"). After the expiration date, the pin is no longer used and all installed CAs for that domain are trusted.

The goal of this test is to check if any expiration date is in the past.

## Steps

1. Reverse engineer the app (@MASTG-TECH-0017).
2. Inspect the AndroidManifest.xml, and check if a `networkSecurityConfig` is set in the `<application>` tag. If yes, inspect the referenced file, and extract the expiration dates for every domain.

## Observation

The output should contain a list of expiration dates for pinned certificates.

## Evaluation

The test case fails if any expiration date is in the past.
29 changes: 29 additions & 0 deletions tests-beta/android/MASVS-NETWORK/MASTG-TEST-0242.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Missing Certificate Pinning in Network Traffic
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a second dynamic test that uses Frida e.g. via objection or other scripts that try to bypass pinning in order to detect it. There are caveats of course as the original test indicates but it's useful.

See https://mas.owasp.org/MASTG/techniques/android/MASTG-TECH-0012/

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpholguera not sure I understand what you mean. The link you mention explains how to deactivate pinning using Frida etc. How would that help identifying if pinning is done / done correctly?

What could be (somewhat) helpful, is to try the Frida scripts to see where it attaches, to then statically analyse those locations (relating to the to-be-created test for #3183).

Am I missing something?

platform: network
id: MASTG-TEST-0x242
type: [static]
weakness: MASWE-0047
---

## Overview

There are various ways how certificate pinning can be done for an application.

Since statically finding all of the locations where certificate pinning is performed might not be feasible, this test case uses dynamic analysis to observe all connections the app makes.

The goal of this test case is to dynamically check if the connection to a server can be intercepted using a [Man-in-the-Middle attack]("../../../Document/0x04f-Testing-Network-Communication.md#mitm-attack). If this is possible, it means that the certificate is not pinned correctly or not pinned at all.

## Steps

1. Set up an intercepting proxy, for example @MASTG-TOOL-0077 or @MASTG-TOOL-0097.
2. Install the application on a device connected to that proxy, and intercept the communication.
3. Extract all domains which were intercepted.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And monitor logcat for clues while doing this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpholguera which clues do you have in mind?
The way I did this before was to just look at the intercepting proxy, logcat from the device never added anything too useful for this particular test.


## Observation

The output should contain a list domains, for which the interception was successful.

## Evaluation

The test case fails if any relevant domain was intercepted.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about...?

Suggested change
The test case fails if any relevant domain was intercepted.
The test fails if any **security-sensitive domain** was intercepted.

We could add a new prerequisite and link to it here. As in https://github.com/OWASP/owasp-mastg/blob/master/prerequisites/identify-security-relevant-contexts.md

For example (REVIEW, AI generated):

Identifying Security-Sensitive Relevant Domains

What is a Security-Sensitive Relevant Domain?

A relevant domain is any endpoint where failing to enforce certificate pinning could expose sensitive data or compromise security. This typically includes:

  • Authentication endpoints (e.g., login APIs, OAuth/token exchanges)
  • Endpoints handling sensitive user data (e.g., financial transactions, personal data transfers)
  • Endpoints that initiate encrypted tunnels (e.g., VPN, secure messaging services)
  • Internal API endpoints used for secure app-server communication

What is NOT a Security-Sensitive Relevant Domain?

  • Third-party CDNs or analytics services that the app does not control

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpholguera I agree this would benefit a lot from a definition. Maybe sth to discuss in our next meeting, I don't think this is easy to define.

Imo, security-sensitive depends a lot on the app context, but also on the test / weakness itself. I could imagine certificate pinning being mandatory for L2 for specific domains, but it certainly shouldn't be mandatory for all domains for L1.

In the AI generated text, the 3rd party or analytics service is already not ideal imo. Whilst I agree with this for certificate pinning, I don't think analytics services are by default not security sensitive. E.g., thinking about GDPR, I can imagine personal identifiers being sent there, which need to be handled with care and are security sensitive...

3 changes: 3 additions & 0 deletions tests/android/MASVS-NETWORK/MASTG-TEST-0022.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ platform: android
title: Testing Custom Certificate Stores and Certificate Pinning
masvs_v1_levels:
- L2
status: deprecated
covered_by: [MASTG-TEST-0x22-1,MASTG-TEST-0x22-2,MASTG-TEST-0x22-3]
deprecation_note: New version available in MASTG V2
---

## Overview
Expand Down