Insecure broadcast receivers

OWASP category: MASVS-PLATFORM: Platform Interaction

Overview

Improperly implemented broadcast receivers can allow an attacker to send a malicious intent to make the vulnerable application perform actions which are not intended for external callers.

The vulnerability generally refers to instances where the broadcast receiver is unintentionally exported, either by setting android:exported="true" in the AndroidManifest or by creating a broadcast receiver programmatically which makes the receiver public by default. If the receiver doesn't contain any intent filters the default value is "false" but if the receiver contains at least one intent filter the default value of android:exported is "true".

Intentionally exported broadcast receivers without proper access control can be abused if the developer did not intend for it to be called by all applications.

Impact

Insecurely implemented broadcast receivers can be abused by an attacker to gain unauthorized access to execute behavior in the application that the developer did not mean to expose to third parties.

Mitigations

Avoid the problem entirely

To resolve the dilemma entirely, set exported to false:

<receiver android:name=".MyReceiver" android:exported="false">
    <intent-filter>
        <action android:name="com.example.myapp.MY_ACTION" />
    </intent-filter>
</receiver>

Use calls and callbacks

In the case you used broadcast receivers for internal app purposes (ie. event completion notification), you can restructure your code to pass a callback that would fire after event completion instead.

Event completion listener

Kotlin

interface EventCompletionListener {
    fun onEventComplete(data: String)
}

Java

public interface EventCompletionListener {
    public void onEventComplete(String data);
}
Secure task

Kotlin

class SecureTask(private val listener: EventCompletionListener?) {
    fun executeTask() {
        // Do some work...

        // Notify that the event is complete
        listener?.onEventComplete("Some secure data")
    }
}

Java

public class SecureTask {

    final private EventCompletionListener listener;

    public SecureTask(EventCompletionListener listener) {
        this.listener = listener;
    }

    public void executeTask() {
        // Do some work...

        // Notify that the event is complete
        if (listener != null) {
            listener.onEventComplete("Some secure data");
        }
    }
}
Main activity

Kotlin

class MainActivity : AppCompatActivity(), EventCompletionListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val secureTask = SecureTask(this)
        secureTask.executeTask()
    }

    override fun onEventComplete(data: String) {
        // Handle event completion securely
        // ...
    }
}

Java

public class MainActivity extends AppCompatActivity implements EventCompletionListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SecureTask secureTask = new SecureTask(this);
        secureTask.executeTask();
    }

    @Override
    public void onEventComplete(String data) {
        // Handle event completion securely
        // ...
    }
}

Secure broadcast receivers with permissions

Only register dynamic receivers for protected broadcasts (broadcasts that only system level applications can send) or with self-declared signature level permissions.

Resources