Android SSL pinning is a security feature where an application hardcodes specific SSL certificates or public keys. When the app tries to connect to its backend server, it checks if the server’s certificate matches the one it expects. If there’s a mismatch, the connection fails, preventing man-in-the-middle attacks.

Here’s how an Android app, specifically one that uses SSL pinning, behaves when you try to intercept its traffic with Burp Suite.

First, let’s set up Burp Suite. We’ll use the default configuration, which listens on 127.0.0.1:8080.

# Burp Suite running, default config
java -jar burpsuite_community.jar

Next, we need to configure the Android device or emulator to use Burp Suite as its proxy. This is usually done in the Wi-Fi settings for the network the device is connected to.

Proxy Settings:

  • IP Address: 192.168.1.100 (Replace with your machine’s IP address)
  • Port: 8080

Now, if you try to access an HTTPS URL in the app, you’ll see a connection error. The app will likely display a message like "Connection failed" or "Unable to connect." This is because the app’s pinned certificate doesn’t match the certificate Burp Suite is presenting.

To bypass SSL pinning, we need to install Burp Suite’s CA certificate on the Android device and tell the app to trust user-installed certificates.

Step 1: Install Burp Suite’s CA Certificate on Android

  1. On your Android device, open a browser and navigate to http://burpsuite (or http://<your-machine-ip>:8080).
  2. Click on the "CA Certificate" link to download the certificate.
  3. The device will prompt you to name the certificate. A common name is "Burp".
  4. The certificate will be installed in the Credentials storage under VPN and app user.

Step 2: Configure Android to Trust User-Installed Certificates

This step is crucial and varies significantly depending on the Android version.

  • Android 7.0 (Nougat) and newer: Apps are configured by default not to trust user-installed CA certificates for network traffic. You need to create a network security configuration file within the app’s res/xml directory. This is typically done by the app developer. If you are testing an app you don’t control, this method won’t work directly unless the app is designed for debugging. However, for rooted devices or emulators, you can often achieve system-wide trust.

    If you have root access or are on an emulator, you can manually place the Burp CA certificate into the system’s trust store. This usually involves:

    1. Copying the downloaded cacert.cer file to /sdcard/Download/.
    2. Using adb shell and su to get root privileges.
    3. Copying the certificate to /system/etc/security/cacerts/ (or a similar path depending on the Android version and ROM).
    4. Setting the correct permissions: chmod 644 /system/etc/security/cacerts/<hash_of_cert_name>.0
    5. Rebooting the device.
  • Android 6.0 (Marshmallow) and older: User-installed certificates were trusted by default. Simply installing the Burp CA certificate as described in Step 1 was often sufficient.

Step 3: Configure the App to Trust User-Installed Certificates (If Applicable)

For apps targeting Android 7.0+ that don’t have a custom network security configuration, you might be able to use a tool like Frida to dynamically patch the app’s behavior at runtime.

Here’s a sample Frida script to bypass SSL pinning:

Java.perform(function() {
    var trustManager = Java.use('javax.net.ssl.X509TrustManager');
    var trustManagerImpl = Java.registerClass({
        // Implement the X509TrustManager interface
        implements: [trustManager],
        methods: {
            checkClientTrusted: function(chain, authType) {
                console.log("Client trusted");
            },
            checkServerTrusted: function(chain, authType) {
                console.log("Server trusted");
            },
            getAcceptedIssuers: function() {
                return [];
            }
        }
    });

    var nullTrustManager = Java.use('android.app.trust.TrustManager');
    nullTrustManager.$init.overload('android.content.Context', 'android.app.trust.TrustManager$Callback');

    var originalTrustManager = null;
    var trustManagerInstance = null;

    Java.enumerateLoadedClasses({
        onMatch: function(className) {
            if (className.includes('OkHttpClient') || className.includes('SSLSocketFactory')) {
                console.log("Found potential SSL client: " + className);
                try {
                    var SSLSocketFactory = Java.use(className);
                    // Attempt to bypass OkHttp's default trust manager
                    if (SSLSocketFactory.trustManager() && SSLSocketFactory.trustManager().implementation) {
                        console.log("Found OkHttp TrustManager, bypassing...");
                        SSLSocketFactory.trustManager.implementation = function() {
                            return trustManagerImpl.$new();
                        };
                    }
                    // Attempt to bypass default SSLSocketFactory
                    if (SSLSocketFactory.sslSocketFactory() && SSLSocketFactory.sslSocketFactory().implementation) {
                        console.log("Found SSLSocketFactory, bypassing...");
                        SSLSocketFactory.sslSocketFactory.implementation = function() {
                            return trustManagerImpl.$new();
                        };
                    }
                } catch (e) {
                    // console.error("Error bypassing " + className + ": " + e.message);
                }
            }
        },
        onComplete: function() {}
    });

    // For apps that use a custom TrustManager directly
    try {
        var SSLContext = Java.use('javax.net.ssl.SSLContext');
        var sslContextInstance = SSLContext.getInstance('TLS');
        sslContextInstance.init(null, [trustManagerImpl.$new()], null);
        var sslSocketFactory = sslContextInstance.getSocketFactory();

        var defaultSSLSocketFactory = Java.use('javax.net.ssl.SSLSocketFactory');
        defaultSSLSocketFactory.getDefault.implementation = function() {
            console.log("Bypassing default SSLSocketFactory");
            return sslSocketFactory;
        };
        defaultSSLSocketFactory.getSocketFactory.implementation = function() {
            console.log("Bypassing default getSocketFactory");
            return sslSocketFactory;
        };
    } catch (e) {
        // console.error("Error bypassing SSLContext: " + e.message);
    }
});

To run this Frida script:

  1. Install Frida on your machine and the Frida server on your Android device.

  2. Start Burp Suite and configure your device’s proxy.

  3. Install Burp’s CA certificate on the device.

  4. Run the Frida script against the target app:

    frida -U -f com.example.targetapp -l bypass.js --no-pause
    

    (Replace com.example.targetapp with the actual package name and bypass.js with the script filename.)

Once these steps are completed and the app successfully trusts the Burp CA certificate, you should see traffic in Burp Suite’s HTTP history. The app will now establish a connection with Burp Suite, allowing you to inspect and manipulate its requests and responses.

The next error you’ll hit is likely related to application-specific logic or authorization checks that are not dependent on SSL certificate validation.

Want structured learning?

Take the full Burpsuite course →