Introduction
Mobile applications frequently rely on XML configuration files to store settings, API keys, firewall rules, and user preferences. While XML is convenient, storing sensitive data directly in these files introduces critical security vulnerabilities. This blog post analyzes a sample XML configuration file, identifies security risks, proposes solutions, and provides Dart code for validation.
The XML Configuration File
<appConfig>
<environment>
<mode value="production" />
<api>
<baseUrl>https://api.holberton.com</baseUrl>
<apiKey>ABCD1234-EFGH5678-IJKL9101</apiKey>
<timeout>30</timeout>
</api>
</environment>
<permissions>
<permission name="location" required="true" />
<permission name="storage" required="false" />
<permission name="camera" required="false" />
</permissions>
<users>
<user id="1" role="admin">
<name>John Doe</name>
<email>johndoe@holberton.com</email>
<preferences>
<language>en</language>
<theme>dark</theme>
<notifications enabled="true" />
</preferences>
</user>
<user id="2" role="viewer">
<name>Jane Smith</name>
<email>janesmith@holberton.com</email>
<preferences>
<language>fr</language>
<theme>light</theme>
<notifications enabled="false" />
</preferences>
</user>
</users>
<security>
<encryption>
<type>AES-256</type>
<key>Base64EncodedEncryptionKey==</key>
</encryption>
<firewall>
<rules>
<rule action="allow" ip="192.168.1.0/24" />
<rule action="deny" ip="0.0.0.0/0" />
</rules>
</firewall>
</security>
<features>
<feature name="betaTesting" enabled="true" />
<feature name="chat" enabled="false" />
<feature name="fileSharing" enabled="true" />
</features>
</appConfig>
Security Risks Analysis
1. Hardcoded Sensitive Data (CRITICAL)
| Field | Value | Risk |
|---|---|---|
apiKey |
ABCD1234-EFGH5678-IJKL9101 |
Anyone who downloads the APK/IPA can extract this key from the bundled XML file using apktool or strings
|
encryption key |
Base64EncodedEncryptionKey== |
If this key encrypts user data, an attacker can decrypt everything by decompiling the app |
Attack scenario:
# Attacker extracts the APK
apktool d target_app.apk
# Finds the XML config
cat res/xml/config.xml | grep apiKey
# Output: <apiKey>ABCD1234-EFGH5678-IJKL9101</apiKey>
# Uses stolen key to access backend API
curl -H "Authorization: Bearer ABCD1234-EFGH5678-IJKL9101" https://api.holberton.com/v1/admin
Why this matters: Hardcoded secrets are the #1 mobile security vulnerability according to OWASP MASVS. Once the app ships, the key is exposed to anyone with a file manager or decompiler.
2. Overly Permissive Permissions
| Permission | required |
Risk |
|---|---|---|
location |
true |
Mandatory but may not be needed for all features |
storage |
false |
Set to false but still declared — if the app requests it at runtime, it grants unnecessary file system access |
camera |
false |
Same issue — declared permissions can be requested dynamically regardless of the XML flag |
The required="false" attribute is misleading. It suggests these permissions are optional, but the app can still request them at runtime through the platform permission dialog. Users often click "Allow" without reading, granting storage and camera access to an app that may not need them.
3. Misconfigured Firewall Rules
<rule action="allow" ip="192.168.1.0/24" />
<rule action="deny" ip="0.0.0.0/0" />
Problems:
-
Rule order matters. The
denyrule for0.0.0.0/0(all traffic) is last, which is correct for a default-deny policy. However,192.168.1.0/24allows the entire subnet — this includes devices that should not have access. -
No port restrictions. The allow rule permits ALL ports from
192.168.1.0/24. An attacker who compromises any device on that subnet gains full access. -
Internal network is trusted. The assumption that all devices on
192.168.1.0/24are trusted is dangerous — a compromised IoT device on the same network would bypass the firewall entirely.
4. User Data Stored in Plaintext
<user id="1" role="admin">
<name>John Doe</name>
<email>johndoe@holberton.com</email>
</user>
User PII (names, emails, roles) is stored in plaintext XML. If the app is decompiled or the file is accessed through a path traversal vulnerability, all user data is exposed.
5. Beta Features Enabled in Production
<feature name="betaTesting" enabled="true" />
Beta features enabled in production can expose unfinished, untested functionality that may contain vulnerabilities. An attacker can probe beta endpoints that bypass normal security checks.
Solutions
1. Secure Sensitive Data
Never hardcode secrets. Use one of these approaches:
| Method | Description | Use Case |
|---|---|---|
| Environment variables | Keys loaded from .env file excluded from version control |
Development |
| Secure vault | Android Keystore / iOS Keychain | Runtime storage |
| Backend fetch | App requests a short-lived token from a secure server at startup | Production |
| Encrypted preferences | Keys encrypted with a device-specific key before storage | Persistent config |
Recommended approach:
<!-- Remove hardcoded values -->
<api>
<baseUrl>https://api.holberton.com</baseUrl>
<!-- apiKey is fetched at runtime from a secure endpoint -->
<timeout>30</timeout>
</api>
2. Restrict Permissions
- Remove all permissions that are not strictly required
- Use role-based access control (RBAC) to grant permissions dynamically
- Request permissions only when needed (Android 6+ runtime permissions)
- Never declare permissions "just in case"
<permissions>
<permission name="location" required="true" />
</permissions>
3. Fix Firewall Rules
<firewall>
<rules>
<rule action="allow" ip="192.168.1.10" port="443" /> <!-- API server only -->
<rule action="allow" ip="192.168.1.20" port="443" /> <!-- Backup server -->
<rule action="deny" ip="0.0.0.0/0" /> <!-- Default deny -->
</rules>
</firewall>
Key changes:
- Allow only specific IPs, not entire subnets
- Restrict to port 443 (HTTPS) only
- Keep the deny-all as the last rule
4. Encrypt User Data
Store user preferences in encrypted storage (SharedPreferences with encryption on Android, NSUserDefaults is plaintext on iOS — use Keychain instead).
5. Disable Beta in Production
<features>
<feature name="betaTesting" enabled="false" />
</features>
Use build-time flags (Dart --dart-define) to control feature visibility per environment.
Dart Code: XML Parsing and Validation
import 'package:xml/xml.dart';
import 'dart:io';
void main() {
final xmlString = File('config.xml').readAsStringSync();
final document = XmlDocument.parse(xmlString);
final root = document.rootElement;
final errors = <String>[];
// --- Validate API Key is not empty ---
final apiKey = root.findAllElements('apiKey').firstOrNull;
if (apiKey == null || apiKey.innerText.trim().isEmpty) {
errors.add('CRITICAL: apiKey is missing or empty');
} else {
print('[PASS] apiKey is present');
}
// --- Validate timeout range (10-60 seconds) ---
final timeout = root.findAllElements('timeout').firstOrNull;
if (timeout != null) {
final timeoutValue = int.tryParse(timeout.innerText.trim());
if (timeoutValue == null || timeoutValue < 10 || timeoutValue > 60) {
errors.add(
'ERROR: timeout must be between 10 and 60 seconds '
'(found: ${timeout.innerText.trim()})',
);
} else {
print('[PASS] timeout is within valid range: $timeoutValue');
}
} else {
errors.add('ERROR: timeout element is missing');
}
// --- Validate unique user IDs ---
final users = root.findAllElements('user');
final userIds = <String>{};
for (final user in users) {
final id = user.getAttribute('id');
if (id == null || id.isEmpty) {
errors.add('ERROR: user element is missing an id attribute');
} else if (!userIds.add(id)) {
errors.add('ERROR: duplicate user id found: $id');
} else {
final name = user.findAllElements('name').firstOrNull?.innerText ?? 'unknown';
print('[PASS] user id=$id ($name) is unique');
}
}
// --- Validate firewall rules ---
final rules = root.findAllElements('rule');
final validActions = {'allow', 'deny'};
for (final rule in rules) {
final action = rule.getAttribute('action');
if (action == null || !validActions.contains(action)) {
errors.add(
'ERROR: invalid firewall rule action: $action '
'(must be "allow" or "deny")',
);
} else {
final ip = rule.getAttribute('ip') ?? 'unknown';
print('[PASS] firewall rule: $action $ip');
}
}
// --- Security warnings ---
final hardcodedKey = root.findAllElements('key').firstOrNull?.innerText;
if (hardcodedKey != null && hardcodedKey.isNotEmpty) {
errors.add(
'CRITICAL: encryption key is hardcoded in XML. '
'Move it to a secure vault (Keychain/Keystore)',
);
}
final storedApiKey = root.findAllElements('apiKey').firstOrNull?.innerText;
if (storedApiKey != null && storedApiKey.isNotEmpty) {
errors.add(
'CRITICAL: API key is hardcoded in XML. '
'Fetch it at runtime from a secure backend',
);
}
// --- Report ---
print('');
if (errors.isEmpty) {
print('All validations passed.');
} else {
print('Found ${errors.length} issue(s):');
for (final error in errors) {
print(' - $error');
}
}
}
Expected Output
[PASS] apiKey is present
[PASS] timeout is within valid range: 30
[PASS] user id=1 (John Doe) is unique
[PASS] user id=2 (Jane Smith) is unique
[PASS] firewall rule: allow 192.168.1.0/24
[PASS] firewall rule: deny 0.0.0.0/0
Found 2 issue(s):
- CRITICAL: encryption key is hardcoded in XML. Move it to a secure vault (Keychain/Keystore)
- CRITICAL: API key is hardcoded in XML. Fetch it at runtime from a secure backend
Conclusion
XML configuration files are a common attack surface in mobile applications. The issues found in this analysis — hardcoded secrets, overly permissive permissions, weak firewall rules, and plaintext user data — are all preventable with proper security practices.
Key takeaways:
- Never hardcode secrets in XML or any config file bundled with the app
- Validate all configuration values at runtime (timeout ranges, user ID uniqueness, rule validity)
- Minimize permissions — only declare what is strictly necessary
- Restrict firewall rules to specific IPs and ports, not entire subnets
- Disable beta features in production builds
References:
Top comments (0)