Skip to content

MASTG-DEMO-0081: Sensitive User Data Sent to Firebase Analytics with Frida

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

Sample

This sample collects the following sensitive user data and sends it to Firebase Analytics using the logEvent method:

  • User ID (Data type: User IDs, Category: Personal info)
  • Blood type (Data type: Health info, Category: Health and fitness)

For the sake of this demo, we pretend that the app is published on Google Play and that the data types collected are not disclosed in the Data safety section.

  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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package org.owasp.mastestapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.decodeFromJsonElement

const val MASTG_TEXT_TAG = "mastgTestText"

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MainScreen()
        }
    }
}

fun UpdateDisplayString(
    defaultMessage: String,
    result: String
): AnnotatedString {
    return buildAnnotatedString {
        append(defaultMessage)
        try {
            val jsonArrayFromString = Json.parseToJsonElement(result) as JsonArray
            val demoResults = jsonArrayFromString.map { Json.decodeFromJsonElement<DemoResult>(it) }

            for (demoResult in demoResults) {
                when (demoResult.status) {
                    Status.PASS -> {
                        withStyle(style = SpanStyle(color = Color.Green)) {
                            append("MASTG-DEMO-${demoResult.demoId} demonstrated a successful test:\n${demoResult.message}\n\n")
                        }
                    }

                    Status.FAIL -> {
                        withStyle(style = SpanStyle(color = Color(0xFFFF9800))) {
                            append("MASTG-DEMO-${demoResult.demoId} demonstrated a failed test:\n${demoResult.message}\n\n")
                        }
                    }

                    Status.ERROR -> {
                        withStyle(style = SpanStyle(color = Color.Red)) {
                            append("MASTG-DEMO-${demoResult.demoId} failed:\n${demoResult.message}\n\n")
                        }
                    }
                }
            }
        } catch (_: Exception) {
            // not a valid set of DemoResult, so print the result without any parsing
            append(result)
        }
    }

}

@Preview
@Composable
fun MainScreen() {
    val defaultMessage = "Click \"Start\" to send the data.\n\n"
    var displayString by remember { mutableStateOf(buildAnnotatedString { append(defaultMessage) }) }
    var selectedBloodType by remember { mutableStateOf("") }
    val context = LocalContext.current
    val mastgTestClass = MastgTest(context)
    // By default run the test in a separate thread, this ensures that network tests such as those using SSLSocket work properly.
    // However, some tests which interact with UI elements need to run on the main thread.
    // You can set shouldRunInMainThread = true in MastgTest.kt for those tests.
    val runInMainThread = MastgTest::class.members
        .find { it.name == "shouldRunInMainThread" }
        ?.call(mastgTestClass) as? Boolean ?: false

    BaseScreen(
        onStartClick = {
            if (runInMainThread) {
                val result = mastgTestClass.mastgTest(selectedBloodType)
                displayString = UpdateDisplayString(defaultMessage, result)
            } else {
                Thread {
                    val result = mastgTestClass.mastgTest(selectedBloodType)
                    android.os.Handler(android.os.Looper.getMainLooper()).post {
                        displayString = UpdateDisplayString(defaultMessage, result)
                    }
                }.start()
            }
        }
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            // Normal visible selection UI: list of radio buttons for blood types
            val bloodTypes = listOf("A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-")

            var expanded by remember { mutableStateOf(false) }

            Button(onClick = { expanded = !expanded }) {
                Text("Select Blood Type")
            }
            DropdownMenu(
                expanded = expanded,
                onDismissRequest = { expanded = false }
            ) {
                bloodTypes.forEach {
                    DropdownMenuItem(
                        text = { Text(it) },
                        onClick = {
                            selectedBloodType = it
                            expanded = false
                        }
                    )
                }
            }

            if (selectedBloodType.isNotEmpty()) {
                Text(
                    modifier = Modifier.padding(vertical = 16.dp),
                    color = Color.White,
                    text = "Selected Blood Type: $selectedBloodType"
                )
            }

            Text(
                modifier = Modifier
                    .padding(top = 8.dp)
                    .testTag(MASTG_TEXT_TAG),
                text = displayString,
                color = Color.White,
                fontSize = 16.sp,
                fontFamily = FontFamily.Monospace
            )
        }
    }
}
 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
