Welcome to the fifth article in our OWASP Mobile Top 10 2024 series! In previous articles we covered M1: Improper Credential Usage, M2: Inadequate Supply Chain Security, M3: Insecure Authentication/Authorization, and M4: Insufficient Input/Output Validation. Today we discuss why "we already use HTTPS" isn't a sufficient answer.
Introduction
M5 is the most misleading item on the list, because most teams read it and move on: "We use HTTPS, this doesn't apply to us."
OWASP's definition is far broader. This risk covers all aspects of getting data from point A to point B, but doing it insecurely. It encompasses mobile-to-mobile communications, app-to-server communications, or mobile-to-something-else communications. It includes all communications technologies that a mobile device might use: TCP/IP, WiFi, Bluetooth/Bluetooth-LE, NFC, audio, infrared, GSM, 3G, SMS, etc.
So M5 isn't just "do you use HTTPS." It's all of this:
- Whether you set up TLS correctly (certificate checking, cipher selection)
- Whether your traffic is consistent (some endpoints HTTPS, others not)
- What your third-party SDKs are doing
- What your WebView is loading
- What you send over alternate channels like push notifications and SMS
💡 Key point: Just because an app uses transport security protocols doesn't mean it's implemented correctly. HTTPS is not a checkbox; it's a system that must be configured properly.
A specific situation for React Native developers
In React Native the network layer lives in three separate places, and most developers only think about the first:
-
The JavaScript side —
fetch,axios,XMLHttpRequest - Platform configuration — ATS on iOS, Network Security Config on Android
- Native modules and SDKs — analytics, ads, crash reporting, payment SDKs
Whatever you do on the JavaScript side, if platform configuration is loose or a third-party SDK uses plaintext HTTP, your app is exposed.
OWASP Assessment
| Metric | Value | Meaning |
|---|---|---|
| Exploitability | EASY | A proxy and the same network is enough |
| Prevalence | COMMON | Very frequently found in applications |
| Detectability | AVERAGE | Basic flaws are easy, subtle ones are not |
| Technical Impact | SEVERE | Account takeover, impersonation |
| Business Impact | MODERATE | Privacy violation, reputational damage |
⚠️ Compare with M4: M4's exploitability was DIFFICULT; M5's is EASY. Exploiting this doesn't require Frida, reverse engineering, or a custom exploit. Being on the same café Wi-Fi as the victim and setting up a proxy is enough.
To identify basic flaws, you can observe the network traffic on the phone. However, detecting more subtle flaws requires a closer look at the application's design and configuration.
The Scope of M5: What's In, What's Out
This distinction matters because teams often look at the wrong item.
✅ In scope
- TLS setup and validation failures
- Weak cipher suites
- Accepting invalid certificates
- Plaintext (HTTP) traffic
- Bluetooth, NFC, WiFi Direct security
- Sensitive data over SMS/MMS
- Sensitive data in push notification payloads
- Mixed SSL sessions
❌ Out of scope
- Data stored on the device → M9: Insecure Data Storage
- Weak/predictable session ID, when the channel is secure → M3: Insecure Authentication
- Weak encryption algorithm outside of transport → M10: Insufficient Cryptography
If the data is being stored locally in the device itself, that's Insecure Data. If the session details are communicated securely (e.g., via a strong TLS connection) but the session identifier itself is bad (perhaps it is predictable, low entropy, etc.), then that's an Insecure Authentication problem, not a communication problem.
Three core risks
The usual risks of insecure communication are around data integrity, data confidentiality, and origin integrity.
| Risk | Question | Example violation |
|---|---|---|
| Confidentiality | Can someone else read it? | Plaintext HTTP, weak cipher |
| Integrity | Can it be changed in transit? | Response manipulation via MITM |
| Origin integrity | Is the other side really who it claims? | Certificate not validated |
If the data can be changed while in transit, without the change being detectable (e.g., via a man-in-the-middle attack) then that is a good example of this risk. If confidential data can be exposed, learned, or derived by observing the communications as it happens (i.e., eavesdropping) or by recording the conversation as it happens and attacking it later (offline attack), that's also an insecure communication problem.
Threat Agents
Most modern mobile applications exchange data with one or more remote servers. When the data transmission takes place, it typically goes through the mobile device's carrier network and the internet; a threat agent listening on the wire can intercept and modify the data if it is transmitted in plaintext or using a deprecated encryption protocol.
📡 An adversary sharing your local network (compromised or monitored Wi-Fi)
- Café, hotel, airport Wi-Fi
- Rogue access point ("Free_Airport_WiFi")
- MITM on the same network via ARP spoofing
📶 Rogue carrier or network devices (routers, cell towers, proxies, etc.)
- IMSI catcher / fake base station
- Compromised corporate proxy
- Router performing DNS hijacking
🦠 Malware on the mobile device
- An app with VPN permission logging all traffic
- Social engineering a user into installing a CA cert
Motives vary: stealing sensitive information, conducting espionage, identity theft, and more.
Attack Vectors
While modern applications do rely on cryptographic protocols such as SSL/TLS, they can sometimes have flaws in their implementations.
1. Deprecated protocols and bad configuration
❌ SSLv2, SSLv3, TLS 1.0, TLS 1.1 → Deprecated
⚠️ TLS 1.2 → Acceptable (with correct ciphers)
✅ TLS 1.3 → Preferred
Weak cipher suites (RC4, 3DES, NULL cipher, EXPORT grade) are still enabled on many servers.
2. Accepting bad certificates
Accepting self-signed, revoked, expired, or wrong-host certificates. This usually happens when a "temporary workaround" added during development leaks into production.
3. Inconsistency
This is the sneakiest one. The app uses TLS only on select workflows — login over HTTPS but profile images over HTTP, or the main API over HTTPS but the analytics SDK over HTTP.
// This pattern is surprisingly common in real projects
const API_BASE = 'https://api.example.com'; // ✅
const CDN_BASE = 'http://cdn.example.com'; // ❌ Why?
const ANALYTICS = 'http://analytics.vendor.com'; // ❌ Third party
Mixed SSL sessions may expose the user's session ID.
The Communication Surface in React Native
In M4 we listed "trust boundaries." The M5 equivalent is every channel your app talks out on:
- 🌐 fetch / axios / XMLHttpRequest — the most visible, and usually the only one considered
-
🖼️ WebView traffic — a separate network stack;
mixedContentModeis critical -
📦
<Image source={{uri}} />and video players — loaded natively, subject to ATS/NSC rules -
🔌 WebSockets (
ws://vswss://) — chat, live data; frequently overlooked - 📊 Third-party SDKs — analytics, ads, crash reporting, payments. Not your code but your traffic (overlaps with M2)
- 🔄 OTA updates (CodePush, expo-updates) — a JS bundle is downloaded; is it signature-verified?
- 🔔 Push notification payloads — passes through third-party servers
- 📱 SMS / MMS — unencrypted, exposed on the carrier network
- 📡 Bluetooth / BLE / NFC — IoT integrations, payment terminals
- 🛠️ Metro dev server (development) — plaintext HTTP; must not leak into production
The rule: Each of these channels must be audited separately. Moving fetch to HTTPS closes exactly one item on the list.
OWASP Example Attack Scenarios
There are a few common scenarios that penetration testers frequently discover when inspecting a mobile app's communication security.
Scenario #1 — Lack of certificate inspection
The mobile app and an endpoint successfully connect and perform a TLS handshake to establish a secure channel. However, the mobile app fails to inspect the certificate offered by the server and the mobile app unconditionally accepts any certificate offered to it by the server. This destroys any mutual authentication capability between the mobile app and the endpoint. The mobile app is susceptible to man-in-the-middle attacks through a TLS proxy.
Scenario #2 — Weak handshake negotiation
The mobile app and an endpoint successfully connect and negotiate a cipher suite as part of the connection handshake. The client successfully negotiates with the server to use a weak cipher suite that results in weak encryption that can be easily decrypted by the adversary. This jeopardizes the confidentiality of the channel between the mobile app and the endpoint.
Scenario #3 — Privacy information leakage
The mobile app transmits personally identifiable information to an endpoint via non-secure channels instead of over SSL/TLS. This jeopardizes the confidentiality of any privacy-related data between the mobile app and the endpoint.
Scenario #4 — Credential information leakage
The mobile app transmits user credentials to an endpoint via non-secure channels instead of over SSL/TLS. This allows an adversary to intercept those credentials in cleartext.
Scenario #5 — Two-factor authentication bypass
The mobile app receives a session identifier from an endpoint via non-secure channels instead of over SSL/TLS. This allows an adversary to bypass two-factor authentication by using the intercepted session identifier.
💡 Note scenario #5: The entire authentication architecture you built in M3 collapses the moment a session ID travels over a plaintext channel. The layers are connected.
Technical and Business Impacts
Technical Impacts — SEVERE
This flaw can expose user data which might lead to account takeover, user impersonation, PII data leaks and more; for instance an attacker might intercept user credentials, session, 2FA tokens which can open the door for more elaborate attacks.
- 🔴 Account takeover
- 🔴 User impersonation
- 🔴 PII data leakage
- 🔴 Credential interception
- 🔴 Session token interception
- 🔴 2FA token interception → door to more elaborate attacks
Business Impacts — MODERATE
At a minimum, interception of sensitive data through a communication channel will result in a privacy violation.
| Category | Description |
|---|---|
| Identity theft | Acting as the user with intercepted PII |
| Fraud | Financial fraud with intercepted details |
| Reputational damage | Public fallout from a privacy violation |
| Legal consequences | GDPR/CCPA: failing to protect data in transit is a clear violation |
ℹ️ Note: OWASP rates the business impact here as MODERATE — one notch below the SEVERE of M3 and M4. The reasoning is that a single MITM attack usually affects a single user: targeted leakage rather than a bulk breach. But at scale (hundreds of users on public Wi-Fi) that distinction disappears.
Prevention Strategies
OWASP's general best practices:
- Assume the network layer is not secure and is susceptible to eavesdropping
- Apply SSL/TLS to every transport channel the app uses to transmit data to a backend API or web service
- Account for outside entities like third-party analytics companies and social networks; avoid mixed SSL sessions as they may expose the user's session ID
- Use strong, industry standard cipher suites with appropriate key lengths
- Use certificates signed by a trusted CA provider
- NEVER allow bad certificates (self-signed, expired, untrusted root, revoked, wrong host...)
- Consider certificate pinning
- Always require SSL chain verification
- Only establish a secure connection after verifying the identity of the endpoint server using trusted certificates in the key chain
- Alert users through the UI if the app detects an invalid certificate
- Do not send sensitive data over alternate channels (SMS, MMS, or notifications)
- If possible, apply a separate layer of encryption to any sensitive data before it is given to the SSL channel — a secondary defense if SSL vulnerabilities are discovered
- During development, avoid overriding SSL verification methods to allow untrusted certificates; use self-signed certificates or a local development CA instead
- During security assessments, analyze application traffic to see if any goes through plaintext channels
Platform-specific notes
iOS: Default classes in the latest version of iOS handle SSL cipher strength negotiation very well. Trouble comes when developers temporarily add code to bypass these defaults to accommodate development hurdles.
Android: Remove all code after the development cycle that may allow the application to accept all certificates such as AllowAllHostnameVerifier or SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER. These are equivalent to trusting all certificates. Avoid overriding onReceivedSslError to allow invalid SSL certificates.
In React Native, these two warnings translate to the ATS and Network Security Config settings we'll cover next.
React Native Specific Security
1. Disable plaintext traffic at the platform level
This is the first and most important step against M5. It protects at the OS level regardless of what you write in JavaScript.
iOS — App Transport Security (ATS)
When you create a React Native project, Info.plist usually ships with a localhost exception. Check what state it's in for your production build.
<!-- ios/YourApp/Info.plist -->
<!-- ❌ DANGEROUS — allows all HTTP traffic -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<!-- ✅ SECURE — exception only for the Metro dev server -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
⚠️ A release build shouldn't even contain the localhost exception. It's only needed during development to reach the Metro bundler. Use separate
Info.plistfiles for Debug and Release in Xcode, or strip it with a build script.
If NSAllowsArbitraryLoads is enabled, App Store review will ask you to justify it — which is already a warning sign.
Android — Network Security Config
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Default: no plaintext anywhere -->
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<!-- System CAs only — don't trust user-installed CAs -->
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- Exception for Metro, debug builds only -->
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
<!-- android/app/src/main/AndroidManifest.xml -->
<application
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false"
...>
It's critical that <certificates src="user" /> appears only inside <debug-overrides>. If that line sits in base-config, the app trusts any CA the user installed on their device — that is, any proxy — which opens the door to MITM.
Expo projects
In Expo these settings are managed through app.json / app.config.js:
// app.config.js
export default {
expo: {
ios: {
infoPlist: {
NSAppTransportSecurity: {
NSAllowsArbitraryLoads: false,
},
},
},
android: {
usesCleartextTraffic: false,
},
},
};
2. Certificate / Public Key Pinning
Platform configuration says "there must be a valid certificate." Pinning says "it must be this certificate." The difference is whether a proxy CA installed on the user's device (Burp, Charles, a corporate MITM box) can read your traffic.
Which library?
| Library | Approach | Note |
|---|---|---|
react-native-ssl-public-key-pinning |
Public key hash pinning | Covers standard networking APIs automatically |
@bam.tech/react-native-ssl-pinning |
Expo module + plugin | Installed via Expo config plugin |
react-native-ssl-pinning |
Provides its own fetch
|
Only that fetch is protected |
The most important difference: some libraries give you their own fetch, others cover the whole network layer. With the former, code using axios or <Image> is not protected.
All network requests done through the standard Networking APIs will have the certificate pinning configuration automatically enabled after initialization.
Public key pinning implementation
// services/sslPinning.js
import { initializeSslPinning } from 'react-native-ssl-public-key-pinning';
export async function setupSslPinning() {
// You may want pinning off in development —
// but gate it ONLY behind __DEV__
if (__DEV__) {
console.log('SSL pinning disabled (dev build)');
return;
}
await initializeSslPinning({
'api.mycompany.com': {
includeSubdomains: true,
publicKeyHashes: [
// Public key hash of the current certificate
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
// ✅ BACKUP PIN — mandatory for rotation
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=',
],
},
});
}
// Call in App.tsx BEFORE any network request happens
useEffect(() => {
setupSslPinning();
}, []);
The most common pinning mistakes
Mistake 1: A single pin. When your certificate is renewed, the app breaks completely and users get no service until they update. At least one backup pin is mandatory.
Mistake 2: Pinning the leaf certificate. The leaf changes most often (every 90 days with Let's Encrypt). Pinning the intermediate CA's public key is far more stable.
Mistake 3: No rotation plan. Add the new pin to the app and ship it before renewing the certificate; change the server certificate only after users have updated. Reverse that order and the app breaks.
Mistake 4: Embedding certificates in JS. The hashes aren't secret anyway (they're derived from public keys), but having the pinning logic on the JS side makes it easier to bypass with Frida. Prefer libraries that operate at the native layer.
Mistake 5: Disabling in debug and never testing production. If you wrote if (__DEV__) return;, you have not tested pinning in a release build. Always produce a release build and try it against a proxy.
The limits of pinning
Pinning makes MITM harder but doesn't solve it. A determined attacker on a rooted/jailbroken device can bypass the pinning check with Frida. Position pinning as "a layer against casual MITM and corporate proxies," not as an unbreakable shield.
3. Consistency: audit every channel
This is the most commonly missed area. You moved the main API to HTTPS — what about the rest?
// utils/urlAudit.js
// A simple guard that catches plaintext URLs during development
const isHttp = (url) => typeof url === 'string' && url.startsWith('http://');
export function assertSecureUrl(url, context = 'request') {
if (isHttp(url)) {
const message = `Insecure URL (${context}): ${url}`;
if (__DEV__) {
throw new Error(message); // Fail loudly in dev
}
console.error(message); // Log in prod
return false;
}
return true;
}
// Central check via an Axios interceptor
apiClient.interceptors.request.use((config) => {
const fullUrl = `${config.baseURL ?? ''}${config.url ?? ''}`;
assertSecureUrl(fullUrl, 'axios');
return config;
});
WebView traffic
<WebView
source={{ uri: url }}
// ✅ No mixed content — an HTTPS page can't load HTTP resources
mixedContentMode="never"
// ✅ Remember from M4: origin allowlist
originWhitelist={['https://app.mycompany.com']}
onShouldStartLoadWithRequest={handleShouldStartLoad}
/>
mixedContentMode defaults to never on Android, but writing it explicitly documents the intent against someone flipping it to always later.
Images and media
// ❌ HTTP image — passes if ATS/NSC is off, leaks the user's profile photo
<Image source={{ uri: 'http://cdn.example.com/avatar.png' }} />
// ✅ The schema validation from M4 helps here too
const AvatarSchema = z.string().url().startsWith('https://');
In M4 we wrote avatarUrl: z.string().url().startsWith('https://'). That validation was actually an M5 control — the layers reinforce each other.
WebSockets
// ❌ Unencrypted WebSocket
const ws = new WebSocket('ws://chat.example.com');
// ✅ Over TLS
const ws = new WebSocket('wss://chat.example.com');
ws:// is easy to miss because neither ATS nor Network Security Config always blocks it — and the data carried in chat apps is highly sensitive.
4. Keep sensitive data off alternate channels
OWASP is explicit: do not send sensitive data over SMS, MMS, or notifications.
// ❌ Sensitive data in a push notification payload
{
title: 'Payment Confirmation',
body: 'Transfer of $5,000 to account ending 8413',
data: {
accountNumber: '1234567890',
otpCode: '482915', // ❌ NEVER
}
}
// ✅ Send a reference only; fetch the data in-app over a secure channel
{
title: 'Payment Confirmation',
body: 'You have a new transaction',
data: {
type: 'PAYMENT',
transactionId: 'TXN-8829301', // Just an ID
}
}
Push notification payloads pass through Apple's (APNs) and Google's (FCM) servers and appear on the device lock screen. Those two facts are reason enough to keep sensitive data out of them.
5. An extra encryption layer for sensitive data
OWASP's 12th recommendation: where possible, apply a separate layer of encryption to sensitive data before handing it to the SSL channel. That way, if a vulnerability is discovered in SSL later, you have a secondary defense.
This isn't necessary for every app — but it's worth considering in flows carrying payments, health data, or identity documents.
// Example: encrypt a sensitive payload before sending
import { encryptPayload } from './crypto';
async function submitSensitiveData(data) {
// TLS is already there; this is a second layer
const encrypted = await encryptPayload(data, serverPublicKey);
return apiClient.post('/sensitive-endpoint', {
payload: encrypted,
// Metadata can stay plain
version: 2,
});
}
A caution: don't invent this layer yourself. Use a library, think through key management, or you'll land in M10 (Insufficient Cryptography).
6. Don't let development conveniences reach production
Most M5 violations come not from malice but from a convenience added during development and then forgotten.
// ❌ None of these lines should ship to production
// 1. Globally disabling certificate validation
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// 2. Accepting self-signed certs for the QA server
const agent = new https.Agent({ rejectUnauthorized: false });
// 3. Logging sensitive data
console.log('Token:', accessToken);
console.log('API response:', JSON.stringify(response.data));
The third is especially insidious: console.log output is readable via adb logcat on Android and Console.app on iOS. Anyone with physical access to the device — and in some cases another app — can read those logs.
// babel.config.js — strip console calls in production builds
module.exports = {
presets: ['module:@react-native/babel-preset'],
env: {
production: {
plugins: [
['transform-remove-console', { exclude: ['error', 'warn'] }],
],
},
},
};
Network inspectors in debug tools like Flipper and Reactotron should also be off in production builds. If you are building an iOS Expo development build and want to test out your pinning configuration, you will need to disable expo-dev-client's network inspector as it interferes with the pinning setup. The network inspector is automatically disabled on production builds.
7. Third-party SDK traffic
In M2 we discussed supply chain security. From an M5 perspective the question is: where and how do these SDKs talk?
# Audit the network behaviour of your dependencies
# 1. Run the app through a proxy, list every host it contacts
# 2. Investigate every host you didn't expect
Checklist:
- Does the SDK use HTTPS, or does it fall back to HTTP "for performance"?
- Does the SDK do its own certificate validation, or use the system stack?
- Does the telemetry it sends contain sensitive data?
- Do the SDK's hosts require an ATS/NSC exception? (If so, that's a warning sign.)
Testing Strategies
The good news about M5: it's easier to test than the other items. Set up a proxy and look at the traffic.
Traffic inspection with a proxy
# 1. Install mitmproxy
pip install mitmproxy
mitmproxy --listen-port 8080
# 2. Point the device at the proxy (Wi-Fi settings)
# 3. Install the mitmproxy CA certificate on the device
# 4. Run the app and watch the traffic
Expected results:
| Scenario | Without pinning | With pinning |
|---|---|---|
| Proxy CA not installed | Connection fails | Connection fails |
| Proxy CA installed | ⚠️ Traffic visible | ✅ Connection refused |
The second row is the critical one: if you can read traffic while the proxy CA is installed, pinning isn't working.
Scanning for plaintext traffic
# Any HTTP in the app's traffic?
mitmdump -s flow_filter.py
# flow_filter.py — report plaintext requests
def request(flow):
if flow.request.scheme == "http":
print(f"⚠️ PLAINTEXT: {flow.request.pretty_url}")
Server-side TLS configuration
# Comprehensive TLS audit with testssl.sh
docker run --rm -ti drwetter/testssl.sh https://api.mycompany.com
# What to check:
# - Are TLS 1.0/1.1 disabled?
# - Are weak ciphers (RC4, 3DES, NULL) disabled?
# - Is the certificate chain complete?
# - Is the HSTS header present?
For a publicly reachable endpoint, SSL Labs is an easier alternative — and it's also where you can grab the public key hashes needed for pinning.
Automated tests
// __tests__/network.security.test.js
describe('URL security audit', () => {
it('should have no HTTP URLs in configuration', () => {
const config = require('../src/config/endpoints');
const urls = Object.values(config).filter(v => typeof v === 'string');
urls.forEach(url => {
expect(url).not.toMatch(/^http:\/\//);
});
});
it('WebSocket URL should be wss://', () => {
const { WS_URL } = require('../src/config/endpoints');
expect(WS_URL).toMatch(/^wss:\/\//);
});
});
# Search the source for plaintext URLs in CI
grep -rn "http://" src/ --include="*.ts" --include="*.tsx" \
| grep -v "localhost" \
| grep -v "127.0.0.1" \
&& echo "❌ Plaintext URL found" && exit 1
Manual test scenarios
| Test | Method | Expected |
|---|---|---|
| Certificate validation | Connect to a server with an invalid cert | Connection refused |
| Pinning | Run the app on a device with the proxy CA installed | Connection refused |
| Plaintext | Scan all traffic in the proxy | No HTTP requests |
| Mixed content | Open a page with HTTP resources in the WebView | Resource not loaded |
| Release build | Check ATS/NSC settings in the release APK/IPA | NSAllowsArbitraryLoads=false |
| Log leakage | Search for tokens with adb logcat
|
No tokens visible |
Tools and Resources
Traffic analysis
| Tool | Use Case | Note |
|---|---|---|
| mitmproxy | CLI proxy, scriptable | Ideal for automated testing |
| Burp Suite | Comprehensive security testing | The standard for mobile testing |
| Charles Proxy | GUI proxy | Easy to use |
| Wireshark | Packet-level analysis | For layers below TLS |
TLS configuration auditing
| Tool | Use Case |
|---|---|
| testssl.sh | Server TLS configuration scanning |
| SSL Labs | Web-based, for public endpoints |
| Mozilla SSL Config Generator | Generating server configuration |
React Native libraries
| Library | Purpose |
|---|---|
react-native-ssl-public-key-pinning |
Public key pinning, covers the whole network layer |
@bam.tech/react-native-ssl-pinning |
Expo module + config plugin |
babel-plugin-transform-remove-console |
Strips logs in production |
Useful resources
| Resource | URL |
|---|---|
| OWASP M5 | owasp.org/www-project-mobile-top-10 |
| OWASP MASVS-NETWORK | mas.owasp.org |
| Transport Layer Security Cheat Sheet | cheatsheetseries.owasp.org |
| Android Network Security Config | developer.android.com/training/articles/security-config |
| Apple ATS documentation | developer.apple.com |
Conclusion
M5 is too broad an item to close with the sentence "we use HTTPS." The real question isn't whether you use HTTPS, but whether you use it consistently across every channel and with the right configuration.
🎯 Security Roadmap
1. Beginner level
- ATS:
NSAllowsArbitraryLoads = false(no exceptions in release) - Android:
usesCleartextTraffic = false+ network_security_config - All endpoints HTTPS, WebSockets
wss:// - Strip
console.login production - No sensitive data in push/SMS payloads
2. Intermediate level
- Certificate/public key pinning (with a backup pin)
- WebView
mixedContentMode="never" - Audit third-party SDK traffic
- Scan for plaintext URLs in CI
- Proxy-test the release build
3. Enterprise level
- Pin rotation procedure and automation
- Extra encryption layer for sensitive payloads
- Server-side TLS hardening (TLS 1.3, HSTS)
- Continuous traffic monitoring and anomaly detection
- Penetration testing and bug bounty
Key Takeaways
Exploitability is EASY. Exploiting this needs no special skill — the same Wi-Fi and a proxy is enough. That's why it belongs high in your priority list.
Consistency is everything. One HTTP endpoint can devalue the ninety-nine you moved to HTTPS.
Platform configuration comes before JavaScript. ATS and Network Security Config are the last line of defense against mistakes in your code.
Pinning is a layer, not a solution. Effective against corporate proxies and casual MITM; a delay against a determined attacker.
Don't pin without a backup pin. Pinning without thinking through certificate rotation is the fastest way to lock yourself out.
Development conveniences are the biggest source of risk.
rejectUnauthorized: false,NSAllowsArbitraryLoads, debug logs — all added in good faith and forgotten.M5 is intertwined with the other items. An intercepted session token breaks M3; a WebView loading HTTP content overlaps with M4; SDK traffic connects to M2.
Next Steps
In our next article, we'll examine M6: Inadequate Privacy Controls. We'll cover the responsibilities of mobile apps in collecting, processing, storing, and sharing personal data, along with permission management, data minimization, and privacy-by-design principles.
References
- OWASP Mobile Top 10 2024 — M5
- OWASP MASVS
- OWASP Transport Layer Security Cheat Sheet
- Android Network Security Configuration
- react-native-ssl-public-key-pinning
- @bam.tech/react-native-ssl-pinning
- SSL Labs Server Test
- React Native Security
This article is the fifth in the OWASP Mobile Top 10 2024 series. Previous articles covered M1: Improper Credential Usage, M2: Inadequate Supply Chain Security, M3: Insecure Authentication/Authorization, and M4: Insufficient Input/Output Validation. Follow along for the rest of the series!
Top comments (0)