Files
basedbank/docs/AI_SECURITY_CHECK.md

22 KiB
Raw Blame History

BasedBank — AI Security Audit Report

Generated by: Claude Sonnet 4.6 Date: 2026-05-17 Scope: Full source audit — credential storage, lock screen, network layer, all outbound connections, data privacy, manifest, dependencies, backup rules. Context: This app is built from reverse-engineered protocol implementations of the official MIB (Faisanet), BML, and Fahipay Android apps. Any cryptographic weaknesses that are part of those upstream protocols are inherited constraints, not design flaws in this codebase.


Summary

Category Rating Notes
Credential storage at rest Excellent AES-256-GCM, Android Keystore
Lock screen hashing Excellent PBKDF2-HMAC-SHA256, 100k iterations
Screen capture protection Good FLAG_SECURE on by default
Backup exclusion Excellent All sensitive prefs excluded
Manifest attack surface Excellent Only MainActivity exported
Third-party data leakage Low No analytics SDKs; phone numbers sent to Ooredoo/Dhiraagu for validation
Certificate pinning Missing Biggest actionable gap
Brute-force resistance Weak 5 attempts / 30s — too short
Lockout bypass (rooted device) Possible Counter stored in plain prefs
Merchant icon URL fetch Medium Arbitrary URLs from server — no domain validation
MIB transport crypto Upstream constraint Blowfish/ECB is the MIB protocol
Code obfuscation N/A Open-source app, not applicable

1. All Outbound Connections

The following is the complete list of every server the app may contact. There is zero telemetry, analytics, or crash reporting.

MIB (Maldives Islamic Bank — Faisanet)

URL Purpose
https://faisanet.mib.com.mv/faisamobilex_smvc/ All MIB API calls (login, accounts, transfer, contacts)
https://faisamobilex-wv.mib.com.mv/ajaxAccounts/trxHistory MIB transaction history (WebView cookie auth)
https://faisamobilex-wv.mib.com.mv/personalProfile MIB profile page (HTML scrape)

BML (Bank of Maldives)

URL Purpose
https://www.bankofmaldives.com.mv/internetbanking/web/login BML web login (session cookie acquisition)
https://www.bankofmaldives.com.mv/internetbanking/web/login/2fa BML 2FA / OTP submission
https://www.bankofmaldives.com.mv/internetbanking/web/profile BML profile (sets blaze_identity cookie)
https://www.bankofmaldives.com.mv/internetbanking/oauth/authorize PKCE OAuth authorization
https://www.bankofmaldives.com.mv/internetbanking/oauth/token OAuth token exchange
https://www.bankofmaldives.com.mv/internetbanking/api/mobile/dashboard BML accounts & balances
https://www.bankofmaldives.com.mv/internetbanking/api/mobile/contacts BML contacts (read / add / delete)
https://www.bankofmaldives.com.mv/internetbanking/api/mobile/transfer BML funds transfer
https://www.bankofmaldives.com.mv/internetbanking/api/mobile/account/{id}/history/{page} BML CASA history
https://www.bankofmaldives.com.mv/internetbanking/api/mobile/card/statement BML prepaid card statement
https://www.bankofmaldives.com.mv/internetbanking/api/mobile/validate/account/{input} BML account validation
https://www.bankofmaldives.com.mv/internetbanking/api/mobile/favara/account-verification/{acct}/MIB BML→MIB cross-bank verification
https://www.bankofmaldives.com.mv/internetbanking/api/mobile/userinfo BML user info
https://app.bankofmaldives.com.mv/api/v2/foreign-limits BML foreign spending limits

Fahipay

URL Purpose
https://fahipay.mv/api/app/lang/data/ Session init (seeds __Secure-sess cookie)
https://fahipay.mv/api/app/login/ Fahipay login
https://fahipay.mv/api/app/otp/ Fahipay TOTP verification
https://fahipay.mv/actions/getprofile/?lang=en Fahipay user profile
https://fahipay.mv/actions/getbalance/?lang=en Fahipay wallet balance
https://fahipay.mv/actions/activity/?s={offset}&l=15&lang=en Fahipay transaction history
https://fahipay.mv/api/app/favs/?page={group}&lang=en Fahipay saved favourites (contacts)
Arbitrary merchant icon URLs Transaction icons returned in Fahipay activity response (see §5)

Telecom Operators (transfer helper validation)

URL Purpose Data sent
https://www.ooredoo.mv/ooredoo-prod/QuickPayPackage/v1/numberTypeValidation?action=cust_details&msisdn=960{number} Validates Ooredoo number type (pre/post/hybrid) 7-digit phone number
https://www.dhiraagu.com.mv/services/easy-pay Fetches Dhiraagu nonce (HTML) None
https://www.dhiraagu.com.mv/api/sdk-dhr-webapi.ashx?website_id=CA2BB809-...&sub=dhiraaguIO&act=infoUnlisted Validates Dhiraagu number, returns owner name 7-digit phone number

2. Credential Storage

File: app/src/main/java/sh/sar/basedbank/util/CredentialStore.kt

All credentials, session tokens, OTP seeds, and PII are encrypted before being written to SharedPreferences using AES-256-GCM with a key generated and stored inside the Android Keystore (hardware-backed on supported devices). The key never leaves the secure enclave.

Encrypted data:

  • MIB username, password hash, OTP seed, session keys (key1/key2), app ID
  • BML username, password, OTP seed, access token, device ID
  • Fahipay ID card, password, authId, session cookie (__Secure-sess), device UUID
  • PIN/pattern PBKDF2 hash + salt
  • All user profile PII (full name, email, mobile, NID, customer ID, birthdate)

The IV is randomly generated per encryption call and stored alongside the ciphertext (iv:ciphertext Base64 format), so each encrypted value is unique even with the same plaintext.

Cache encryption (CacheEncryption.kt) reuses the same AndroidKeyStore key for all cache SharedPreferences — consistent and correct.


3. Lock Screen

File: app/src/main/java/sh/sar/basedbank/LockActivity.kt File: app/src/main/java/sh/sar/basedbank/ui/onboarding/SecuritySetupFragment.kt

PIN / Pattern Hashing

  • Algorithm: PBKDF2WithHmacSHA256
  • Iterations: 100,000
  • Salt: 16 bytes, SecureRandom-generated, unique per setup
  • Output: 256-bit key stored encrypted in CredentialStore (AndroidKeyStore)
  • Password array is cleared via PBEKeySpec.clearPassword() after use

Legacy Migration

Old installs used raw SHA-256(salt + input) stored in plaintext SharedPreferences. On the first successful unlock, the app transparently migrates to PBKDF2 + CredentialStore and removes the legacy plaintext fields.

Biometrics

  • Uses androidx.biometric with BIOMETRIC_WEAK authenticators
  • Falls back to PIN/pattern if biometrics unavailable or cancelled

Brute-Force Lockout

  • Max attempts: 5
  • Lockout duration: 30 seconds
  • Attempt counter stored in lock_attempts SharedPreferences

Weakness: The lockout is weaker than ideal for two reasons:

  1. 30 seconds is very short — an attacker gets ~10 guesses/minute, ~600/hour.
  2. The lock_attempts.xml SharedPreferences file is not encrypted. On a rooted device, deleting this file resets the counter entirely, bypassing brute-force protection.

A stronger policy would use progressive delays (1 min, 5 min, 15 min, 1 hour) and store the counter inside CredentialStore.


4. Autolock

File: app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt

  • Autolock timer resets on every user interaction (onUserInteraction)
  • Warns the user 10 seconds before locking with a countdown dialog
  • On resume, checks elapsed pause time and locks immediately if the timeout was exceeded while the app was backgrounded
  • Default timeout: 60 seconds (configurable in Settings)

5. Screen Capture Protection

File: app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt:80

FLAG_SECURE is applied on HomeActivity creation, blocking screenshots and hiding app content in the recents/app-switcher. This is enabled by default and can be toggled off by the user in Settings.


6. Network Layer

MIB Transport Cryptography (Upstream Protocol Constraint)

File: app/src/main/java/sh/sar/basedbank/api/mib/MibCrypto.kt

The MIB Faisanet API uses Blowfish/ECB/PKCS5Padding for payload encryption. ECB mode does not use an IV, meaning identical plaintext blocks produce identical ciphertext blocks, and block patterns can be analysed. This is the protocol defined by MIB's own servers and official app — it is an upstream constraint inherited from the reverse-engineered protocol, not a choice made in this codebase.

The session key itself is derived via a Diffie-Hellman exchange (2048-bit+ modulus) with SHA-256 key derivation, which provides forward secrecy per session. The hardcoded DEFAULT_KEY is only used as the bootstrap key for the initial DH handshake; all subsequent requests use the DH-derived session key.

Certificate Pinning

File: app/src/main/res/xml/network_security_config.xml

The network security config is empty (<network-security-config/>), meaning the app relies on Android's default system CA trust store. There is no certificate pinning for any backend.

Risk: A device with a malicious or corporate CA certificate installed (e.g. MDM, proxy, or a compromised CA) could perform a man-in-the-middle attack against all API traffic. This is the most significant actionable security gap in the app.

Recommendation: Add <pin-set> entries for each banking domain's leaf or intermediate certificate in network_security_config.xml.

Merchant Icon Fetch — Arbitrary URL

Files: AccountHistoryFragment.kt:274295, TransferHistoryFragment.kt:298315

Fahipay transaction history responses include an iconUrl field containing a merchant logo URL. The app fetches these images directly using a bare OkHttpClient() with no domain restriction, no URL validation, and no TLS pinning. The URL comes from Fahipay's server.

Risks:

  1. Server-directed requests to third parties — If Fahipay's server is compromised or MITM'd, it can cause the app to make GET requests to arbitrary URLs. This is effectively a server-side request forgery at the client level.
  2. Merchant tracking by Fahipay — Fahipay (or any CDN serving the icons) can observe exactly which merchant URLs are fetched, and cross-reference this with user session data to build a detailed picture of spending patterns, timing, and even infer location from merchants.
  3. No HTTPS enforcement — A http:// icon URL would be fetched without the user's knowledge, bypassing TLS for that image fetch. Android's default usesCleartextTraffic is still true for these unconstrained clients.

The icon cache correctly writes to context.cacheDir which is excluded from backup, so cached icons don't leak into cloud storage.

User-Agent & Device Identity Impersonation

The app presents different identities to different backends, which is intentional for protocol compatibility:

Backend User-Agent sent
MIB API android/1.0
MIB WebView Mozilla/5.0 (Linux; Android 14; wv) AppleWebKit/537.36 ... Mobile Safari/537.36
BML web steps Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0
BML API calls bml-mobile-banking/345 (POCO; Android 14; 22101320I)
Fahipay login/OTP WebView UA with actual Build.MODEL
Fahipay API calls okhttp/4.12.0
Ooredoo No custom UA
Dhiraagu Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0

The BML API User-Agent hardcodes a specific device model (POCO; Android 14; 22101320I) to mimic the official BML mobile app. This is required for the API to accept requests.

The Fahipay login includes Build.MODEL and Build.MANUFACTURER from the actual device, sent as device[model] and device[manufacturer] form fields. This is a device fingerprint sent to Fahipay on every login.


7. Privacy — Phone Number Disclosure to Telecom Operators

Files: OoredooClient.kt, DhiraaguClient.kt

When a user enters a recipient phone number in the transfer flow, the app validates it by calling the respective telecom's public API:

  • The 7-digit number is sent to ooredoo.mv (as msisdn=960XXXXXXX)
  • The number is sent to dhiraagu.com.mv (in a JSON POST body)

These are unauthenticated public endpoints, but the requests disclose to each operator which numbers are being queried and when. This also means:

  • Ooredoo knows a BasedBank user is about to transact with a given Ooredoo number at a specific time.
  • Dhiraagu additionally returns the account owner's name (accountOwnerInfo.name), which is displayed in the UI to help the user confirm the recipient.

This is inherent to the feature (the Dhiraagu name lookup is useful for transfer safety), but users should be aware their query patterns are visible to the telecom.


8. Third-Party Data Leakage

No user data is sent to any advertiser, analytics service, or the developer.

Dependency Audit

Library Purpose Phones Home?
androidx.* UI / Lifecycle / Navigation No
com.google.android.material Material Design UI components No
com.squareup.okhttp3:okhttp HTTP client No
androidx.camera.* QR scanner camera No
com.github.markusfisch:zxing-cpp QR barcode decoding No
androidx.biometric Biometric authentication No
org.jetbrains.kotlinx:kotlinx-coroutines-android Coroutines No

No Firebase, no Crashlytics, no analytics SDKs, no ad networks.

Logging

There are zero android.util.Log calls anywhere in the codebase. Nothing is written to logcat. The okhttp3:logging-interceptor dependency has been removed.

TOTP

Totp.kt is a self-contained RFC 6238 implementation using only javax.crypto from the Android standard library. No third-party OTP SDK is used.


9. Backup & Data Extraction

Files: app/src/main/res/xml/backup_rules.xml, app/src/main/res/xml/data_extraction_rules.xml

Both the legacy backup rules (API ≤ 30) and the modern data extraction rules (API 31+) exclude all sensitive data from cloud backup and device-to-device transfer:

  • credential_store.xml
  • mib_prefs.xml
  • prefs.xml
  • account_cache.xml
  • contacts_cache.xml
  • financing_cache.xml
  • foreign_limits_cache.xml
  • recents_cache.xml
  • lock_attempts.xml
  • cache/ directory (covers merchant icons and contact image cache)

Note: even if backup were to occur, the AndroidKeyStore encryption key is hardware-bound and does not leave the device, so the encrypted data would be unreadable without the original device's secure hardware.

The manifest has android:allowBackup="true", but the exclusion rules above make the effective backup content empty of anything sensitive.


10. Manifest & Attack Surface

File: app/src/main/AndroidManifest.xml

Only MainActivity is android:exported="true" (required as the launcher entry point). All other activities (LockActivity, OnboardingActivity, LoginActivity, HomeActivity, QrScannerActivity) are exported="false" and cannot be launched by external apps.

The FileProvider is exported but with android:grantUriPermissions="true" scoped correctly — it cannot be accessed without an explicit URI grant.

Permissions

Permission Reason
INTERNET Banking API calls
CAMERA QR scanner
USE_BIOMETRIC Biometric unlock
ACCESS_NETWORK_STATE Network availability checks
WRITE_EXTERNAL_STORAGE Receipt saving, limited to API ≤ 28

No READ_CONTACTS, no READ_SMS, no RECEIVE_SMS, no location permissions.


11. Code Obfuscation

isMinifyEnabled = false in the release build config. This is not a security concern because BasedBank is an open-source project — the source code is already publicly available. Obfuscating the APK would provide no additional protection when the source is readable. The only benefit of enabling R8 would be APK size reduction via dead-code elimination.


12. Minor Code Issues

DhiraaguClient JSON String Interpolation

File: app/src/main/java/sh/sar/basedbank/api/dhiraagu/DhiraaguClient.kt:41

val body = """{"number":"$number"}""".toRequestBody(...)

The phone number is embedded directly into a JSON string via string interpolation rather than using a JSON builder. If number contained quotes or backslashes, it would produce malformed JSON. In practice, number is validated upstream to be a 7-digit numeric string, so the actual injection risk is negligible — but it is worth noting as a code hygiene issue.

MIB App ID Impersonates iOS

File: app/src/main/java/sh/sar/basedbank/api/mib/MibLoginFlow.kt:395

id = "IOS17.2-" + (1..15).map { ... }.joinToString("")

The app ID sent to MIB's servers is prefixed with IOS17.2- to match the format expected by the server. This is a protocol compatibility measure.


13. Hardcoded Values

All hardcoded values in this codebase are protocol constants extracted from reverse-engineering the official banking apps. There are no developer secrets, API keys owned by this project, or user credentials baked into the build.

