OWASP cho Mobile
Nói tới bảo mật mobile, gần như mọi yêu cầu từ khách hàng, ngân hàng hay đơn vị pentest đều dẫn về OWASP. Bài này bắt đầu bằng một ví dụ “soi” một app thật, sau đó mới đi vào các tài liệu của OWASP và cách áp dụng.
Ví dụ: soi nhanh bản build của app MyShop
Phần tiêu đề “Ví dụ: soi nhanh bản build của app MyShop”App MyShop (Flutter, có cả Android và iOS) sắp phát hành. Trước khi lên store, một bạn trong team thử đóng vai kẻ tấn công: tải file APK/IPA về, giải nén, đọc thử. Không cần công cụ phức tạp, chỉ trong một buổi chiều đã thấy 6 vấn đề.
1. API key nằm trong app
Phần tiêu đề “1. API key nằm trong app”unzip -o myshop.apk -d myshop_apkstrings myshop_apk/lib/arm64-v8a/libapp.so | grep -iE "sk_live|secret|api[_-]?key"# sk_live_51HxxxxxxxxxxxxxxxxxxxxxxxxTeam để secret key của cổng thanh toán trong code Dart để gọi thẳng API thanh toán từ app. Mọi thứ đóng gói trong app, kể cả code đã obfuscate hay giá trị truyền qua --dart-define, đều đọc được. Ai có key này có thể gọi API thanh toán dưới danh nghĩa của bạn.
Sửa: secret chỉ nằm ở server. App gọi server của mình, server mới gọi bên thứ ba. Key nào bắt buộc phải nằm trong app (Google Maps, Firebase…) thì giới hạn nó theo package name + SHA-1 / bundle ID và chỉ bật đúng API cần dùng.
→ OWASP M1: Improper Credential Usage.
2. Token đăng nhập lưu dạng văn bản thường
Phần tiêu đề “2. Token đăng nhập lưu dạng văn bản thường”// SAIfinal prefs = await SharedPreferences.getInstance();await prefs.setString('access_token', token);SharedPreferences trên Android là file XML, UserDefaults trên iOS là file plist, đều không mã hoá. Máy root/jailbreak, bản backup, hoặc một lỗ hổng khác đọc được file là lấy được token.
Sửa: dùng kho lưu trữ an toàn của hệ điều hành: Keychain trên iOS, Android Keystore (qua EncryptedSharedPreferences hoặc tự mã hoá bằng key trong Keystore) trên Android. Với Flutter là flutter_secure_storage:
const storage = FlutterSecureStorage();await storage.write(key: 'access_token', value: token);→ OWASP M9: Insecure Data Storage.
3. Log in ra token
Phần tiêu đề “3. Log in ra token”debugPrint('Login OK: ${response.data}'); // in cả access_token, refresh_tokenTrên Android, log của app có thể bị đọc qua adb logcat khi cắm máy; các công cụ báo lỗi (crash reporting) cũng có thể gửi log này về server bên thứ ba.
Sửa: không bao giờ log token, mật khẩu, thông tin cá nhân. Tắt log chi tiết của HTTP client ở bản release (ví dụ chỉ thêm interceptor log của Dio khi kDebugMode).
→ OWASP M6: Inadequate Privacy Controls, M9: Insecure Data Storage.
4. Bỏ qua kiểm tra chứng chỉ SSL “cho tiện test”
Phần tiêu đề “4. Bỏ qua kiểm tra chứng chỉ SSL “cho tiện test””// SAI: chấp nhận mọi chứng chỉ, để test với server staging dùng chứng chỉ tự kýHttpClient()..badCertificateCallback = (cert, host, port) => true;Đoạn này lọt vào bản release. Bất kỳ ai ở cùng mạng Wi-Fi quán cà phê có thể đứng giữa (man-in-the-middle) đọc và sửa toàn bộ dữ liệu.
Sửa: xoá hẳn. Muốn test với chứng chỉ riêng thì cấu hình theo nền tảng, chỉ cho bản debug: network_security_config.xml với <debug-overrides> trên Android; trên iOS cài chứng chỉ vào máy test. Đồng thời đảm bảo không còn http:// (Android cleartextTrafficPermitted="false", iOS không thêm ngoại lệ ATS).
→ OWASP M5: Insecure Communication.
5. API chỉ kiểm tra ở app
Phần tiêu đề “5. API chỉ kiểm tra ở app”Màn hình “Đơn hàng của tôi” gọi GET /orders/123. App chỉ hiện đơn của người đang đăng nhập, nhưng server không kiểm tra đơn 123 có thuộc người đó không. Đổi số trong request (bằng proxy như Burp Suite, mitmproxy) là xem được đơn của người khác.
Sửa: mọi kiểm tra quyền phải ở server. Ẩn nút, ẩn màn hình ở app chỉ là giao diện, không phải bảo mật.
→ OWASP M3: Insecure Authentication/Authorization.
6. Bản release vẫn để chế độ debug, deep link không kiểm tra dữ liệu
Phần tiêu đề “6. Bản release vẫn để chế độ debug, deep link không kiểm tra dữ liệu”- Android build release vẫn còn
android:debuggable="true"do cấu hình nhầm build type;android:allowBackup="true"mặc định khiến dữ liệu app nằm trong bản backup. - Một
Activitykhông cần thiết cóandroid:exported="true", app khác gọi thẳng vào được. - Deep link
myshop://pay?amount=...&to=...mở thẳng màn hình chuyển tiền đã điền sẵn và tự xác nhận.
Sửa: rà AndroidManifest.xml của bản release (xem file manifest đã merge trong Android Studio), chỉ export đúng những gì cần, tắt backup hoặc cấu hình loại trừ dữ liệu nhạy cảm. Dữ liệu từ link luôn là dữ liệu không tin cậy (xem phần lưu ý trong bài Universal Links và App Links).
→ OWASP M8: Security Misconfiguration, M4: Insufficient Input/Output Validation.
Sáu lỗi trên đều có trong danh sách OWASP Mobile Top 10. Phần còn lại của bài giải thích danh sách này và các tài liệu đi kèm.
OWASP và bộ tài liệu cho mobile
Phần tiêu đề “OWASP và bộ tài liệu cho mobile”OWASP (Open Worldwide Application Security Project) là tổ chức phi lợi nhuận về bảo mật phần mềm, nổi tiếng với các danh sách “Top 10”. Với mobile, OWASP có hai dự án:
| Tài liệu | Là gì | Dùng khi nào |
|---|---|---|
| Mobile Top 10 | Danh sách 10 nhóm rủi ro phổ biến nhất | Nắm tổng quan, đào tạo team, nói chuyện với người không chuyên |
| MASVS (Mobile Application Security Verification Standard) | Tiêu chuẩn: danh sách yêu cầu (control) app cần đạt | Làm yêu cầu bảo mật cho dự án, tiêu chí nghiệm thu |
| MASWE (Mobile Application Security Weakness Enumeration) | Danh sách các điểm yếu cụ thể, nối control của MASVS với bài test | Tra cứu một điểm yếu cụ thể |
| MASTG (Mobile Application Security Testing Guide) | Hướng dẫn kiểm thử chi tiết: kỹ thuật, công cụ, bài test cho từng control | Pentest, tự kiểm tra app |
Ba tài liệu MASVS, MASWE, MASTG thuộc dự án OWASP MAS (Mobile Application Security) và liên kết với nhau: mỗi bài test trong MASTG gắn với một điểm yếu trong MASWE, mỗi điểm yếu gắn với một control trong MASVS.
Top 10 là “biết mình hay sai ở đâu”, MASVS là “phải đạt những gì”, MASTG là “kiểm tra bằng cách nào”.
OWASP Mobile Top 10 (2024)
Phần tiêu đề “OWASP Mobile Top 10 (2024)”Bản 2024 là lần cập nhật đầu tiên kể từ 2016.
| # | Rủi ro | Ý chính với mobile |
|---|---|---|
| M1 | Improper Credential Usage | Secret, API key, mật khẩu nằm trong app hoặc dùng sai cách |
| M2 | Inadequate Supply Chain Security | Thư viện, SDK bên thứ ba có lỗ hổng hoặc độc hại; quy trình build bị xâm nhập |
| M3 | Insecure Authentication/Authorization | Xác thực yếu, kiểm tra quyền chỉ ở app, session không hết hạn |
| M4 | Insufficient Input/Output Validation | Dữ liệu từ deep link, WebView, file, server… không được kiểm tra |
| M5 | Insecure Communication | HTTP thường, bỏ qua kiểm tra chứng chỉ, gửi dữ liệu nhạy cảm qua kênh không an toàn |
| M6 | Inadequate Privacy Controls | Thu thập hoặc làm lộ dữ liệu cá nhân quá mức cần |
| M7 | Insufficient Binary Protections | App dễ bị dịch ngược, sửa đổi, đóng gói lại |
| M8 | Security Misconfiguration | Cấu hình sai: debug, backup, component exported, quyền thừa |
| M9 | Insecure Data Storage | Lưu dữ liệu nhạy cảm không mã hoá, lộ qua log, cache, backup |
| M10 | Insufficient Cryptography | Thuật toán yếu, tự chế mã hoá, quản lý key sai |
So với 2016: M1 và M2 là nhóm mới; M7 gộp hai nhóm cũ “Reverse Engineering” và “Code Tampering”; M8 thay nhóm “Extraneous Functionality”.
Dưới đây là cách hiểu và cách phòng tránh từng nhóm trên Android, iOS và Flutter.
M1: Improper Credential Usage
Phần tiêu đề “M1: Improper Credential Usage”- Nguyên tắc: coi như mọi thứ trong app đều bị đọc được. Obfuscation, mã hoá chuỗi, giấu trong native code chỉ làm chậm kẻ tấn công, không ngăn được.
- Secret của server (khoá thanh toán, khoá quản trị, service account) không bao giờ đưa vào app.
- Không lưu mật khẩu người dùng; lưu token và làm mới bằng refresh token.
- Với Flutter:
--dart-definevà file.envđóng gói vào app đều không phải nơi giữ bí mật.
M2: Inadequate Supply Chain Security
Phần tiêu đề “M2: Inadequate Supply Chain Security”- Chỉ dùng package/SDK có nguồn gốc rõ ràng, còn được bảo trì. Trên pub.dev xem publisher đã xác minh, điểm, ngày cập nhật.
- Khoá phiên bản (commit file
pubspec.lock,Podfile.lock, Gradle lockfile hoặc version catalog), cập nhật có kiểm soát. - Theo dõi lỗ hổng của dependency (Dependabot, OSV,
flutter pub outdated). - Bảo vệ quy trình build: quyền truy cập CI, secret ký app (xem bài Google Play App Signing và Certificate và Provisioning Profile).
- SDK bên thứ ba thu thập gì thì app của bạn chịu trách nhiệm: cần khai báo trong Privacy Manifest (iOS) và Data safety (Google Play).
M3: Insecure Authentication/Authorization
Phần tiêu đề “M3: Insecure Authentication/Authorization”- Mọi kiểm tra quyền ở server.
- Token ngắn hạn, có refresh token, thu hồi được khi đăng xuất hoặc đổi mật khẩu.
- Đăng nhập bằng sinh trắc học: dùng sinh trắc học để mở khoá một key trong Keystore/Keychain (Android
BiometricPromptvớiCryptoObject, iOS Keychain vớiSecAccessControl), không chỉ dựa vào kết quảtrue/falsecủa hàm xác thực, vì kết quả đó dễ bị sửa trên máy đã root/jailbreak. - OAuth: dùng Authorization Code + PKCE, mở trang đăng nhập bằng trình duyệt hệ thống (Custom Tabs,
ASWebAuthenticationSession), không dùng WebView.
M4: Insufficient Input/Output Validation
Phần tiêu đề “M4: Insufficient Input/Output Validation”- Dữ liệu từ deep link, push notification, clipboard, file người dùng chọn, QR code, và cả response của server đều phải kiểm tra.
- WebView: tắt JavaScript nếu không cần; với
addJavascriptInterface(Android) /WKScriptMessageHandler(iOS) /JavaScriptChannel(Flutter) chỉ cho trang tin cậy gọi; không cho WebView truy cập file cục bộ. - Truy vấn SQLite dùng tham số, không nối chuỗi.
M5: Insecure Communication
Phần tiêu đề “M5: Insecure Communication”- Chỉ HTTPS. Android:
network_security_config.xmlvớicleartextTrafficPermitted="false"(mặc định từ Android 9). iOS: App Transport Security, không thêmNSAllowsArbitraryLoads. - Không bao giờ tắt kiểm tra chứng chỉ trong code.
- Certificate pinning giúp chống trường hợp kẻ tấn công cài được chứng chỉ giả vào máy, nhưng có chi phí vận hành: chứng chỉ server đổi mà app chưa cập nhật pin là app mất kết nối. Nếu pin, nên pin public key (không pin cả chứng chỉ), luôn có pin dự phòng, và có kế hoạch xoay vòng.
M6: Inadequate Privacy Controls
Phần tiêu đề “M6: Inadequate Privacy Controls”- Chỉ xin quyền khi cần, đúng lúc cần, và giải thích lý do.
- Không gửi dữ liệu cá nhân (email, số điện thoại, vị trí) vào analytics, log, crash report.
- Ẩn nội dung nhạy cảm khi app vào background (ảnh chụp màn hình trong trình chuyển app): Android
FLAG_SECURE, iOS che màn hình khisceneWillResignActive. - Khai báo trung thực trong Privacy Nutrition Label (App Store), Data safety (Google Play).
M7: Insufficient Binary Protections
Phần tiêu đề “M7: Insufficient Binary Protections”- Bật thu gọn và làm rối code: Android R8 (
minifyEnabled true), Flutter--obfuscate --split-debug-info=<thư mục>. - Với app có giá trị cao (ngân hàng, game có giao dịch), cân nhắc phát hiện root/jailbreak, phát hiện debugger, hook (Frida), app bị đóng gói lại.
- Xác minh app và thiết bị từ phía server: Play Integrity API (Android), App Attest (iOS). Server mới là nơi ra quyết định, vì kiểm tra trong app luôn có thể bị vượt qua.
- Tất cả các lớp bảo vệ này làm chậm kẻ tấn công chứ không chặn được hẳn. Không dùng chúng thay cho việc không đưa secret vào app (M1) và kiểm tra quyền ở server (M3).
M8: Security Misconfiguration
Phần tiêu đề “M8: Security Misconfiguration”Rà soát bản release:
- Android: không
debuggable;allowBackuphoặcdataExtractionRulesloại trừ dữ liệu nhạy cảm; chỉexported="true"cho component thật sự cần;ContentProvidercó permission; không xin quyền thừa. - iOS: không để lại cấu hình ATS nới lỏng, URL scheme, entitlement không dùng.
- Server/Backend-as-a-Service: Firebase Realtime Database/Firestore/Storage có security rules đúng (không để
allow read, write: if true). - Tắt màn hình debug, menu ẩn, endpoint test trong bản release.
M9: Insecure Data Storage
Phần tiêu đề “M9: Insecure Data Storage”- Dữ liệu nhạy cảm (token, khoá) lưu trong Keychain/Keystore. Dữ liệu lớn cần bảo vệ thì mã hoá bằng key nằm trong Keychain/Keystore (ví dụ SQLCipher).
- Không ghi dữ liệu nhạy cảm ra bộ nhớ ngoài (external storage) trên Android.
- Để ý các nơi “rò rỉ”: log, cache HTTP, cache bàn phím (tắt gợi ý cho ô mật khẩu), clipboard, ảnh chụp màn hình app switcher, file tạm.
- iOS: dùng Data Protection (
NSFileProtectionComplete) cho file nhạy cảm; chọn mứckSecAttrAccessiblephù hợp cho Keychain (ví dụ...ThisDeviceOnlyđể không đi theo bản backup sang máy khác).
M10: Insufficient Cryptography
Phần tiêu đề “M10: Insufficient Cryptography”- Không tự chế thuật toán. Dùng thư viện chuẩn của nền tảng (Android Keystore +
javax.crypto, iOS CryptoKit). - Không dùng MD5, SHA-1 cho mục đích bảo mật; không dùng AES ở chế độ ECB; dùng AES-GCM.
- Key không hardcode trong app, sinh và giữ trong Keystore/Keychain (có phần cứng bảo vệ như StrongBox, Secure Enclave khi có).
- Về lâu dài, để ý xu hướng mật mã hậu lượng tử (ví dụ Android 17 đã hỗ trợ ML-DSA trong Keystore, xem phần quantum-ready key trong bài Google Play App Signing).
MASVS: tiêu chuẩn để làm yêu cầu dự án
Phần tiêu đề “MASVS: tiêu chuẩn để làm yêu cầu dự án”Top 10 tốt để nhận thức, nhưng khi cần tiêu chí nghiệm thu (hợp đồng với khách hàng, yêu cầu của ngân hàng, kiểm định), nên dùng MASVS. Bản hiện tại là MASVS v2.1.0, gồm 8 nhóm control:
| Nhóm | Nội dung |
|---|---|
| MASVS-STORAGE | Lưu trữ an toàn dữ liệu nhạy cảm |
| MASVS-CRYPTO | Dùng mật mã đúng cách |
| MASVS-AUTH | Xác thực và phân quyền |
| MASVS-NETWORK | Giao tiếp mạng an toàn |
| MASVS-PLATFORM | Tương tác an toàn với nền tảng (IPC, WebView, UI) |
| MASVS-CODE | Chất lượng code: cập nhật, dependency, kiểm tra dữ liệu đầu vào |
| MASVS-RESILIENCE | Chống dịch ngược, sửa đổi app |
| MASVS-PRIVACY | Quyền riêng tư người dùng (thêm từ v2.1.0) |
MASVS v2 bỏ các “level” cũ (L1, L2, R) trong bản thân tiêu chuẩn. Thay vào đó, MASTG dùng testing profile để chọn mức kiểm thử phù hợp:
- MAS-L1: mức cơ bản, cho hầu hết app.
- MAS-L2: cho app xử lý dữ liệu nhạy cảm (ngân hàng, y tế), cần phòng thủ nhiều lớp.
- MAS-R: chống dịch ngược và sửa đổi, cho app cần bảo vệ tài sản trí tuệ hoặc chống gian lận (game, DRM).
- MAS-P: các bài test về quyền riêng tư.
Cách dùng thực tế: chọn profile theo loại app, lấy danh sách control tương ứng làm checklist từ đầu dự án, không đợi tới lúc pentest mới đối chiếu.
Tự kiểm tra app
Phần tiêu đề “Tự kiểm tra app”Một số công cụ thường dùng để tự soi app của mình trước khi gửi pentest (chỉ dùng trên app của mình hoặc app được phép kiểm thử):
| Công cụ | Dùng để |
|---|---|
| MobSF | Quét tĩnh APK/IPA tự động: quyền, cấu hình manifest/plist, secret trong code, thư viện |
| jadx | Dịch ngược APK sang Java/Kotlin để đọc |
| apktool | Giải nén APK, đọc manifest và resource đã biên dịch |
| Burp Suite, mitmproxy | Proxy chặn và sửa request giữa app và server |
| Frida, objection | Hook app lúc chạy, thử vượt qua các lớp bảo vệ |
| Android Lint, các lint bảo mật | Bắt lỗi cấu hình ngay khi code |
Với Flutter: code Dart biên dịch thành mã máy trong libapp.so (Android) và App.framework (iOS) nên jadx không đọc được logic Dart, nhưng strings vẫn lộ chuỗi, và đã có công cụ chuyên phân tích file snapshot của Dart. Đừng coi việc “khó dịch ngược” là lớp bảo vệ.
Checklist ngắn cho team
Phần tiêu đề “Checklist ngắn cho team”Trước mỗi lần phát hành:
- Không có secret của server trong app (quét
strings, MobSF). - Token lưu trong Keychain/Keystore, không trong SharedPreferences/UserDefaults.
- Không log token, mật khẩu, dữ liệu cá nhân ở bản release.
- Không còn code bỏ qua kiểm tra chứng chỉ; không cho phép HTTP thường.
- Manifest release: không debuggable, backup đã cấu hình, chỉ export component cần thiết.
- Dữ liệu từ deep link, WebView, push đều được kiểm tra; hành động nhạy cảm cần người dùng xác nhận.
- Đã bật R8/obfuscate và lưu file mapping/symbol để đọc crash.
- Đã cập nhật dependency có lỗ hổng đã biết.
- Khai báo quyền riêng tư (Privacy Manifest, Data safety) khớp với những gì app và SDK thu thập.
- Phía server: kiểm tra quyền trên từng API, security rules của Firebase đúng.