SMS Monitoring

ID

kotlin.android_sms_monitoring

Severity

high

Resource

Information Leak

Language

Kotlin

Tags

CWE:250, CWE:359, MASVS:MSTG-PRIVACY-1, MASVS:MSTG-PRIVACY-3, android, privacy, stalkerware

Description

SMS monitoring refers to applications that intercept, read, or track SMS messages. This raises significant privacy concerns as SMS messages contain private communications, including:

  • Personal conversations

  • Two-factor authentication codes (OTP)

  • Banking and financial notifications

  • Password reset links

  • Personal identification information

Google Play Store restricts applications that request SMS permissions without legitimate SMS handling as their primary purpose.

Rationale

Detecting unexpected SMS monitoring will be reported by this detector.

Privacy Violation (CWE-359):

SMS monitoring enables unauthorized access to private communications, creating risks for:

  • Stalkerware / Spyware: Domestic abuse scenarios where partners monitor communications

  • Banking Fraud: Interception of SMS OTP codes for two-factor authentication bypass

  • Data Harvesting: Collection of sensitive information from messages

  • Credential Theft: Capturing password reset codes and verification messages

Unnecessary Privileges (CWE-250):

Applications often request SMS permissions without legitimate need:

  • Games requesting READ_SMS permission

  • Utility apps monitoring all SMS messages

  • Apps using SMS permissions when SMS Retriever API is available

Regulatory Compliance:

Google Play Store policies restrict non-SMS apps from requesting SMS permissions


Vulnerable Patterns

This detector identifies the following dangerous patterns:

  • SMS Permissions in Manifest

SMS permissions (READ_SMS, RECEIVE_SMS, SEND_SMS) are declared in AndroidManifest.xml:

<!-- VULNERABLE: Requesting SMS permissions -->
<manifest>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
</manifest>
  • BroadcastReceiver SMS Interception

When BroadcastReceiver listens for SMS_RECEIVED intent:

<!-- VULNERABLE: High priority SMS receiver -->
<receiver android:name=".SMSReceiver" android:exported="true">
    <intent-filter android:priority="999">
        <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>
// VULNERABLE: SMS interception
class SMSReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val bundle = intent.extras
        val pdus = bundle?.get("pdus") as Array<*>
        val messages = pdus.map {
            SmsMessage.createFromPdu(it as ByteArray)
        }

        messages.forEach { message ->
            val sender = message.displayOriginatingAddress
            val body = message.displayMessageBody
            // Send to remote server (spyware behavior)
            sendToServer(sender, body)  // PRIVACY VIOLATION
        }
    }
}
  • ContentObserver SMS Database Monitoring

ContentObserver monitors the SMS database using a query with the content://sms URI:

// VULNERABLE: Monitoring SMS database
class SMSObserver(handler: Handler) : ContentObserver(handler) {
    override fun onChange(selfChange: Boolean) {
        val cursor = context.contentResolver.query(
            Uri.parse("content://sms/inbox"),
            null, null, null, null
        )

        cursor?.use {
            while (it.moveToNext()) {
                val address = it.getString(it.getColumnIndex("address"))
                val body = it.getString(it.getColumnIndex("body"))
                // Exfiltrate all SMS messages
                sendToRemoteServer(address, body)  // PRIVACY VIOLATION
            }
        }
    }
}

// Registration
context.contentResolver.registerContentObserver(
    Uri.parse("content://sms"),
    true,
    SMSObserver(Handler())
)
  • Direct SMS Database Queries

// VULNERABLE: Querying all SMS messages
fun getAllSMS(): List<SMS> {
    val cursor = contentResolver.query(
        Telephony.Sms.CONTENT_URI,
        arrayOf("_id", "address", "body", "date"),
        null, null, "date DESC"
    )

    cursor?.use {
        while (it.moveToNext()) {
            // Access all SMS messages
        }
    }
}

Legitimate vs Malicious Use

Some apps have SMS handling as its primary purpose. Declared in Google Play Store description, they are clearly visible as SMS app to user. They comply with default SMS handler requirements.

In Banking/Finance Apps, SMS is used for OTP Verification. They U¡use SMS Retriever API (no permissions), with temporary, transaction-specific scope. With user-initiated authentication flows, such apps document a clear privacy policy.

Parental Control Apps also perform transparent monitoring with consent. Typically, they have a clear app icon and purpose, and comply with Google Play Family policies.

Malicious/StalkerWare usages often show the following Red Flags:

  • Hidden operation (no visible app icon)

  • Monitors ALL SMS without user awareness

  • Sends SMS data to remote servers

  • No user control or opt-out mechanism

  • Runs in background permanently

  • Installed secretly by third party

Remediation

For OTP verification, use Google’s SMS Retriever API which requires no permissions:

// 1. Start SMS retriever
val client = SmsRetriever.getClient(context)
client.startSmsRetriever()
    .addOnSuccessListener {
        Log.d(TAG, "SMS Retriever started")
    }

// 2. Register BroadcastReceiver
class SMSBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
            val extras = intent.extras
            val status = extras?.get(SmsRetriever.EXTRA_STATUS) as Status

            when (status.statusCode) {
                CommonStatusCodes.SUCCESS -> {
                    val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String
                    val otp = extractOTP(message)
                    // Use OTP for verification
                }
            }
        }
    }
}

// 3. Server sends SMS with app hash
// Format: <#> Your OTP is: 123456 FA+9qCX9VSu

Benefits: * No permissions required * Automatic OTP extraction * User privacy protected * Google Play Store compliant

When you don’t control the SMS format:

// Start SMS consent request
val client = SmsRetriever.getClient(context)
client.startSmsUserConsent(null /* or sender phone number */)
    .addOnSuccessListener {
        Log.d(TAG, "Consent request started")
    }

// When SMS arrives, show one-time consent dialog
// User can approve access to that specific message

Remove Unnecessary SMS Permissions

<!-- DON'T: Unnecessary SMS permissions -->
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>

If SMS permissions are truly necessary:

  1. Justify in app description and privacy policy

  2. Request at runtime with clear explanation

  3. Provide user control to revoke

  4. Minimize data collection and retention

  5. Don’t send SMS data to remote servers

Secure BroadcastReceiver Configuration

If SMS handling is your app’s core functionality:

<receiver
    android:name=".SMSReceiver"
    android:exported="false"
    android:permission="android.permission.BROADCAST_SMS">
    <intent-filter android:priority="0">
        <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>
class SMSReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // Only process if app is default SMS handler
        if (!Telephony.Sms.getDefaultSmsPackage(context).equals(context.packageName)) {
            return
        }

        // Handle SMS
    }
}

References