Trên android, các ứng dụng được chạy trên một sandbox riêng biệt. Như vậy, dữ liệu của mỗi ứng dụng sẽ được giữ riêng biệt theo mặc định, không thể truy cập tuỳ ý bởi các ứng dụng khác (trừ khi được cấp quyền root). Để một ứng dụng có thể cho phép các ứng dụng khác tương tác với data của mình, android cung cấp một số giải pháp trong đó có Content Provider

Content Provider là một thành phần để quản lý truy cập dữ liệu, nó cung cấp các phương thức khác nhau để cho phép các ứng dụng khác thêm, sửa, xoá, xem dữ liệu được cung cấp bởi ứng dụng. Các dữ liệu này có thể là dữ liệu lấy từ các repository như SQLiteDatabase, file, data từ API…

Việc tương tác với data của Content Provider sẽ thông qua class ContentResolver. ContentResolver cho phép người dùng có thể query, insert, update, delete dữ liệu tuỳ ý bằng cách truyền các tham số của các phương thức trên tới Content Provider. Các tham số này được coi là untrusted data, và vấn đề phát sinh từ đây…

Thông thường Content Provider sẽ dùng data repository là SQLiteDatabase, hãy xem xét ví dụ sau:

Giả mình có một ứng dụng (tạm gọi là app V) có chức năng cung cấp thông tin món ăn cho các ứng dụng khác nhằm giúp giảm bớt thời gian suy nghĩ “hôm nay ăn gì?”, tuy nhiên app chỉ cung cấp tên món ăn còn về thành phần món ăn thì phải nạp card mới có 😀
Trong class FoodDatabaseHelper, tạo database foods_database có thông tin như hình dưới và thêm chút dữ liệu món ăn tại insertSampleData

Sau đó tạo method query phục vụ cho mục đích lấy dữ liệu và chỉ cho phép đọc dữ liệu từ cột name trong Content Provider (class FoodProvider)

Tiếp đó là tạo một app khác (app A), có chức năng lấy tên món ăn từ app V

Nếu chỉ lấy mỗi tên món ăn thì oke, trong khi nếu cố gắng lấy thông tin cột ingredients (thông tin về thành phần món ăn) app sẽ báo lỗi

Trông có vẻ an toàn vì app V chỉ cho phép đọc data từ cột name. Nhưng không hẳn…

Khi app attacker gửi truy vấn đến V, thực chất app này sẽ tạo ra một câu truy vấn SQL như sau:

Câu truy vấn trên được tạo dựa vào tham số của method query trong app V. Mặc định sẽ là SELECT * FROM foods_table, nếu các tham số còn lại khác null thì câu truy vấn sẽ thay đổi tương ứng, ví dụ như nếu selection khác null thì câu truy vấn sẽ có thêm WHERE + selection.
Như vậy là có mùi SQL injection ở đây!
Thử thay đổi giá trị của biến selection trong app A từ “name = ‘Pizza'” thành “name = ‘Pizza’ union select ingredients from foods_table where name = ‘Pizza'”

Và từ đó thì tình thế thay đổi, app A có thể lấy full thông tin mà không cần nạp card 😀

Có thể thấy rằng, việc nhận dữ liệu từ một ứng dụng khác mà không thông qua validate đúng cách có thể gây ra các lỗi bảo mật nghiêm trọng. Bên trên là ví dụ về Content Provider sẽ dùng data repository là SQLiteDatabase, đối với các dạng Content Provider khác (ví dụ như FileProvider..v..v..) thì việc validate dữ liệu đúng cách là rất cần thiết.

Đối với ví dụ trên, có thể tránh lỗi bằng cách sử dụng parameter query, cụ thể như sau:
Thêm biến _selection như hình, sử dụng biến này trong database.query và truyền null đối với tham số orderBy nếu không cần thiết (nếu cần thì tạo whitelist những option được phép)

Như vậy, để lấy dữ liệu app A sẽ phải thay đổi bằng cách sử dụng

Nếu cố tình truyền

Thì đơn giản là không có dữ liệu nào cả.

Các component trong app sẽ luôn có quyền đọc ghi đối với Content Provider, do vậy nếu data từ các app khác đi vào một component, sau đó đi đến Content Provider thì cũng có khả năng gây ra lỗi bảo mật.
Để truy cập vào dữ liệu của Content Provider cung cấp, thì Content Provider đó cần phải được export (exported=”true” như hình dưới). Nếu chỉ có export mà không khai báo các quyền cần thiết (ví dụ như thuộc tính android:readPermission), thì các ứng dụng khác mặc định có quyền truy cập dữ liệu của Content Provider.
Lưu ý là nếu chỉ khai báo quyền read mà không khai báo quyền write (bằng thuộc tính android:writePermission), thì các ứng dụng khác auto có quyền ghi 😀

Nếu ứng dụng khai báo quyền như hình trên, thì tất cả các app đều có thể truy cập dữ liệu bằng các khai báo thẻ use-permission trong file AndroidManifest.xml, ví dụ như:

Khi khai báo quyền này trong file manifest, ứng dụng sẽ tự động được cấp quyền truy cập dữ liệu của app dev.sangcx.victim khi người dùng cài đặt.
Để ngăn việc các app khác có thể truy cập dữ liệu tuỳ ý mà chỉ cho phép các ứng dụng được ký cùng key (có thể là các ứng dụng của cùng một công ty) mới được truy cập, có thể thay giá trị của android:protectionLevel từ normal thành signature.

Nói chung là Content Provider đã export rồi thì kể cả có set quyền này nọ mà không dùng android:protectionLevel là signature thì mình thấy hơi phế, vì có khai báo quyền thì app của attacker chỉ cần khai báo quyền tương ứng là xong.

Giả sử ứng dụng của bạn cấp quyền ghi dữ liệu cho các ứng dụng khác, do vô tình vì set android:protectionLevel = normal, hoặc là cố ý. Thì một app nào đó có thể chèn thật nhiều data để app của bạn lưu trữ. Dữ liệu sẽ tăng lên làm đầy bộ nhớ máy của người dùng, lúc họ kiểm tra thì thấy app của bạn dùng nhiều bộ nhớ…hmmm, chắc app của bạn không bị mắng đâu 😁

Categorized in: