In android there are several ways to make HTTP requests. For example using HttpURLConnection (low-level API built into Java), OkHttp (A popular third-party library) etc.
Cleartext Traffic
Starting from Android 9 (API level 28), HTTP clients like URLConnection, Cronet, and OkHttp enforce the use of HTTPS, thus disabling cleartext traffic by default. However, it's important to note that other HTTP client libraries, such as Ktor, may not enforce these restrictions [🔗].
However, if developers explicitly set usesCleartextTraffic=true [🔗] in the manifest or network security configuration [🔗], cleartext traffic is permitted.
SSL interception
To intercept TLS/SSL traffic, the proxy certificate must be trusted by the device. Android recognizes two types of certificates: user certificates and system certificates. Applications can explicitly configure which certificate types they trust using network security config.
If the application doesn't accept user certificates you need to install system certificate (or patching network security config).
User Certificate
Install it in the user CA store via Android settings. In general apps trust user certificates if it targets Android 6 (API 23) or lower, or network security config allows it.
Install user certificate guide
Download the certificate from http://<burp_proxy_listener>
Go on setting, search certificate and install by selected it
Install on older Android ≤ 11
Same as above but you need to run this command because it expected another file format.
openssl x509 -inform DER -in cacert.der -out cacert.pem
System Certificate
Requirement: rooted device.
Rooted physical device
Rooted emulator
With Android (AVD) using non-Google emulator image
Install system certificate guide
Install the proxy certificate as a regular user certificate
adb shell
Run this script:
su
# Backup the existing system certificates to the user certs folder
cp /system/etc/security/cacerts/* /data/misc/user/0/cacerts-added/
# Create the in-memory mount on top of the system certs folder
mount -t tmpfs tmpfs /system/etc/security/cacerts
# copy all system certs and our user cert into the tmpfs system certs folder
cp /data/misc/user/0/cacerts-added/* /system/etc/security/cacerts/
# Fix any permissions & selinux context labels
chown root:root /system/etc/security/cacerts/*
chmod 644 /system/etc/security/cacerts/*
chcon u:object_r:system_file:s0 /system/etc/security/cacerts/*
Install system certificate on Android ≥ 14 guide
Install the proxy certificate as a regular user certificate
# Create a separate temp directory, to hold the current certificates
# Otherwise, when we add the mount we can't read the current certs anymore.
mkdir -p -m 700 /data/local/tmp/tmp-ca-copy
# Copy out the existing certificates
cp /apex/com.android.conscrypt/cacerts/* /data/local/tmp/tmp-ca-copy/
# Create the in-memory mount on top of the system certs folder
mount -t tmpfs tmpfs /system/etc/security/cacerts
# Copy the existing certs back into the tmpfs, so we keep trusting them
mv /data/local/tmp/tmp-ca-copy/* /system/etc/security/cacerts/
# Copy our new cert in, so we trust that too
cp /data/misc/user/0/cacerts-added/* /system/etc/security/cacerts/
# Update the perms & selinux context labels
chown root:root /system/etc/security/cacerts/*
chmod 644 /system/etc/security/cacerts/*
chcon u:object_r:system_file:s0 /system/etc/security/cacerts/*
# Deal with the APEX overrides, which need injecting into each namespace:
# First we get the Zygote process(es), which launch each app
ZYGOTE_PID=$(pidof zygote || true)
ZYGOTE64_PID=$(pidof zygote64 || true)
# N.b. some devices appear to have both!
# Apps inherit the Zygote's mounts at startup, so we inject here to ensure
# all newly started apps will see these certs straight away:
for Z_PID in "$ZYGOTE_PID" "$ZYGOTE64_PID"; do
if [ -n "$Z_PID" ]; then
nsenter --mount=/proc/$Z_PID/ns/mnt -- \
/bin/mount --bind /system/etc/security/cacerts /apex/com.android.conscrypt/cacerts
fi
done
# Then we inject the mount into all already running apps, so they
# too see these CA certs immediately:
# Get the PID of every process whose parent is one of the Zygotes:
APP_PIDS=$(
echo "$ZYGOTE_PID $ZYGOTE64_PID" | \
xargs -n1 ps -o 'PID' -P | \
grep -v PID
)
# Inject into the mount namespace of each of those apps:
for PID in $APP_PIDS; do
nsenter --mount=/proc/$PID/ns/mnt -- \
/bin/mount --bind /system/etc/security/cacerts /apex/com.android.conscrypt/cacerts &
done
wait # Launched in parallel - wait for completion here
echo "System certificate injected"
Patching Network Security Config
Unpack the apk
apktool d target.apk
Modify the AndroidManifest.xml to add a networkSecurityConfig (xml/network_security_config.xml). If it's already present edit the file.
Enforce DNS usage using Android's VPN feature with tools like RethinkDNS.
From "configure" -> "DNS" -> Change DNS settings to "Other DNS"
Select "DNS Proxy"
Create a new entry pointing at your local DNS server host
Finally, configure your proxy tool for invisible proxying. Burp will act as an HTTP(S) server, parse the HOST header, and forward requests. Ensure an invisible proxy listener is set on ports 443 and 80.
Invisible proxying
Normal Proxy
In a normal proxy, the client (e.g., a browser or app) is explicitly configured to use the proxy. This means the client intentionally routes traffic through the proxy. Thus:
The client is aware of the existence of the proxy.
HTTPS requires the client to accept the certificate generated by the proxy (MITM).
The request contains both the relative path (/path) and the full address (e.g. GET http://www.example.com/path HTTP/1.1)
Invisible Proxy
An invisible proxy [🔗] operates without the client being explicitly configured to use it. This is useful when the client does not support proxy configurations. Therefore, the client remains unaware of the proxy. However:
With plain HTTP, a proxy-style request looks like this:
GET http://example.org/foo.php HTTP/1.1
Host: example.org
A non-proxy-style request looks like this:
GET /foo.php HTTP/1.1
Host: example.org
Proxies usually use the full URL in the first line to determine the destination, ignoring the Host header. In invisible proxying, Burp parses the Host header from non-proxy-style requests to determine the destination.