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_SMSpermission -
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
Use SMS Retriever API (Recommended)
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
Use SMS User Consent API
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:
-
Justify in app description and privacy policy
-
Request at runtime with clear explanation
-
Provide user control to revoke
-
Minimize data collection and retention
-
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
-
OWASP MASTG-TEST-0024: Testing for App Permissions
-
Android Developer: SMS Retriever API
-
Android Developer: SMS User Consent API
-
Google Play: SMS and Call Log Permissions
-
CWE-359: Exposure of Private Personal Information
-
CWE-250: Execution with Unnecessary Privileges