Bảo mật ứng dụng Flutter
Đôi lời
Bảo mật ứng dụng di động là một thách thức mà các nhà phát triển sẽ phải đối mặt ít nhất một lần trong đời. Không phải lúc nào cũng đảm bảo được rằng mọi thứ trong ứng dụng được bảo mật tuyệt đối nếu chúng ta chưa bao giờ có cơ hội tìm hiểu sâu về bảo mật cấp thấp hoặc ứng dụng chưa từng được tấn công/kiểm thử bởi bên khác.
Quokka cũng đã từng như thế, phát triển ứng dụng nhưng rất ít khi áp dụng các biện pháp bảo mật. Nhưng sau quá trình làm việc cật lực với hệ thống bảo mật lớn như Ngân hàng, và passed khá thành công một số thách thức từ đội Pentest, Quokka quyết định ở đây tổng hợp các biện pháp bảo mật cơ bản của ứng dụng di động với hy vọng anh em có thể dễ dàng hiểu hơn về chúng.
Trong bài viết này, Quokka sẽ liệt kê 10 rủi ro hàng đầu trong ứng dụng di động được lấy từ OWASP và đối với mỗi rủi ro đó, Quokka sẽ cung cấp một số thông tin về cách giải quyết chúng trong ứng dụng Flutter của bạn. Hãy chú ý ở mục M8 với lỗ hổng Restricted Settings nhé!
Đối với những người không biết OWASP là gì: Open Worldwide Application Security Project® (OWASP) là một tổ chức phi lợi nhuận hoạt động nhằm cải thiện tính bảo mật của phần mềm…. Đó là nguồn để các nhà phát triển và nhà công nghệ bảo mật hệ thống của họ.
10 lỗ hổng hàng đầu
🛠️ Improper Platform Usage
Việc sử dụng sai tính năng hoặc không kiểm soát bảo mật tốt của platform dùng trong ứng dụng gây ra những rủi ro không đáng có. Nó có thể bao gồm việc sử dụng sai:
- Platform permissions
- Touch/Face ID
- Keychain
- Android intents
- Một số biện pháp kiểm soát bảo mật khác của hệ điều hành di động.
Phải làm gì:
- Chỉ yêu cầu người dùng những quyền thực sự cần: không thêm những quyền mà nó không hề liên quan (hoặc không cần thiết phải sử dụng) mà chỉ yêu cầu những quyền thực sự cần thiết cho các chức năng trong ứng dụng của bạn.
- Đừng chỉ dùng Touch/Face ID với Local authentication: trong một số trường hợp cơ bản, chỉ cần Local Authentication là đủ và bạn có thể sử dụng local_auth để triển khai. Nhưng nếu muốn mức độ bảo mật cao hơn khi xác thực bằng sinh trắc học, bạn nên triển khai quy trình xác minh dựa trên khóa bất đối xứng (chi tiết hơn tại đây). Bạn có thể dùng một vài packages như flutter_biometrics hoặc biometricx.
- Sử dụng KeyChain/Keystore để lưu trữ dữ liệu nhạy cảm: đây là cách lưu an toàn cho cả dữ liệu ứng dụng và dữ liệu từ hệ thống. Ứng dụng nên sử dụng chúng để lưu trữ mọi dữ liệu nhảy cảm, có ý nghĩa bảo mật (session, passwords, dữ liệu đăng ký thiết bị, v.v.). ĐỪNG lưu trữ các dữ liệu nhạy cảm đó trong bộ nhớ cục bộ của ứng dụng. Bạn có thể sử dụng flutter_secure_storage để lưu trữ dữ liệu trong bộ lưu trữ an toàn. Keychain được sử dụng trong iOS và giải pháp dựa trên KeyStore được sử dụng trong Android.
- Luôn cập nhật: các lỗ hổng mới luôn được tìm ra mỗi ngày. Bạn phải luôn đảm bảo sử dụng phiên bản stable mới nhất của Flutter, phiên bản mới nhất trong thư viện của mình và thường xuyên kiểm tra các nguyên tắc bảo mật của nhóm Flutter và cộng đồng phát triển di động nói chung. Ngoài ra, việc tăng min SDK level lên cao hơn sẽ giúp ứng dụng có được mức đảm bảo tốt hơn về mặt bảo mật, nhưng đổi lại điều này sẽ khiển một số phiên bản hệ điều hành cũ hơn không tương thích với ứng dụng của bạn.
📦 M2: Insecure Data Storage
Lỗ hổng Insecure Data Storage xảy ra khi nhóm phát triển cho rằng người dùng hoặc phần mềm độc hại sẽ không có quyền truy cập vào hệ thống tệp của thiết bị di động, từ đó lưu những dữ liệu nhạy cảm ngay trên thiết bị. Việc chủ quan này có thể dẫn đến mất dữ liệu, trong trường hợp tốt nhất là một người dùng và trong trường hợp xấu nhất là nhiều người dùng.
Phải làm gì:
- Sử dụng KeyChain/Keystore để lưu trữ dữ liệu nhạy cảm: như đã đề cập ở phần trước, KeyChain/Keystore nên được sử dụng để lưu trữ một lượng nhỏ thông tin nhạy cảm.
- Chỉ lưu trữ dữ liệu nếu thực sự cần thiết: bạn chỉ nên lưu trữ dữ liệu nếu chúng là "chìa khóa", là phần không thể thiếu cho các chức năng của ứng dụng.
- Xóa dữ liệu nếu chúng chỉ được lưu trữ cho mục đích tạm thời hoặc bộ nhớ đệm: khi bạn xác định được rằng dữ liệu bạn đang lưu trữ là không cần thiết. Sau khi thực hiện một thao tác, hãy đảm bảo rằng bạn cũng xóa chúng.
- Mã hóa dữ liệu: nếu bạn thực sự cần lưu trữ dữ liệu người dùng trên thiết bị thì hãy đảm bảo rằng những dữ liệu đó đã được mã hóa, đặc biệt nếu chúng là dữ liệu cá nhân. hive là một giải pháp tốt cho bạn.
- Ẩn nội dung nhạy cảm trên chế độ xem đa nhiệm: nếu ứng dụng của bạn hiển thị thông tin nhạy cảm trên các giao diện màn hình, bạn cũng nên giấu những dữ liệu đó khỏi những con mắt tò mò khi ở chế độ đa nhiệm, bằng cách sử dụng thứ gì đó như safe_application.
☎️ M3: Insecure Communication
Khi xây dựng ứng dụng di động, dữ liệu thường được trao đổi theo kiểu Client-Server. Lúc dữ liệu được gửi đi, nó được truyền qua mạng của nhà cung cấp dịch vụ di động và internet. Những kẻ tấn công có thể khai thác các lỗ hổng, bắt dữ liệu nhạy cảm khi nó được truyền tải qua mạng.
Phải làm gì:
- Sử dụng chứng chỉ SSL/TLS: điều này là hiển nhiên đối với một hệ thống an toàn. Bạn phải áp dụng SSL/TLS để truyền tải các kênh mà ứng dụng di động sẽ sử dụng để truyền thông tin nhạy cảm (session token hoặc dữ liệu nhạy cảm khác,...) tới Backend API hoặc Web service. Bạn cũng nên tính đến các Entities bên ngoài (như dịch vụ phân tích bên thứ ba, mạng xã hội,...) bằng cách sử dụng phiên bản SSL của họ khi một ứng dụng chạy thông qua browser/webkit. Tránh dùng các SSL sessions trộn lẫn vì chúng có thể làm lộ session ID của người dùng.
- Sử dụng tính năng Certificate pinning:: việc ghim chứng chỉ sẽ chỉ cho phép các chứng chỉ từ client hợp lệ truy cập vào một trang web, hạn chế rủi ro. Nó có thể triển khai trong ứng dụng Flutter bằng cách sử dụng http_certificate_pinning.
- Mã hoá dữ liệu nhạy cảm trước khi gửi cho kênh SSL: biện pháp này phòng trường hợp có lỗ hổng trong quá trình triển khai SSL, dữ liệu được mã hóa sẽ là biện pháp bảo vệ tốt để chống lại việc dữ liệu nhạy cảm bị đánh cắp.
👤 M4: Insecure Authentication
Các cơ chế xác thực kém hoặc bị thiếu cho phép kẻ tấn công thực thi các chức năng ẩn trong ứng dụng di động hoặc backend server. Trong ứng dụng di động, người dùng không phải lúc nào cũng online trong phiên của họ. Kết nối internet di động kém tin cậy hơn so với kết nối web. Do đó, ứng dụng dành cho thiết bị di động có thể có giới hạn thời gian, yêu cầu xác thực ngoại tuyến.
Phải làm gì:
- Không chỉ dùng xác thực cục bộ: xác thực người dùng cục bộ có thể dễ dàng bị bẻ khoá trên các thiết bị đã jailbroken thông qua các thao tác khi run-time hoặc sửa đổi tệp nhị phân. Vấn đề này cũng liên quan đến việc lạm dụng xác thực sinh trắc học được nêu trong phần M1.
- Tránh sử dụng các phương thức xác thực dễ bị tấn công: bạn không nên lưu trữ mật khẩu cục bộ hoặc mã PIN trong bộ nhớ ứng dụng, luôn cập nhật các nguyên tắc mới nhất về phương thức xác thực và triển khai phương thức phù hợp nhất với nhu cầu của bạn.
🔐 M5: Insufficient Cryptography
Việc sử dụng thuật toán mã hoá không an toàn thường xuyên xảy ra trong nhiều ứng dụng di động. Có hai cách cơ bản mà mã hoá không an toàn được thể hiện trong các ứng dụng di động.
Đầu tiên, ứng dụng di động triển khai quy trình mã hóa/giải mã có sai sót và bị kẻ tấn công khai thác để giải mã dữ liệu nhạy cảm.
Thứ hai, ứng dụng di động triển khai hoặc tận dụng thuật toán mã hóa/giải mã có bản chất yếu và bị kẻ tấn công giải mã trực tiếp.
Phải làm gì:
- Tránh sử dụng các thuật toán không an toàn hoặc không được dùng nữa: nhiều thuật toán và giao thức mã hóa không nên được sử dụng vì chúng đã được chứng minh là có những điểm yếu hoặc không đủ đáp ứng các yêu cầu bảo mật hiện đại. Bao gồm: RC2, MD4, MD5, SHA1. Một số thuật toán tốt bạn có thể sử dụng thay thế là AES, Fernet, Salsa. Bạn có thể quan tâm đến việc sử dụng các gói như encrypt và crypto.
- Bảo mật developer identity: mọi dữ liệu có thể làm lộ developer identity đều phải được mã hóa. Trên Android, hãy mã hóa các tệp nhạy cảm như
key.jks
&keystore.properties
bằng GPG. Tránh lưu dữ liệu nhạy cảm không được mã hóa trong kho lưu trữ.
✋ M6: Insecure Authorisation
Phòng trường hợp bạn chưa phân biệt được Authentication và Authorization:
- Authentication (Xác thực) là nhận dạng một cá nhân.
- Authorization (Uỷ quyền) là kiểm tra xem cá nhân được xác định có các quyền cần thiết để thực hiện một hành động cụ thể hay không.
Nếu việc ủy quyền không được xử lý đúng cách, người dùng (ẩn danh hoặc đã được xác thực) có thể thực hiện các chức năng có quyền cao hơn.
Phải làm gì:
- Chỉ xác minh roles và permissions của người dùng đã được xác thực bằng thông tin được trả về từ phía Backend: tránh dựa vào bất kỳ roles hoặc thông tin permissions nào từ thiết bị di động.
- Xử lý các request chưa được xác thực: Ví dụ khi server trả về 401, nghĩa là thông tin xác thực mà chúng ta đang sử dụng không còn hợp lệ nữa. Khi đó hãy đảm bảo điều này được truyền đạt chính xác tới người dùng và các phiên hoạt động sẽ bị chấm dứt.
🚮 M7: Poor Code Quality
Rủi ro đến từ việc sử dụng sai API, sử dụng API không an toàn, sử dụng cấu trúc ngôn ngữ không an toàn hoặc một số vấn đề ở code-level khác. Quan trọng đây không phải là code trên máy chủ, mà nó là chất lượng code trên chính thiết bị di động.
Phải làm gì:
- Maintain code của dự án theo các nguyên tắc đã được thống nhất: bạn có thể tận dụng
lints
để triển khai một bộ đề xuất gợi ý khi phát triển ứng dụng Flutter. Sử dụng flutter_lints, bạn cũng có thể tự chỉnh sửa lints theo nguyên tắc của team bạn. - Tích hợp phân tích source code: nên tích hợp việc phân tích mã nguồn trong quá trình phát triển, áp dụng nó vào quy trình CI/CD của bạn càng tốt.
- Tự động xác định các lỗ hổng: tự động hóa có thể giúp xác định lỗi tràn bộ đệm và rò rỉ bộ nhớ thông qua việc sử dụng các công cụ phân tích của bên thứ ba.
- Kiểm tra chất lượng code trước khi tích hợp thư viện của bên thứ 3: đây là một việc khó vì chúng ta thường có xu hướng tin tưởng tuyệt đối vào thư viện của bên thứ 3. Thay vào đó, điều chúng ta nên làm (nếu có thể) là kiểm tra xem các thư viện đó có tuân thủ các nguyên tắc bảo mật cập nhật mới nhất hay không. Sau khi được tích hợp, về cơ bản chúng là một phần mã nguồn của bạn. Vì vậy, chúng phải tuân theo các tiêu chuẩn bảo mật tương tự như mã nguồn mà bạn đã triển khai. Các công cụ như snyk.io rất hữu ích để giảm rủi ro liên quan đến việc sử dụng thư viện của bên thứ 3.
🥷 M8: Code Tampering
Các tổ chức/cá nhân phát triển ứng dụng di động không thể kiểm soát hoàn toàn việc sản phẩm của họ hoạt động trong môi trường nào. Thực tế có rất nhiều cách khác nhau để thay đổi môi trường mà ứng dụng hoạt động. Những thay đổi này cho phép kẻ tấn công có thể sửa đổi mã nguồn của ứng dụng.
Mặc dù mã nguồn ứng dụng di động dễ bị tác động, nhưng điều quan trọng bạn có muốn phát hiện và ngăn chặn việc sửa đổi mã nguồn trái phép hay không. Các ứng dụng được viết cho một số ngành kinh doanh (như game) dễ bị sửa đổi mã nguồn hơn so với các ứng dụng khác (ví dụ như ứng dụng dịch vụ khách sạn). Do đó, điều quan trọng là phải xem xét tác động đối với việc kinh doanh sản phẩm trước khi quyết định có giải quyết rủi ro này hay không.
Phải làm gì:
- Thêm tính năng phát hiện root/jailbreak: thông thường, một ứng dụng đã được sửa đổi mã nguồn sẽ hoạt động trong môi trường đã Jailbreak hoặc Root. Do đó, việc thử phát hiện môi trường bị sửa đổi trong thời gian ứng dụng chạy và đưa ra những phản ứng tương ứng là điều hợp lý (gửi báo cáo lên server hoặc bắt buộc tắt ứng dụng). Để giảm thiểu rủi ro và ngăn việc sử dụng ứng dụng trên thiết bị bị xâm nhập, bạn có thể sử dụng flutter_jailbreak_detection.
- Kiểm tra Accessibility và Notification Listener trên Android: Sự nguy hiểm khi điện thoại bị chiếm quyền Accessibility và Notification Listener đã được cảnh báo từ lâu. Do đó, tính năng an ninh Cài đặt hạn chế (Restricted Settings) đã được giới thiệu từ Android 13 nhằm ngăn các ứng dụng đến từ bên ngoài Google Play truy cập vào Accessibility và Notification Listener. Tuy nhiên gần đây, một loại mã độc mới có tên SecuriDropper có thể xâm nhập cả những điện thoại mới nhất của năm 2023. Thậm chí người dùng đã nâng cấp lên Android 14 cũng sẽ vẫn bị mã độc tấn công theo phương thức này. Do đó, với những ứng dụng cần sự bảo mật cao như Giao dịch ngân hàng, cần có những biện pháp nghiệp vụ kết hợp để đối phó như kiểm tra nguồn cài của ứng dụng, kiểm tra các ứng dụng có quyền Accessibility và Notification Listener trên thiết bị, kiểm tra các quyền được cấp trong Accessibility là có an toàn không...
🏗️ M9: Reverse Engineering
Một ứng dụng được coi là dễ bị tấn công bằng kỹ thuật dịch ngược nếu kẻ tấn công có thể thực hiện bất kỳ điều nào sau đây:
- Hiểu rõ nội dung bảng chuỗi nhị phân
- Thực hiện chính xác phân tích chức năng chéo
- Mã nguồn của ứng dụng có thể được tái tạo từ hệ nhị phân
Mặc dù hầu hết các ứng dụng đều có thể bị tấn công bởi kỹ thuật dịch ngược, nhưng điều quan trọng là phải kiểm tra tác động tiềm ẩn của kỹ thuật dịch ngược đối với hoạt động kinh doanh, cân nhắc xem có nên giảm thiểu rủi ro này hay không.
Phải làm gì:
- Sử dụng code obfuscation: Flutter cho phép bạn xáo trộn code ứng dụng, bạn có thể tìm thêm chi tiết tại đây. Một điều đáng nói là ngay cả khi không có obfuscation, muốn dịch ngược ứng dụng Flutter vẫn khó hơn các ứng dụng Native. Nhưng nếu chúng ta chỉ phát hành ứng dụng mà không trộn code của mình, một số công cụ phân tích nhị phân cổ điển như BinDiff hoặc Diaphora có thể khôi phục tên ban đầu của các chức năng bằng cách sử dụng bản dựng không bị trộn code. Tuy nhiên, ngay cả khi một ứng dụng Flutter luôn được phát hành với tuỳ chọn
obfuscate
, chuyên gia dịch ngược vẫn có thể sử dụng kỹ thuật phân tích nhị phân để xác định các Flutter frameworks phổ biế được sử dụng trong ứng dụng. Bạn có thể tìm thấy bài viết chi tiết về Kỹ thuật dịch ngược trong Flutter tại liên kết này.
🐛 M10: Extraneous Functionality
Thông thường, các nhà phát triển sẽ làm một vài chức năng backdoor ẩn hoặc các nghiệp vụ phát triển nội bộ khác và không có ý định đưa chúng vào môi trường sản xuất. Ví dụ: nhà phát triển có thể quên không xoá cụm comment chứa mật khẩu trong code của ứng dụng. Một ví dụ khác là việc vô hiệu hóa xác thực 2 yếu tố trong quá trình testing. Đặc điểm xác định của rủi ro này là để kích hoạt chức năng dựa vào một điều kiện xác thực trong ứng dụng chưa phát hành.
Phải làm gì:
- Kiểm tra logs: đảm bảo không có nội dung nhạy cảm nào về ứng dụng trong trong logs hoặc ta chỉ cần xóa mọi logs khi ứng dụng chạy release.
- Bỏ các phần test khỏi bản dựng chính thức: mọi mock data hoặc test code phải được loại bỏ khỏi bản phát hành vì nó có thể tiết lộ thông tin quan trọng về định dạng dữ liệu và chức năng ứng dụng.
- Kiểm tra các cài đặt cấu hình của ứng dụng: nếu bạn sử dụng tệp cấu hình bên ngoài, hãy đảm bảo chúng được thiết lập đúng cách trước khi tạo bản phát hành và đảm bảo không thể thay đổi chúng theo cách thủ công, nó sẽ vô tình tiết lộ thông tin quan trọng về ứng dụng của bạn.
🎁 Tổng kết
Quokka hy vọng bài viết này hữu ích cung cấp cho anh em một số hướng dẫn về cách cải thiện tính bảo mật của ứng dụng Flutter.
Lưu ý rằng trên đây chỉ là danh sách 10 lỗ hổng và biện pháp bảo mật dựa trên OWASP, nó không phải là danh sách đầy đủ, có thể có những lỗ hổng khác. Bạn cần cập nhật liên tục những tin tức về các lỗ hổng bảo mật. Hacker ở trong bóng tối mà! Ngoài ra, bạn cũng có thể cân nhắc khi áp dụng các biện pháp bảo mật vào sản phẩm, nó tùy thuộc vào nhu cầu kinh doanh và mục tiêu của dự án.
Cảm ơn vì đã đọc 🙏
Nguồn:
All rights reserved