package org.owasp.mastestapp

import android.content.Context
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.logEvent
import kotlin.random.Random

class MastgTest(context: Context) {

    val analytics = FirebaseAnalytics.getInstance(context)

    // Random arbitrary number for the sake of the demo
    val userId: String = (1..8).map { Random.nextInt(0, 10) }.joinToString("")

    fun mastgTest(bloodType: String): String {
        analytics.logEvent("user_blood_type") {
            param("user_id", userId)
            param("blood_type", bloodType)
        }

        return """
            'user_blood_type' event was sent to Firebase Analytics.

            User id: $userId
            Blood type: $bloodType
        """.trimIndent()
    }
}
1
implementation("com.google.firebase:firebase-analytics:23.0.0")

Steps

  1. Install the app on a device ( Installing Apps)
  2. Make sure you have Frida for Android installed on your machine and the frida-server running on the device
  3. Run run.sh to spawn the app with Frida
  4. Select a blood type from the dropdown
  5. Click the Start button
  6. Stop the script by pressing Ctrl+C and/or q to quit the Frida CLI
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var target = {
  category: "PRIVACY",
  hooks: [
    {
      class: "com.google.firebase.analytics.FirebaseAnalytics",
      methods: [
        "logEvent"
      ]
    }
  ]
}
1
2
#!/bin/bash
../../../../utils/frida/android/run.sh ./hooks.js

Observation

The output shows all instances of logEvent calls to the Firebase Analytics SDK found at runtime, along with the parameters sent. A backtrace is also provided to help identify the location in the code.

output.json
 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
{
  "type": "summary",
  "hooks": [
    {
      "class": "com.google.firebase.analytics.FirebaseAnalytics",
      "method": "logEvent",
      "overloads": [
        {
          "args": [
            "java.lang.String",
            "android.os.Bundle"
          ]
        }
      ]
    }
  ],
  "totalHooks": 1,
  "errors": [],
  "totalErrors": 0
}
{
  "id": "68f13e77-c2aa-4778-be90-fc7a5c50af60",
  "type": "hook",
  "category": "PRIVACY",
  "time": "2025-12-04T14:03:05.046Z",
  "class": "com.google.firebase.analytics.FirebaseAnalytics",
  "method": "logEvent",
  "instanceId": 33276343,
  "stackTrace": [
    "com.google.firebase.analytics.FirebaseAnalytics.logEvent(Native Method)",
    "org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:35)",
    "org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$15$lambda$14(MainActivity.kt:107)",
    "org.owasp.mastestapp.MainActivityKt.$r8$lambda$REihytfpbfdEl1cb3dLDlSFXk5M(Unknown Source:0)",
    "org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda2.run(D8$$SyntheticClass:0)",
    "java.lang.Thread.run(Thread.java:1119)"
  ],
  "inputParameters": [
    {
      "declaredType": "java.lang.String",
      "value": "user_blood_type"
    },
    {
      "declaredType": "android.os.Bundle",
      "value": "<instance: android.os.Bundle>",
      "runtimeType": "android.os.Bundle",
      "instanceId": "130116900",
      "instanceToString": "Bundle[{blood_type=A+, user_id=87495975}]"
    }
  ],
  "returnValue": [
    {
      "declaredType": "void",
      "value": "void"
    }
  ]
}

Evaluation

This test fails because sensitive data (blood_type parameter) is being sent to Firebase Analytics via the logEvent method for a particular user (user_id parameter) and this data collection is not disclosed in the Data safety section on Google Play (as we indicated in the sample description).