DEV Community

yal41n
yal41n

Posted on

Mobile Security Analysis: XML Configuration File Risks

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

Problems:

  • Rule order matters. The deny rule for 0.0.0.0/0 (all traffic) is last, which is correct for a default-deny policy. However, 192.168.1.0/24 allows 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/24 are 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>
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Never hardcode secrets in XML or any config file bundled with the app
  2. Validate all configuration values at runtime (timeout ranges, user ID uniqueness, rule validity)
  3. Minimize permissions — only declare what is strictly necessary
  4. Restrict firewall rules to specific IPs and ports, not entire subnets
  5. Disable beta features in production builds

References:

Top comments (0)