Value File What it is Risk
DEFAULT_KEY = "8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678" MibCrypto.kt:12 Blowfish bootstrap key for MIB's initial DH handshake. Same key is in the official Faisanet APK. Not secret — server also knows it; only used for the key exchange handshake, not session traffic.
A = BigInteger("1563516802667...") MibCrypto.kt:14 MIB Diffie-Hellman private exponent (client side). Hardcoded, not generated fresh per session. See below.
P = BigInteger("2410312426921...") MibCrypto.kt:17 MIB DH prime modulus. Same value as in the official app. Public parameter — no risk.
CLIENT_ID = "98C83590-513F-4716-B02B-EC68B7D9E7E7" BmlLoginFlow.kt:30 BML OAuth client ID, extracted from the official BML mobile app APK. Not a personal secret — it is the same value for all BML mobile clients.
REDIRECT_URI = "https://app.bankofmaldives.com.mv/oauth/mobile-callback" BmlLoginFlow.kt:31 BML OAuth redirect URI, must match what BML's server expects. Fixed protocol value.
APP_USER_AGENT = "bml-mobile-banking/345 (POCO; Android 14; 22101320I)" BmlLoginFlow.kt:32 Impersonates the official BML app and a specific POCO device model. Intentional compatibility measure; no personal data.
APP_VERSION = "2.1.43.345" BmlLoginFlow.kt:33 BML app version string being impersonated. Fixed protocol value.
website_id = "CA2BB809-3A22-485B-A518-DA6B6DE653A5" DhiraaguClient.kt:45 Dhiraagu SDK identifier embedded in the lookup URL. Extracted from Dhiraagu's public Easy Pay page. Public value embedded in their web page; not a secret.
MIB_SWIFT_ON_BML = "F4E79935-3E73-E611-80DD-00155D020F0A" AddContactSheetFragment.kt:457 BML's internal bank code (SWIFT/FinInstnId) used to identify MIB as the counterpart bank during BML transfers. Protocol constant, not a secret.

MIB Hardcoded DH Private Key — Analysis

File: MibCrypto.kt:14-29

The Diffie-Hellman private exponent A is hardcoded as a constant rather than being generated fresh for each session. In a standard DH exchange:

  • The client generates a random private value a
  • Sends g^a mod P to the server
  • Receives g^b mod P from the server
  • Computes shared secret (g^b)^a mod P

With A hardcoded and published in source code:

  1. Anyone who captures network traffic containing smod (the server's g^b mod P value) can compute shared = BigInteger(smod).modPow(A, P) and fully derive the session key.
  2. This eliminates forward secrecy — captured past sessions remain decryptable by anyone with the hardcoded A.

However, this is again an upstream protocol constraint: the official Faisanet APK uses the same hardcoded DH exponent. The MIB server requires a specific cmod (g^A mod P) and would reject a freshly-generated one. Changing A would break compatibility with the server unless MIB updates their backend.

Net effect: The MIB DH exchange provides session isolation (each session gets a unique smod from the server), but not true forward secrecy because A is fixed and public. An attacker capturing traffic can decrypt any MIB session if they also capture the server's smod response from the handshake.


Findings Summary

Critical

None.

High

  • No certificate pinning — MITM possible on devices with untrusted CA certs installed. Affects all backends: MIB, BML, Fahipay, Ooredoo, Dhiraagu, and merchant icon URLs.

Medium

  • Merchant icon fetch — arbitrary server URLs — Fahipay can direct the app to fetch images from any URL. No domain restriction. Fahipay CDN can observe spending patterns via icon request timing.
  • Weak lockout policy — 30-second lockout after 5 attempts; no progressive backoff.
  • Lockout counter not encryptedlock_attempts.xml is plaintext; deletable on rooted devices.

Low / Informational

  • MIB DH private key hardcoded — forward secrecy is not achieved; captured sessions can be decrypted with the known A. Upstream protocol constraint.
  • Phone number disclosure to Ooredoo/Dhiraagu — Numbers queried for transfer validation are visible to each telecom operator.
  • Device fingerprint in Fahipay loginBuild.MODEL and Build.MANUFACTURER sent on every login.
  • BIOMETRIC_WEAK allows 2D face unlock on some devices, which may be spoofable with a photo.
  • MIB Blowfish/ECB — inherited upstream protocol weakness, not actionable without server-side changes.
  • DhiraaguClient JSON string interpolation — low real-world risk given numeric-only input validation upstream.
  • android:allowBackup="true" — flagged by automated scanners but effectively mitigated by the exclusion rules.