MASTG-KNOW-0055: Keyboard Cache
When users enter information into input fields, the keyboard software often provides suggestions based on previously entered data. This auto-completion feature can be very useful for messaging apps and other scenarios. However, by default, the Android keyboard may retain (or "cache") input history to offer suggestions and auto-completion. In contexts where sensitive data is entered (such as passwords or PINs), this caching behavior can inadvertently expose sensitive information.
Apps can control this behavior by appropriately configuring the inputType attribute on text input fields. There are several ways to do this:
XML Layouts:
In the app's XML layout files (typically located in the /res/layout directory after unpacking the APK), you can define the input type directly in the <EditText> element using the android:inputType attribute. For example, setting the input type to "textPassword" automatically disables auto-suggestions and caching:
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_hint"
android:inputType="textPassword" />
Using the Traditional Android View System:
When creating input fields in code using the traditional Android view system, you can set the input type programmatically. For example, using an EditText in Kotlin:
val input = EditText(context).apply {
hint = "Enter PIN"
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
}
Using Jetpack Compose:
If you are developing with Jetpack Compose, you do not use EditText directly. Instead, you use composable functions such as TextField or OutlinedTextField along with parameters like keyboardOptions and visualTransformation to achieve similar behavior. For example, to create a password field without suggestions:
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Enter Password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
autoCorrect = false
),
modifier = Modifier.fillMaxWidth()
)
In this Compose example, the PasswordVisualTransformation() masks the input, and keyboardOptions with KeyboardType.Password helps specify the password input type. The autoCorrect parameter is set to false to prevent suggestions.
Internally, the KeyboardType enum in Jetpack Compose maps to the Android inputType values. For example, the KeyboardType.Password corresponds to the following inputType:
KeyboardType.Password -> {
this.inputType =
InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
}
Non-Caching Input Types¶
Regardless of the method used, the app can use the following inputType attributes, when applied to <EditText> elements, instruct the system to disable suggestions and prevent caching for those input fields:
XML android:inputType |
Code InputType |
API level |
|---|---|---|
textNoSuggestions |
TYPE_TEXT_FLAG_NO_SUGGESTIONS |
3 |
textPassword |
TYPE_TEXT_VARIATION_PASSWORD |
3 |
textVisiblePassword |
TYPE_TEXT_VARIATION_VISIBLE_PASSWORD |
3 |
numberPassword |
TYPE_NUMBER_VARIATION_PASSWORD |
11 |
textWebPassword |
TYPE_TEXT_VARIATION_WEB_PASSWORD |
11 |
Note: In the MASTG tests we won't be checking the minimum required SDK version in the Android Manifest minSdkVersion because we are considering testing modern apps. If you are testing an older app, you should check it. For example, Android API level 11 is required for textWebPassword. Otherwise, the compiled app would not honor the used input type constants allowing keyboard caching.
The inputType attribute is a bitwise combination of flags and classes. The InputType class contains constants for both flags and classes. The flags are defined as TYPE_TEXT_FLAG_* and the classes are defined as TYPE_CLASS_*. The values of these constants are defined in the Android source code. You can find the source code for the InputType class here.
The inputType attribute in Android is a bitwise combination of these constants:
- Class constants (
TYPE_CLASS_*): Input type (text, number, phone, etc.) - Variation constants (
TYPE_TEXT_VARIATION_*, etc.): Specific behavior (password, email, URI, etc.) - Flag constants (
TYPE_TEXT_FLAG_*): Additional modifiers (no suggestions, multi-line, etc.)
For example, this Kotlin code:
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
Where:
TYPE_CLASS_TEXT= 1TYPE_TEXT_VARIATION_PASSWORD= 128
Results in 1 or 128 = 129, which is the value you will see in the decompiled code.
How to decode input type attributes after reverse engineering:
To decode the inputType value, you can use the following masks:
TYPE_MASK_CLASS=0x0000000F(to extract the class part)TYPE_MASK_VARIATION=0x00000FF0(to extract the variation part)TYPE_MASK_FLAGS=0x00FFF000(to extract the flags part)
You can quickly decode inputType values using the masks and the bitwise AND operation e.g. in Python:
129 & 0x0000000F # 1 (TYPE_CLASS_TEXT)
129 & 0x00000FF0 # 128 (TYPE_TEXT_VARIATION_PASSWORD)
How to find cached data:
If you write e.g. "OWASPMAS" in the passphrase field a couple of times, the app will cache it and you will be able to find it in the cache database:
adb shell 'strings /data/data/com.google.android.inputmethod.latin/databases/trainingcachev3.db' | grep -i "OWASPMAS"
OWASPMAS@
OWASPMAS@
OWASPMAS%