Skip to content

MASTG-DEMO-0103: Missing Overlay Protection on a Sensitive View

Download MASTG-DEMO-0103 APK Open MASTG-DEMO-0103 Folder Build MASTG-DEMO-0103 APK

Sample

The sample demonstrates three buttons performing sensitive actions, each using a different overlay protection approach:

  1. Vulnerable button: A button without any overlay protection, making it susceptible to tapjacking attacks.
  2. Protected button: A button with filterTouchesWhenObscured = true to block touches when the window is obscured.
  3. Custom protected button: A button with a custom onFilterTouchEventForSecurity override that manually checks for the FLAG_WINDOW_IS_OBSCURED flag.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package org.owasp.mastestapp

import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.view.Gravity
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.Toast

// SUMMARY: This sample demonstrates three approaches to overlay attack protection on sensitive UI elements:
// a vulnerable button with no protection, a button using setFilterTouchesWhenObscured, and a button
// with a custom onFilterTouchEventForSecurity override.

class MastgTest(private val context: Context) {

    val shouldRunInMainThread = true

    fun mastgTest(): String {
        val layout = LinearLayout(context).apply {
            orientation = LinearLayout.VERTICAL
            gravity = Gravity.CENTER
            setBackgroundColor(Color.TRANSPARENT)
        }

        val halfWidth = (context.resources.displayMetrics.widthPixels * 0.5).toInt()

        val buttonParams = LinearLayout.LayoutParams(
            halfWidth,
            LinearLayout.LayoutParams.WRAP_CONTENT
        ).apply {
            gravity = Gravity.CENTER_HORIZONTAL
            topMargin = 24
        }

        // FAIL: [MASTG-TEST-0x01] The vulnerable button performs a sensitive action (payment confirmation)
        // without any overlay protection, making it susceptible to tapjacking attacks.
        val vulnerableButton = Button(context).apply {
            text = "Vulnerable: Confirm Payment"
            setOnClickListener {
                Toast.makeText(context, "Vulnerable button clicked", Toast.LENGTH_SHORT).show()
            }
        }
        layout.addView(vulnerableButton, buttonParams)

        // PASS: [MASTG-TEST-0x01] The protected button uses filterTouchesWhenObscured=true to discard
        // touch events when the view is obscured by another window.
        val protectedButton = Button(context).apply {
            text = "Protected: Confirm Payment"
            filterTouchesWhenObscured = true
            setOnClickListener {
                Toast.makeText(context, "Protected button clicked", Toast.LENGTH_SHORT).show()
            }
        }
        layout.addView(protectedButton, buttonParams)

        // PASS: [MASTG-TEST-0x01] The custom protected button overrides onFilterTouchEventForSecurity
        // and checks FLAG_WINDOW_IS_OBSCURED to implement a custom security policy.
        val customProtectedButton = object : Button(context) {
            override fun onFilterTouchEventForSecurity(event: MotionEvent): Boolean {
                if ((event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
                    Toast.makeText(context, "Blocked obscured touch", Toast.LENGTH_SHORT).show()
                    return false
                }
                return super.onFilterTouchEventForSecurity(event)
            }
        }.apply {
            text = "Custom Protection: Grant Permission"
            setOnClickListener {
                Toast.makeText(context, "Custom protected button clicked", Toast.LENGTH_SHORT).show()
            }
        }
        layout.addView(customProtectedButton, buttonParams)

        (context as? Activity)?.let { activity ->
            val root = activity.findViewById<ViewGroup>(android.R.id.content)
            if (layout.parent == null) {
                root.addView(
                    layout,
                    FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT,
                        FrameLayout.LayoutParams.MATCH_PARENT
                    )
                )
            }
        }

        return "Created buttons with various overlay protections"
    }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
package org.owasp.mastestapp;

import android.app.Activity;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;

/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000b\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\n\u001a\u00020\u000bR\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u0014\u0010\u0006\u001a\u00020\u0007X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\b\u0010\t¨\u0006\f"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "shouldRunInMainThread", "", "getShouldRunInMainThread", "()Z", "mastgTest", "", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
/* loaded from: classes3.dex */
public final class MastgTest {
    public static final int $stable = 8;
    private final Context context;
    private final boolean shouldRunInMainThread;

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
        this.shouldRunInMainThread = true;
    }

    public final boolean getShouldRunInMainThread() {
        return this.shouldRunInMainThread;
    }

