Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
4d1d2b0
added frida.re base script and DEMO-0058 which uses this script
bernhste Jun 26, 2025
246fdf9
clean up after frida.re run, clearer description in DEMO code
bernhste Jun 26, 2025
2acc017
added previously removed output.txt
bernhste Jun 26, 2025
4120adc
Added DEMO-0059
bernhste Jun 26, 2025
1946708
changed demo to be compatible with the MAS Android base app from the …
bernhste Jun 26, 2025
09c89d4
refactor: improve formatting and add missing semicolons in registerHook
cpholguera Jun 27, 2025
ca6251a
feat: add maxFrames parameter to registerHook for stack trace control
cpholguera Jun 27, 2025
d9b74ae
fix: change output file extension from .txt to .json in run.sh
cpholguera Jul 1, 2025
cbd5040
update demo-0058 output to be json
cpholguera Jul 1, 2025
8a78b1c
update demo-0059 output to be json
cpholguera Jul 1, 2025
4703049
fix demo-0059 kotlin file to be the original
cpholguera Jul 1, 2025
4974b99
Merge branch 'OWASP:master' into DEMO-KeyGenParamSpec
bernhste Jul 3, 2025
c0785a2
removed reversed code from demos which only use frida.re
bernhste Jul 3, 2025
8818782
removed reference to reversed files
bernhste Jul 3, 2025
b0675ea
Merge branch 'OWASP:master' into DEMO-KeyGenParamSpec
bernhste Jul 6, 2025
6308bd2
moved frida.re decoder to /utils
bernhste Jul 7, 2025
4218303
Merge branch 'OWASP:master' into DEMO-KeyGenParamSpec
bernhste Jul 29, 2025
a495baf
refactored the frida files for more clarity, and less folders
bernhste Jul 29, 2025
210cbfa
Merge branch 'OWASP:master' into DEMO-KeyGenParamSpec
bernhste Jul 29, 2025
5022d75
JSON evaluation for frida.re script done in evaluat.sh, fixed wrong f…
bernhste Jul 29, 2025
de052a0
Update demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/MASTG-DEMO-0058.md
bernhste Jul 29, 2025
762b3ad
Update demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/MASTG-DEMO-0058.md
bernhste Jul 29, 2025
4c29d82
changed the order of the files shown in the steps (hooks.js bevore ru…
bernhste Jul 30, 2025
34fe78b
Update demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/MASTG-DEMO-0058.md
bernhste Jul 30, 2025
8bb145c
Update demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/MASTG-DEMO-0058.md
bernhste Jul 30, 2025
7784bdc
Update demos/android/MASVS-STORAGE/MASTG-DEMO-0059/MASTG-DEMO-0059.md
bernhste Jul 30, 2025
b696d05
updated evaluation text in demo 0058/0059 for better clarity.
bernhste Jul 30, 2025
5491380
Update demos/android/MASVS-STORAGE/MASTG-DEMO-0059/MastgTest.kt
bernhste Jul 30, 2025
3dc936d
updated how to use SharedPreferences in secure and insecure ways.
bernhste Jul 31, 2025
436b463
Update demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/hooks.js
bernhste Aug 1, 2025
f4c7aae
updated DEMO-0058 with additional use cases of ECB keys on current An…
bernhste Aug 1, 2025
b033f52
updated output.json
bernhste Aug 1, 2025
872caa4
updated output.json
bernhste Aug 1, 2025
d653de6
Merge branch 'master' into DEMO-KeyGenParamSpec
bernhste Aug 30, 2025
1100fc4
fixed a MD file linting issue
bernhste Aug 30, 2025
044ceda
updated Check Website Build" to also work with PR form forks
bernhste Aug 30, 2025
31f7ea8
revert changes to Check Website Build due to pipeline error
bernhste Aug 30, 2025
03dac59
Merge branch 'master' into DEMO-KeyGenParamSpec
cpholguera Aug 31, 2025
f6505ca
Merge branch 'master' into DEMO-KeyGenParamSpec
cpholguera Aug 31, 2025
f4a1f54
Merge branch 'master' into DEMO-KeyGenParamSpec
bernhste Aug 31, 2025
8e1c052
Merge branch 'OWASP:master' into DEMO-KeyGenParamSpec
bernhste Sep 1, 2025
8638823
Update demos/android/MASVS-STORAGE/MASTG-DEMO-0059/hooks.js
bernhste Sep 1, 2025
c12806b
added the suggested tests for the sandbox analysis to the shared pref…
bernhste Sep 1, 2025
980ee97
fix md lint
bernhste Sep 1, 2025
664f608
Merge branch 'master' into DEMO-KeyGenParamSpec
bernhste Sep 9, 2025
1eb6617
Update utils/frida/android/android_decoder.js
bernhste Sep 9, 2025
0fac32a
Update utils/frida/android/base_script.js
bernhste Sep 9, 2025
f14a3ab
Update demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/hooks.js
bernhste Sep 9, 2025
5a0c8e5
Update demos/android/MASVS-STORAGE/MASTG-DEMO-0059/hooks.js
bernhste Sep 9, 2025
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
50 changes: 50 additions & 0 deletions demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/MASTG-DEMO-0058.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
platform: android
title: Use of Insecure ECB Block Mode in KeyGenParameterSpec
id: MASTG-DEMO-0058
code: [kotlin]
test: MASTG-TEST-0232
---

### Sample

The code below generates symmetric encryption keys meant to be stored in the Android KeyStore, but it does so using the ECB block mode, which is considered broken due to practical known-plaintext attacks and is disallowed by NIST for data encryption. The method used to set the block modes is [`KeyGenParameterSpec.Builder#setBlockModes(...)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setBlockModes(java.lang.String[])):

```kotlin
public KeyGenParameterSpec.Builder setBlockModes (String... blockModes)
```

Current versions of Android prohibit the usage of keys with for ECB in some cases. For example, it is not possible to use the key to encrypt data by the default. Nevertheless, there are some case, where ECB can still be used:

- Decrypt data
- Encrypt data with a key given `setRandomizedEncryptionRequired` is set to `false`

{{ MastgTest.kt }}

### Steps

1. Install the app on a device (@MASTG-TECH-0005)
2. Make sure you have @MASTG-TOOL-0001 installed on your machine and the frida-server running on the device
3. Run `run.sh` to spawn the app with Frida
4. Click the **Start** button
5. Stop the script by pressing `Ctrl+C` and/or `q` to quit the Frida CLI

{{ hooks.js # run.sh }}

### Observation

The output shows all instances of block modes mode that were found at runtime. A backtrace is also provided to help identify the location in the code.

{{ output.json }}

### Evaluation

The method `setBlockModes` has now been called three times with ECB as one of the block modes.

The test fails, as key used with these `KeyGenParameterSpec` can now be used used to insecurely encrypt data.

You can automatically evaluate the output using tools like `jq` as demonstrated in `evaluation.sh`.

{{ evaluate.sh }}

See @MASTG-TEST-0232 for more information.
114 changes: 114 additions & 0 deletions demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.owasp.mastestapp

import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.security.keystore.KeyProtection
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import android.util.Base64
import javax.crypto.spec.SecretKeySpec

class MastgTest(private val context: Context) {

fun mastgTest(): String {

val results = mutableListOf<String>()
var rawKey: SecretKey? = null
var encryptedData: ByteArray? = null
var decryptedData: ByteArray? = null

// Suppose we received a raw key from a secure source and we want to use it for decryption.
// The following commented-out code is an example of generating a raw key and encrypting data with it.
// We obtained the raw key and encrypted data from the logs and added them to the code for demonstration purposes.
try {
// Suppose we received the raw key from a secure source and we want to use it for decryption.
val rawKeyString = "43ede5660e82123ee091d6b4c8f7d150"
val keyBytes = rawKeyString.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
rawKey = SecretKeySpec(keyBytes, KeyProperties.KEY_ALGORITHM_AES)

// The cipher text is 'Hello from OWASP MASTG!' AES/ECB encrypted using CyberChef:
// https://gchq.github.io/CyberChef/#recipe=AES_Encrypt(%7B'option':'Hex','string':'43ede5660e82123ee091d6b4c8f7d150'%7D,%7B'option':'Hex','string':''%7D,'ECB','Raw','Hex',%7B'option':'Hex','string':''%7D)&input=SGVsbG8gZnJvbSBPV0FTUCBNQVNURyE
val encryptedDataString = "20b0eef4e5ad3d8984a4fb94f6001885f0ce25104cb8251f600624b46dcefb92"
encryptedData = encryptedDataString.chunked(2).map { it.toInt(16).toByte() }.toByteArray()

val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val alias = "importedAesKey"
val entry = KeyStore.SecretKeyEntry(rawKey)
val protection = KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build()
keyStore.setEntry(alias, entry, protection)
val importedKey = keyStore.getKey(alias, null) as SecretKey
val cipher2 = Cipher.getInstance("AES/ECB/PKCS7Padding").apply {
init(Cipher.DECRYPT_MODE, importedKey)
}
decryptedData = cipher2.doFinal(encryptedData)
val decryptedString = String(decryptedData)
results.add("\n[*] Keystore-imported AES ECB key decryption (plaintext):\n\n$decryptedString")
} catch (e: Exception) {
results.add("\n[!] Keystore-imported AES ECB key decryption error:\n\n${e.message}")
}

// import the raw key into AndroidKeyStore for encryption which would fail unless randomized encryption is disabled (bad practice)
try {
if (rawKey == null || encryptedData == null) {
throw IllegalStateException("Key or data missing for encryption")
}
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val alias = "importedAesKey2"
val entry = KeyStore.SecretKeyEntry(rawKey)
val protection = KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setRandomizedEncryptionRequired(false) // For demonstration purposes, we disable randomized encryption
.build()
keyStore.setEntry(alias, entry, protection)
val importedKey = keyStore.getKey(alias, null) as SecretKey
val cipher3 = Cipher.getInstance("AES/ECB/PKCS7Padding").apply {
init(Cipher.ENCRYPT_MODE, importedKey)
}
val encryptedBytes = cipher3.doFinal(decryptedData)
val encrypted = Base64.encodeToString(encryptedBytes, Base64.DEFAULT)

results.add("\n\n[*] Keystore-imported AES ECB key encryption (ciphertext):\n\n$encrypted")
} catch (e: Exception) {
results.add("\n\n[!] Keystore-imported AES ECB key encryption error:\n\n${e.message}")
}

// keystore key generation and encryption
try {
val keyAlias = "testKeyGenParameter"
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val spec = KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
// .setRandomizedEncryptionRequired(false) // Disabling randomized encryption would allow the key to be used in ECB mode.
.build()
KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
).apply {
init(spec)
generateKey()
}

val secretKey = keyStore.getKey(keyAlias, null) as SecretKey
val cipher = Cipher.getInstance("AES/ECB/PKCS7Padding").apply {
init(Cipher.ENCRYPT_MODE, secretKey)
}
val encrypted = Base64.encodeToString(cipher.doFinal(decryptedData), Base64.DEFAULT)
results.add("\n[*] Keystore-generated AES ECB key encryption (ciphertext):\n\n$encrypted")
} catch (e: Exception) {
results.add("\n[!] Keystore-generated AES ECB error:\n\n${e.message}")
}

return results.joinToString("\n")
}
}
9 changes: 9 additions & 0 deletions demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/evaluate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

jq '
select(
.class=="android.security.keystore.KeyGenParameterSpec$Builder"
and .method=="setBlockModes"
and (.inputParameters[0].value | contains(["ECB"]))
)
' output.json
20 changes: 20 additions & 0 deletions demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var target = {
category: "CRYPTO",
demo: "0058",
hooks: [
{
class: "android.security.keystore.KeyGenParameterSpec$Builder",
methods: [
"setBlockModes",
"setRandomizedEncryptionRequired"
]
},
{
class: "android.security.keystore.KeyProtection$Builder",
methods: [
"setBlockModes",
"setRandomizedEncryptionRequired"
]
}
]
}
122 changes: 122 additions & 0 deletions demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"id": "523e8eb7-e155-4792-bdae-6c2a728c87ac",
"category": "CRYPTO",
"time": "2025-08-01T09:00:07.277Z",
"class": "android.security.keystore.KeyProtection$Builder",
"method": "setBlockModes",
"stackTrace": [
"android.security.keystore.KeyProtection$Builder.setBlockModes(Native Method)",
"org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:41)",
"org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$7(MainActivity.kt:53)",
"org.owasp.mastestapp.MainActivityKt.$r8$lambda$JVJO2MsmWvFAgk27L17N1ocLpI0(Unknown Source:0)",
"org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)",
"androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)",
"androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)",
"androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)"
],
"inputParameters": [
{
"type": "[Ljava.lang.String;",
"value": [
"ECB"
]
}
],
"returnValue": [
{
"type": "android.security.keystore.KeyProtection$Builder",
"value": "<instance: android.security.keystore.KeyProtection$Builder>"
}
]
}
{
"id": "a162bca9-454e-4d48-a737-0ac6e73983c7",
"category": "CRYPTO",
"time": "2025-08-01T09:00:07.288Z",
"class": "android.security.keystore.KeyProtection$Builder",
"method": "setBlockModes",
"stackTrace": [
"android.security.keystore.KeyProtection$Builder.setBlockModes(Native Method)",
"org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:65)",
"org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$7(MainActivity.kt:53)",
"org.owasp.mastestapp.MainActivityKt.$r8$lambda$JVJO2MsmWvFAgk27L17N1ocLpI0(Unknown Source:0)",
"org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)",
"androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)",
"androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)",
"androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)"
],
"inputParameters": [
{
"type": "[Ljava.lang.String;",
"value": [
"ECB"
]
}
],
"returnValue": [
{
"type": "android.security.keystore.KeyProtection$Builder",
"value": "<instance: android.security.keystore.KeyProtection$Builder>"
}
]
}
{
"id": "8dd8050c-dbc0-4662-804a-8bfb2151ca34",
"category": "CRYPTO",
"time": "2025-08-01T09:00:07.291Z",
"class": "android.security.keystore.KeyProtection$Builder",
"method": "setRandomizedEncryptionRequired",
"stackTrace": [
"android.security.keystore.KeyProtection$Builder.setRandomizedEncryptionRequired(Native Method)",
"org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:67)",
"org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$7(MainActivity.kt:53)",
"org.owasp.mastestapp.MainActivityKt.$r8$lambda$JVJO2MsmWvFAgk27L17N1ocLpI0(Unknown Source:0)",
"org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)",
"androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)",
"androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)",
"androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)"
],
"inputParameters": [
{
"type": "boolean",
"value": false
}
],
"returnValue": [
{
"type": "android.security.keystore.KeyProtection$Builder",
"value": "<instance: android.security.keystore.KeyProtection$Builder>"
}
]
}
{
"id": "394de339-b6c6-485e-babc-672ff5df315f",
"category": "CRYPTO",
"time": "2025-08-01T09:00:07.300Z",
"class": "android.security.keystore.KeyGenParameterSpec$Builder",
"method": "setBlockModes",
"stackTrace": [
"android.security.keystore.KeyGenParameterSpec$Builder.setBlockModes(Native Method)",
"org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:90)",
"org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$7(MainActivity.kt:53)",
"org.owasp.mastestapp.MainActivityKt.$r8$lambda$JVJO2MsmWvFAgk27L17N1ocLpI0(Unknown Source:0)",
"org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0)",
"androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke-k-4lQ0M(Clickable.kt:639)",
"androidx.compose.foundation.ClickableNode$clickPointerInput$3.invoke(Clickable.kt:633)",
"androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1.invokeSuspend(TapGestureDetector.kt:255)"
],
"inputParameters": [
{
"type": "[Ljava.lang.String;",
"value": [
"ECB"
]
}
],
"returnValue": [
{
"type": "android.security.keystore.KeyGenParameterSpec$Builder",
"value": "<instance: android.security.keystore.KeyGenParameterSpec$Builder>"
}
]
}
2 changes: 2 additions & 0 deletions demos/android/MASVS-CRYPTO/MASTG-DEMO-0058/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
../../../../utils/frida/android/run.sh ./hooks.js
70 changes: 70 additions & 0 deletions demos/android/MASVS-STORAGE/MASTG-DEMO-0059/MASTG-DEMO-0059.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
platform: android
title: App Writing Sensitive Data to Sandbox using SharedPreferences
id: MASTG-DEMO-0059
code: [kotlin]
test: MASTG-TEST-0207
---

### Sample

The code snippet below shows sample code which stores sensitive data using `SharedPreferences`. It stores sensitive data using `String` and `StringSet`.

{{ MastgTest.kt }}

### Steps

1. Install the app on a device (@MASTG-TECH-0005)
2. Make sure you have @MASTG-TOOL-0001 installed on your machine and the frida-server running on the device
3. Run `run.sh` to spawn the app with Frida
4. Click the **Start** button
5. Stop the script by pressing `Ctrl+C` and/or `q` to quit the Frida CLI

{{ hooks.js # run.sh }}

### Observation

The output shows all instances of strings written via `SharedPreferences` that were found at runtime. A backtrace is also provided to help identify the location in the code.

{{ output.json }}

### Evaluation

In output.json we can identify several entries that use the `SharedPreferences` API write strings to the app's local sandbox. In this case to `/data/data/org.owasp.mastestapp/shared_prefs/MasSharedPref_Sensitive_Data.xml`:

- `putString` is used to write an unencrypted `UnencryptedGitHubToken` of value `ghp_1234567890a...`
- `putString` is used to write an encrypted `EncryptedAwsKey` of value `V1QyXhGV88RQLmMjoTLLl...`
- `putStringSet` is used to write an unencrypted `UnencryptedPreSharedKeys` set with values `MIIEvAIBADAN...` and `gJXS9EwpuzK8...`

We can use the values and try to trace them back to crypto method calls and check if they are encrypted. For example, let's analyze the `EncryptedAwsKey` of value `V1QyXhGV88RQLmMjoTLLl...`:

- `V1QyXhGV88RQLmMjoTLLl...` is the return value of `Base64.encodeToString` for the input `0x5754325e1195f3c45...`
- `0xa132cb95022985be` is the return value of `Cipher.doFinal` for the input `AKIAIOSFODNN7EXAMPLE`

However, we cannot find any calls to `Base64.encodeToString` or `Cipher.***` for the `preSharedKeys` values written by `putStringSet` (`MIIEvAIBADAN...` and `gJXS9EwpuzK8...`).

You can confirm this by reverse engineering the app and inspecting the code. Inspect the `stackTrace` of the `putString` and `putStringSet` entries, then go to the corresponding locations in the code. For example, go to the `org.owasp.mastestapp.MastgTest.mastgTest` method and try to trace back the input parameters to determine whether they are encrypted.

The test **fails** due because we found some entries that aren't encrypted.

Any data in the app sandbox can be extracted using backups or root access on a compromised phone. For example, run the following command:

```sh
adb shell cat /data/data/org.owasp.mastestapp/shared_prefs/MasSharedPref_Sensitive_Data.xml
```

Which returns:

```xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="EncryptedAwsKey">V1QyXhGV88RQLmMjoTLLlQIphb6SKf4CBqx+PqhH/TTPFtCh9RPTAYezWW5RPhPP&#10; </string>
<set name="UnencryptedPreSharedKeys">
<string>gJXS9EwpuzK8U1TOgfplwfKEVngCE2D5FNBQWvNmuHHbigmTCabsA=</string>
<string>MIIEvAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALfX7kbfFv3pc3JjOHQ=</string>
</set>
<string name="UnencryptedGitHubToken">ghp_1234567890abcdefghijklmnOPQRSTUV</string>
</map>
```

All entries that aren't encrypted can be leveraged by an attacker.
Loading