Skip to content
advanced Phase 12 · Security

Secure Coding Practices

Apply OWASP Mobile Top 10: input validation, secure storage, certificate pinning, and code signing.

45m
0 problems
Topic Progress 0%

OWASP Mobile Top 10

The OWASP Mobile Top 10

The Open Web Application Security Project (OWASP) publishes a list of the most critical mobile security risks. Understanding these vulnerabilities is essential for building secure Android applications.

Key Vulnerabilities

# Vulnerability Description
M1 Improper Credential Usage Hardcoded API keys, weak token storage
M2 Inadequate Supply Chain Security Third-party libraries with known vulnerabilities
M3 Insecure Authentication/Authorization Missing auth checks, weak session management
M4 Insufficient Input/Output Validation SQL injection, XSS, intent injection
M5 Insecure Communication Missing TLS, no certificate pinning
M6 Inadequate Privacy Controls Collecting unnecessary PII, leaking data via logs
M7 Insufficient Binary Protections No obfuscation, no anti-tampering
M8 Security Misconfiguration Debuggable release builds, overly permissive exports
M9 Insecure Data Storage Plaintext passwords, unencrypted databases
M10 Insufficient Cryptography Weak algorithms, hardcoded keys

Practical Prevention

M1 — Credential Usage:

// WRONG: Hardcoded key
val apiKey = "sk_live_abc123def456"

// RIGHT: Store in BuildConfig or secure storage
val apiKey = BuildConfig.API_KEY
// Or better: fetch at runtime from a secure backend

M4 — Input Validation:

// WRONG: Direct string concatenation in queries
val query = "SELECT * FROM users WHERE name = '" + input + "'"

// RIGHT: Parameterized queries
val cursor = db.rawQuery(
    "SELECT * FROM users WHERE name = ?",
    arrayOf(input)
)

M8 — Security Configuration:

<!-- WRONG: Debuggable in release -->
<application android:debuggable="true">

<!-- RIGHT: Let Gradle handle it -->
<application android:debuggable="${isDebug}">

M9 — Data Storage:

// WRONG: Storing password in SharedPreferences
sharedPrefs.edit().putString("password", rawPassword).apply()

// RIGHT: Hash with salt using a slow KDF
val salt = ByteArray(16).also { SecureRandom().nextBytes(it) }
val spec = PBEKeySpec(password.toCharArray(), salt, 100000, 256)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val hash = factory.generateSecret(spec).encoded

Key Takeaway

Security is not a feature you add at the end. It must be baked into the architecture from the start.

Input Validation and Certificate Pinning

Input Validation

Every piece of data that enters your app—user input, deep link parameters, intent extras, push notification payloads—must be validated. Unvalidated input is the root cause of injection attacks, data corruption, and privilege escalation.

Validation Checklist

  1. Type checking — Ensure the data is the expected type
  2. Length limits — Reject unexpectedly long inputs
  3. Range validation — Check numeric values are within bounds
  4. Format validation — Use regex for emails, phone numbers, etc.
  5. Sanitization — Strip or escape dangerous characters
fun validateEmail(input: String?): Boolean {
    if (input == null || input.length > 254) return false
    val pattern = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
    return pattern.matcher(input).matches()
}

fun validateAmount(input: String?): Double? {
    val amount = input?.toDoubleOrNull() ?: return null
    if (amount < 0 || amount > 1_000_000) return null
    return amount
}

Intent Validation

Malicious apps can send crafted intents to exported activities and services. Always validate intent extras.

class ReceiveActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val data = intent?.getStringExtra("url") ?: return
        
        // Validate URL scheme and host
        val uri = Uri.parse(data)
        if (uri.scheme != "https" || uri.host != "yourdomain.com") {
            finish()
            return
        }
        
        loadUrl(uri.toString())
    }
}

Certificate Pinning

Certificate pinning ensures your app only communicates with servers presenting a specific certificate or public key. It prevents MITM attacks even when the attacker controls a trusted CA.

// Using OkHttp with certificate pinning
val certificatePinner = CertificatePinner.Builder()
    .add(
        "api.yourdomain.com",
        "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Base64 SPKI hash
    )
    .build()

val client = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

val request = Request.Builder()
    .url("https://api.yourdomain.com/endpoint")
    .build()

client.newCall(request).enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
        // Handle response
    }

    override fun onFailure(call: Call, e: IOException) {
        if (e is SSLPeerUnverifiedException) {
            // Certificate mismatch — possible MITM attack
            Log.e("Security", "Certificate pinning failed: ${e.message}")
        }
    }
})

Extracting the Public Key Hash

# Extract the certificate
openssl s_client -connect api.yourdomain.com:443 < /dev/null 2>/dev/null | \
    openssl x509 -pubkey -noout | \
    openssl pkey -pubin -outform der | \
    openssl dgst -sha256 -binary | base64

Pinning Strategies

  1. Certificate pinning — Pin to the entire X.509 certificate. Simple but requires rotation when the certificate expires.
  2. Public key pinning — Pin to the Subject Public Key Info (SPKI). Survives certificate renewal as long as the key pair stays the same.
  3. Backup pins — Always include at least one backup pin. If your primary certificate is compromised, you can rotate without forcing an app update.

Code Hardening and Runtime Security

Detecting Rooted Devices

Rooted devices bypass Android's security sandbox. While you shouldn't block rooted users entirely, you should increase scrutiny for sensitive operations.