    /* JADX WARN: Type inference failed for: r6v8, types: [org.owasp.mastestapp.MastgTest$mastgTest$customProtectedButton$1] */
    public final String mastgTest() {
        LinearLayout layout = new LinearLayout(this.context);
        layout.setOrientation(1);
        layout.setGravity(17);
        layout.setBackgroundColor(0);
        int halfWidth = (int) (this.context.getResources().getDisplayMetrics().widthPixels * 0.5d);
        LinearLayout.LayoutParams $this$mastgTest_u24lambda_u241 = new LinearLayout.LayoutParams(halfWidth, -2);
        $this$mastgTest_u24lambda_u241.gravity = 1;
        $this$mastgTest_u24lambda_u241.topMargin = 24;
        final Button $this$mastgTest_u24lambda_u243 = new Button(this.context);
        $this$mastgTest_u24lambda_u243.setText("Vulnerable: Confirm Payment");
        $this$mastgTest_u24lambda_u243.setOnClickListener(new View.OnClickListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                MastgTest.mastgTest$lambda$3$lambda$2($this$mastgTest_u24lambda_u243, view);
            }
        });
        layout.addView($this$mastgTest_u24lambda_u243, $this$mastgTest_u24lambda_u241);
        final Button $this$mastgTest_u24lambda_u245 = new Button(this.context);
        $this$mastgTest_u24lambda_u245.setText("Protected: Confirm Payment");
        $this$mastgTest_u24lambda_u245.setFilterTouchesWhenObscured(true);
        $this$mastgTest_u24lambda_u245.setOnClickListener(new View.OnClickListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda1
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                MastgTest.mastgTest$lambda$5$lambda$4($this$mastgTest_u24lambda_u245, view);
            }
        });
        layout.addView($this$mastgTest_u24lambda_u245, $this$mastgTest_u24lambda_u241);
        final Context context = this.context;
        final ?? r6 = new Button(context) { // from class: org.owasp.mastestapp.MastgTest$mastgTest$customProtectedButton$1
            @Override // android.view.View
            public boolean onFilterTouchEventForSecurity(MotionEvent event) {
                Intrinsics.checkNotNullParameter(event, "event");
                if ((event.getFlags() & 1) != 0) {
                    Toast.makeText(getContext(), "Blocked obscured touch", 0).show();
                    return false;
                }
                return super.onFilterTouchEventForSecurity(event);
            }
        };
        r6.setText("Custom Protection: Grant Permission");
        r6.setOnClickListener(new View.OnClickListener() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda2
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                MastgTest.mastgTest$lambda$7$lambda$6(r6, view);
            }
        });
        layout.addView((View) r6, $this$mastgTest_u24lambda_u241);
        Context context2 = this.context;
        Activity activity = context2 instanceof Activity ? (Activity) context2 : null;
        if (activity != null) {
            ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content);
            if (layout.getParent() == null) {
                root.addView(layout, new FrameLayout.LayoutParams(-1, -1));
                return "Created buttons with various overlay protections";
            }
            return "Created buttons with various overlay protections";
        }
        return "Created buttons with various overlay protections";
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final void mastgTest$lambda$3$lambda$2(Button this_apply, View it) {
        Intrinsics.checkNotNullParameter(this_apply, "$this_apply");
        Toast.makeText(this_apply.getContext(), "Vulnerable button clicked", 0).show();
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final void mastgTest$lambda$5$lambda$4(Button this_apply, View it) {
        Intrinsics.checkNotNullParameter(this_apply, "$this_apply");
        Toast.makeText(this_apply.getContext(), "Protected button clicked", 0).show();
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final void mastgTest$lambda$7$lambda$6(MastgTest$mastgTest$customProtectedButton$1 this_apply, View it) {
        Intrinsics.checkNotNullParameter(this_apply, "$this_apply");
        Toast.makeText(this_apply.getContext(), "Custom protected button clicked", 0).show();
    }
}

To demonstrate this vulnerability in a live environment, you can use App Requesting SYSTEM_ALERT_WINDOW Permission, which shows an attacker app that requests the SYSTEM_ALERT_WINDOW permission to draw overlays over other apps. Running the attacker app while the victim app is in the foreground lets you verify whether the unprotected button accepts touch events through an overlay.

Steps

Let's run our semgrep rule against the decompiled code to detect overlay protection mechanisms.

../../../../rules/mastg-android-overlay-protection.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
rules:
  - id: mastg-android-overlay-protection-setfiltertoucheswhenobscured
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects usage of setFilterTouchesWhenObscured method for overlay attack protection
    message: "[MASVS-PLATFORM-3] setFilterTouchesWhenObscured is used to protect against overlay attacks"
    pattern: $VIEW.setFilterTouchesWhenObscured(...)

  - id: mastg-android-overlay-protection-onfiltertoucheventforsecurity
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects overriding of onFilterTouchEventForSecurity for custom overlay protection
    message: "[MASVS-PLATFORM-3] onFilterTouchEventForSecurity is overridden for custom overlay protection"
    pattern: |
      public boolean onFilterTouchEventForSecurity(...) { ... }

  - id: mastg-android-overlay-protection-flag-window-is-obscured
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects checks for FLAG_WINDOW_IS_OBSCURED flag to detect overlay attacks
    message: "[MASVS-PLATFORM-3] FLAG_WINDOW_IS_OBSCURED is checked to detect overlay attacks"
    pattern-either:
      - pattern: $EVENT.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED
      - pattern: ($EVENT.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0

  - id: mastg-android-overlay-protection-flag-window-is-partially-obscured
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects checks for FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag to detect overlay attacks
    message: "[MASVS-PLATFORM-3] FLAG_WINDOW_IS_PARTIALLY_OBSCURED is checked to detect overlay attacks"
    pattern-either:
      - pattern: $EVENT.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED
      - pattern: ($EVENT.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0

  - id: mastg-android-overlay-protection-xml-attribute
    severity: INFO
    languages:
      - xml
    metadata:
      summary: Detects filterTouchesWhenObscured attribute in layout XML files
    message: "[MASVS-PLATFORM-3] android:filterTouchesWhenObscured attribute is used to protect against overlay attacks"
    pattern: android:filterTouchesWhenObscured="true"

  - id: mastg-android-overlay-protection-sethideoverlaywindows
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects usage of setHideOverlayWindows to prevent overlay windows from appearing over the activity
    message: "[MASVS-PLATFORM-3] setHideOverlayWindows is used to prevent overlay windows from appearing over this window"
    pattern: $WINDOW.setHideOverlayWindows(...)

  - id: mastg-android-overlay-protection-hide-overlay-windows-permission
    severity: INFO
    languages:
      - xml
    metadata:
      summary: Detects HIDE_OVERLAY_WINDOWS permission in the manifest, required for setHideOverlayWindows
    message: "[MASVS-PLATFORM-3] HIDE_OVERLAY_WINDOWS permission is declared, required to use setHideOverlayWindows"
    pattern: android:name="android.permission.HIDE_OVERLAY_WINDOWS"
run.sh
1
2
3
4
#!/bin/bash

# Run semgrep to detect overlay protection mechanisms
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-overlay-protection.yml ./MastgTest_reversed.java --text > output.txt

Observation

The rule detected two instances of overlay protection mechanisms in the decompiled code:

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────┐
 2 Code Findings 
└─────────────────┘

    MastgTest_reversed.java
      rules.mastg-android-overlay-protection-setfiltertoucheswhenobscured
          [MASVS-PLATFORM-3] setFilterTouchesWhenObscured is used to protect against overlay attacks

           54 $this$mastgTest_u24lambda_u245.setFilterTouchesWhenObscured(true);

      rules.mastg-android-overlay-protection-onfiltertoucheventforsecurity
          [MASVS-PLATFORM-3] onFilterTouchEventForSecurity is overridden for custom overlay protection

           64 @Override // android.view.View
           65 public boolean onFilterTouchEventForSecurity(MotionEvent event) {
           66     Intrinsics.checkNotNullParameter(event, "event");
           67     if ((event.getFlags() & 1) != 0) {
           68         Toast.makeText(getContext(), "Blocked obscured touch", 0).show();
           69         return false;
           70     }
           71     return super.onFilterTouchEventForSecurity(event);
           72 }
  • Line 54: setFilterTouchesWhenObscured(true) is set on the protected button.
  • Lines 64-72: onFilterTouchEventForSecurity is overridden to implement custom overlay protection, including a check for the FLAG_WINDOW_IS_OBSCURED flag (literal value 1) at line 67.

Evaluation

The test partially passes and partially fails:

FAIL: The first button ("Vulnerable: Confirm Payment") doesn't appear in the output, meaning it implements no overlay protection. This button performs a sensitive action (payment confirmation) and is susceptible to tapjacking attacks.

PASS: The second button (line 54) correctly implements overlay protection using setFilterTouchesWhenObscured(true), which filters out touch events when the view is obscured by another window.

PASS: The third button (lines 64-72) implements custom overlay protection by overriding onFilterTouchEventForSecurity and checking the FLAG_WINDOW_IS_OBSCURED flag. This provides fine-grained control over how the app responds to overlay attempts.

In a real-world assessment, the unprotected button should be flagged as a finding. Sensitive UI elements such as payment confirmations, permission grants, or authentication controls should implement overlay protection using one of the demonstrated mechanisms.