object RootDetector {
    fun isDeviceRooted(): Boolean {
        // Check for common root binaries
        val paths = arrayOf(
            "/system/app/Superuser.apk",
            "/system/bin/su",
            "/system/xbin/su",
            "/data/local/xbin/su",
            "/data/local/bin/su"
        )
        for (path in paths) {
            if (File(path).exists()) return true
        }
        
        // Check for test-keys build
        val buildTags = android.os.Build.TAGS
        if (buildTags?.contains("test-keys") == true) return true
        
        // Check if su command is available
        return try {
            Runtime.getRuntime().exec("su").waitFor() == 0
        } catch (e: Exception) {
            false
        }
    }
}

ProGuard Log Stripping

# Remove all Log calls in release builds
-assumenosideeffects class android.util.Log {
    public static int v(...);
    public static int d(...);
    public static int i(...);
}

Secure Data Deletion

When clearing sensitive data, overwrite memory buffers before releasing them.

fun secureClear(buffer: ByteArray) {
    java.util.Arrays.fill(buffer, 0.toByte())
}

fun clearAuthToken() {
    val token = getToken()
    token?.toByteArray()?.let { secureClear(it) }
    securePrefs.edit().remove("auth_token").apply()
}

Network Security Configuration

Android 7.0+ allows declarative network security rules without code changes.

<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">api.yourdomain.com</domain>
        <pin-set expiration="2026-12-31">
            <pin digest="SHA-256">base64_hash_here=</pin>
            <pin digest="SHA-256">backup_hash_here=</pin>
        </pin-set>
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </domain-config>
    
    <!-- Block cleartext traffic in release -->
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

Reference it in the manifest:

<application
    android:networkSecurityConfig="@xml/network_security_config">
</application>

Security Checklist for Production

  1. isDebuggable = false in release manifest
  2. Cleartext traffic blocked via network security config
  3. Certificate pinning enabled for all API endpoints
  4. No sensitive data in logs — strip with ProGuard
  5. All exports restricted — use android:exported="false" unless necessary
  6. Input validated — all external data sanitized
  7. Encryption at rest — EncryptedSharedPreferences or SQLCipher
  8. Obfuscation enabled — R8 minification for release builds
  9. Dependency audit — no known vulnerabilities in third-party libraries
  10. No hardcoded secrets — API keys, passwords stored securely

Quiz

1. According to OWASP Mobile Top 10, which vulnerability covers hardcoded API keys and weak token storage?

Question 1 options

2. What is the main advantage of public key pinning over certificate pinning?

Question 2 options

3. Why should you include backup pins when implementing certificate pinning?

Question 3 options

4. How do you block cleartext HTTP traffic declaratively in Android 7.0+?

Question 4 options

Flashcards

Question

What does OWASP M4 cover?

Answer

Insufficient Input/Output Validation — includes SQL injection, XSS, intent injection, and other attacks that exploit unvalidated external data.

Question

What is certificate pinning and why is it important?

Answer

Certificate pinning ensures your app only communicates with servers presenting a specific certificate or public key, preventing man-in-the-middle attacks even when an attacker controls a trusted CA.

Question

How do you prevent sensitive data leakage via logs in release builds?

Answer

Use ProGuard/R8 with -assumenosideeffects to strip Log.v(), Log.d(), and Log.i() calls from the release APK at build time.

Question

Why use the network_security_config.xml file instead of code-based configuration?

Answer

Network security config is declarative, version-independent (no code changes for new Android versions), and can be overridden per build type. It supports certificate pinning, cleartext blocking, and custom trust anchors without code.

Revision Notes

Key Takeaways

  • 1. OWASP Mobile Top 10 is the reference for mobile security — know the categories and mitigations
  • 2. Always validate and sanitize external input including intents, deep links, and API responses
  • 3. Certificate pinning with backup pins protects against MITM attacks including rogue CAs
  • 4. Strip logging statements in release builds using ProGuard -assumenosideeffects
  • 5. Use network_security_config.xml for declarative, version-independent network security

Interview Tips

  • Be ready to name at least 5 OWASP Mobile Top 10 vulnerabilities and their fixes
  • Explain the difference between certificate pinning and public key pinning
  • Discuss how to handle certificate rotation without forcing app updates (backup pins)
  • Know how to block cleartext traffic and why it matters
  • Explain why hardcoded credentials are dangerous even in compiled APKs

Cheat Sheet

Secure Coding Practices Cheat Sheet

OWASP Mobile Top 10:

  • M1: Improper Credential Usage — no hardcoded keys
  • M4: Insufficient Input Validation — parameterized queries, sanitize all input
  • M5: Insecure Communication — TLS + certificate pinning
  • M7: Insufficient Binary Protections — enable R8 obfuscation
  • M9: Insecure Data Storage — encrypt at rest

Input Validation:

  • Validate type, length, range, and format
  • Parameterized queries for all database operations
  • Validate intent extras and deep link parameters
  • Validate URL schemes and hosts

Certificate Pinning:

  • OkHttp: CertificatePinner.Builder()
  • Network Security Config: pin-set in XML
  • Always include backup pins
  • Prefer public key pinning over certificate pinning

Code Hardening:

  • Strip logs with -assumenosideeffects
  • Block cleartext via network security config
  • Detect rooted devices for sensitive operations
  • Secure data deletion with buffer overwriting

Production Checklist:

  • isDebuggable=false
  • No cleartext traffic
  • Cert pinning on all endpoints
  • No sensitive data in logs
  • All exports restricted