# NoSQL Injection ## What is NoSQL Injection? ![image](https://hackmd.io/_uploads/rkA3416RJg.png) ### What is NoSQL **NoSQL (Not only SQL)** đề cập đến các loại cơ sở dữ liệu không quan hệ. Không giống như cơ sở dữ liệu quan hệ truyền thống (SQL), NoSQL cung cấp một mô hình dữ liệu linh hoạt cho phép lưu trữ và truy xuất hiệu quả và có thể mở rộng các loại dữ liệu khác nhau NoSQL ra đời như một phản ứng trước những hạn chế của SQL, vốn được thiết kế chủ yếu cho dữ liệu có cấu trúc và lược đồ cứng nhắc. NoSQL mang lại những lợi ích như khả năng mở rộng theo chiều ngang, tính sẵn sàng cao và mô hình dữ liệu linh hoạt, khiến chúng trở nên phù hợp với các ứng dụng hiện đại xử lý lượng lớn dữ liệu với cấu trúc và yêu cầu đa dạng. Cụ thể như: - Đối với NoSQL + `user_id` từ 1 → 1 triệu → nằm ở `Shard A` + `user_id` từ 1 triệu → 2 triệu → nằm ở `Shard B` ... Mỗi shard nằm ở một máy chủ khác nhau. --> Dễ dàng xử lý, các máy chủ xử lý song song, không bị nghẽn cổ chai - Đối với SQL + Dữ liệu liên kết chặt chẽ + Query phụ thuộc vào nhiều bảng nằm trong cùng một database. --> Nếu chia orders sang máy A, users sang máy B → JOIN không còn thực hiện được dễ dàng ### Types of NoSQL 1. **Key-Value Stores** ```json { "key": "user123", "value": { "name": "John Doe", "age": 30, "email": "john.doe@example.com" } } ``` 2. **Document Stores** ```json { "_id": ObjectId("5cdd783b3abf2c3a17a5b2fd"), "title": "Introduction to NoSQL", "author": "Jane Smith", "tags": ["database", "NoSQL", "document store"], "content": "In this article, we will explore the fundamentals of NoSQL databases...", "date": ISODate("2019-05-17T08:12:37.482Z") } ``` 3. **Column-Family Stores** ```sql user_id (key) | name | email | age u001 | Alice | alice@mail.com | 25 u002 | Bob | bob@mail.com | 30 ``` 4. **Graph Databases**: Tập trung vào các mối quan hệ giữa các thực thể, sử dụng các cấu trúc đồ thị để thể hiện và truy vấn các kết nối phức tạp ```sql (Alice) ---[:FRIEND]---> (Bob) ---[:FRIEND]---> (Charlie) ``` ### What is NoSQL Injection? **NoSQL injection** về mặt khái niệm, nó tương tự như SQL Injection nhưng thay vào đó, nó khai thác các điểm yếu trong cách mà NoSQL xử lý dữ liệu đầu vào từ người dùng. Sự khác nhau sẽ rõ hơn ở phần sau **NoSQL Injection** thường xảy ra khi một ứng dụng không thực hiện đầy đủ việc kiểm tra, làm sạch hoặc escape dữ liệu người dùng trước khi sử dụng trong các query. Attacker có thể lợi dụng điểm yếu này bằng cách chèn vào đầu vào những ký tự đặc biệt, cú pháp riêng của NoSQL, hoặc các toán tử. Attacker có thể tạo ra dữ liệu đầu vào độc hại nhằm thay đổi cấu trúc hoặc logic query gốc, từ đó thực hiện các hành động trái phép hoặc truy cập vào dữ liệu nhạy cảm. Impact có thể bao gồm: leak data, sửa đổi data, [leo thang đặc quyền](https://github.com/advisories/GHSA-jhq3-57xh-6643), DoS ### NoSQL Injection vs SQL Injection **1. Query Language** - **SQL Injection**: Khai thác các lỗ hổng trong syntax SQL và thực thi query. Attacker thao túng các query SQL để nối chuỗi query nhằm thay đổi hành vi của query - **NoSQL Injection**: Khai thác các điểm yếu trong việc xử lý đầu vào của người dùng trong query. Attacker thao túng đầu vào bao gồm các ký tự đặc biệt, cú pháp cụ thể của NoSQL hoặc các toán tử **2. Exploitation Technique** - SQL Injection: Attacker thường sử dụng các kỹ thuật như nối thêm các query, thao tác với các param hoặc tận dụng các ký tự comment: ```sql SELECT * FROM users WHERE username = '<username>' AND password = '<password>' ``` Payload và kết quả như sau ```sql= a' OR '1'='1'-- SELECT * FROM users WHERE username = 'a' OR '1'='1'-- AND password = '<password>' ``` - NoSQL Injection: Attacker khai thác việc thiếu xác thực đầu vào hoặc sanitize không đúng cách để inject, làm thay đổi cấu trúc hoặc logic dự định ```sql db.collection('users').findOne({ username: <username>, password: <password> }); ``` Payload và kết quả như sau ```sql= {$gt: "", $ne: null} db.collection('users').findOne({ username: '{$gt: "", $ne: null}', password: '<password>' }); ``` ### How does NoSQL Injection Work? Đoạn code bên dưới lấy giá trị `username` và `password` từ request body và thực hiện query từ hàm `findOne()` để kiểm tra xem có tồn tại user từ `users` không ```python= const username = req.body.username; const password = req.body.password; db.collection('users').findOne({ username: username, password: password }, (err, user) => { if (err) { // Handle error } if (user) { // User is authenticated, proceed with login } else { // Authentication failed, display error message } }); ``` Nếu thực hiện payload sau: ```json username:{ $ne: null } password:{ $ne: null } ``` Đoạn query sẽ như sau: ``` db.collection('users').findOne({ username: { $ne: null }, password: { $ne: null } }, (err, user) => { ``` Ở đây, attacker đã inject các toán tử `$ne: null` làm giá trị cho cả trường `username` và `password`. Kết quả là query sẽ match tất cả người dùng trong database có `username` và `password` không phải là null, bypass authen và có khả năng truy cập trái phép. ## How to find vuln ## Detecting NoSQL Injection Attacks Để phát hiện có tấn công NoSQLi ta chú ý đến vài dấu hiệu sau: - Tìm kiếm các **param** hoặc **giá trị** query - **Cấu trúc query bất thường**: Có thể liên quan đến các toán tử bất thường, điều kiện logic hoặc cấu trúc query không ngờ đến - **Cacs ký tự lạ**: Các ký tự thường được sử dụng trong các cuộc tấn công NoSQLi như: `'`, `"`,`$`,`&`,`$gt`,`$ne` - **Chiều dài của input bất thường**: Các giá trị đầu vào ngắn hoặc ngắn bất thường hoặc giá trị đầu vào dài bất thường có thể là hành vi khai thác lỗ hổng, trong khi giá trị ngắn có thể chỉ ra việc attacker đang cố bypass cơ chế validate - **Status code trả về mã lỗi**: Kỹ thuật khai thác error base cũng được attacker áp dụng nhiều, việc log trả về log lỗi xảy ra liên tục hoặc độ dài của event có lỗi quá lớn cũng là dấu hiệu của cuộc tấn công - **Các request lặp lại nhiều lần**: Các request số lượng lớn hoặc thời gian request rất nhanh là dấu hiệu của việc attacker sử dụng tool để tấn công Regex để detect tấn công NoSQLi: ```regex (?:\b|["'`])(?i)(?:[^\s]*?(?:\$where|\$regex|\$eq|\$gt|\$gte|\$lt|\$lte|\$ne|\$in|\$nin)[^\s]*?\s*(?:[=:])|(?:(?:\s+or|\|\||&&)\s*\{.*\}\s*))+.* ``` - `(?:\b|["'`])`: Match các ký tự bắt đầu từ ', ",` - `(?i)`: Không phân biệt chữ hoa, thường - `(?:[^\s]*?(?:\$where|\$regex|\$eq|\$gt|\$gte|\$lt|\$lte|\$ne|\$in|\$nin)[^\s]*?\s*(?:[=:])`: Matcch đến các keyword như $where, $regex, $eq, $gt, $gte, $lt, $lte, $ne, $in, $nin, theo đó là dấu = hoặc : - `(?:(?:\s+or|\|\||&&)\s*\{.*\}\s*)`:Match với các toán tử or, ||, && theo đó là cấu trúc json ( {} ) - `.+`: Match với các phần còn lại ### A Detection Example ```log 127.0.0.1 - [28/May/2023:10:15:32 +0000] "GET /products?category=electronics&sort=price ASC HTTP/1.1" 200 512 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" 127.0.0.1 - [28/May/2023:10:18:42 +0000] "GET /products?category=books&sort=price ASC HTTP/1.1" 200 512 "-" "Mozilla/5.0 (Windows NT 10.0; Win643 x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" 127.0.0.1 - [28/May/2023:10:20:59 +0000] "GET /products?category=electronics&sort[$ne]=null HTTP/1.1" 200 512 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" ``` Ở dòng log thứ 3 ta thấy payload `category=electronics&sort[$ne]=null`. Toán tử `[$ne]` trong MongoDB được đại diện cho phép so sánh `not equal` và payload này đang thử bypass câu query điều kiện để kiểm tra xem tham số không bằng `null`. Payload này sẽ thực thi thành công nếu server không xử lý đúng ## How to prevent 1. Input Validation và Sanitization 2. Sử dụng truy vấn tham số hóa (Parameterized Queries) 3. Phân quyền: Chỉ phân quyền có giới hạn cho các tài khoản để hạn chế tác động khi xảy ra tấn công 4. Áp dụng list chỉ cho phép các keys được chấp nhận 5. Xác thực đầu vào ở cả client-side và server-side: Thực hiện xác thực đầu vào cả ở server-side (nơi ứng dụng xử lý yêu cầu) và ở client-side (sử dụng JavaScript hoặc các cơ chế xác thực client-side khác) để cung cấp một lớp phòng thủ bổ sung chống lại tấn công 6. Role-Based Access Control (**RBAC**): Thực hiện cơ chế **RBAC** để kiểm soát quyền truy cập và quyền cho người dùng database. Đảm bảo mỗi user có quyền truy cập phù hợp dựa trên vai trò của họ, hạn chế attack surface với các cuộc tấn công ![image](https://hackmd.io/_uploads/HkMzPXARyl.png) # Practice lab ## Detecting NoSQL injection ### Target Goal Danh mục sản phẩm của bài lab được dựng lên bởi MongoDB NoSQL database và nó có lỗ hổng NoSQLi Để giải bài lab, sử dụng NoSQLi để hiển thị các sản phẩm chưa được phát hành ### Analysis and exploit Ở chức năng Category filter thay vì `filter=Gift`. Thêm kí tự `'` thành `filter=Gift'` --> Có error trả về -> ta sẽ thực hiện inject vào vị trí này ![image](https://hackmd.io/_uploads/ByBrOECRkl.png) Khi inject điều kiện sai `/filter?category=Gifts'+%26%26+0+%26%26+'x` ![image](https://hackmd.io/_uploads/HkD9YVR0ye.png) Khi inject điều kiện đúng `/filter?category=Gifts'&&1&&'` ![image](https://hackmd.io/_uploads/BJOycVR0ke.png) Bây giờ ta sẽ inject điều kiện luôn đúng`/filter?category=Gifts'||1||'`, các danh mục ẩn sẽ hiển thị, hoàn thành bài lab ![image](https://hackmd.io/_uploads/rJ0N9VCCke.png) ## Exploiting NoSQL operator injection to bypass authentication ### Target Goal Chức năng đăng nhập của bài lab được dựng lên bởi MongoDB NoSQL database và nó có lỗ hổng NoSQLi Để giải bài lab, login vào tài khoản admin Credentials: `wiener:peter`. ### Analysis and exploit Để login được vào page thì bắt request sẽ có Response như này ![image](https://hackmd.io/_uploads/H1cTcVAC1x.png) Để inject thành công, ta sẽ sử dụng 2 toán tử `$ne` và `$regrex` trong NoSQL > `$ne` (not equal) = "" , khi chèn vào password sẽ trở thành password not equals null -> Có thể đăng nhập vào bất cứ tài khoản nào có đặt mật khẩu (null là không có giá trị) Nhưng nếu ta sử dụng payload này với `username=administrator` ![image](https://hackmd.io/_uploads/rkEriNCCJe.png) Chứng tỏ username ở đây không phải là administrator mà sẽ chứa kí tự khác. Do đó, phải kết hợp sử dụng `$regrex` như sau: `{"$regex":"admin.*"}` . Lúc này bất cứ acocunt nào có username bắt đầu bằng admin sẽ được chọn là username đăng nhập , không quan trọng các kí tự phía sau Sau khi kết hợp chúng ta sẽ gửi requet đi ![image](https://hackmd.io/_uploads/HyEwiVAC1l.png) Được chuyển hướng tới `/my-account?id=adminvs0otjs4` -> có vẻ đã thành công --> Follow redirection ![image](https://hackmd.io/_uploads/BkiuiVACkx.png) ## Exploiting NoSQL injection to extract data ### Target Goal Chức năng đăng nhập của bài lab được dựng lên bởi MongoDB NoSQL database và nó có lỗ hổng NoSQLi Để giải bài lab, lấy được password của người dùng admin sau đó login vào Credentials: `wiener:peter` ### Analysis and exploit Thực hiện login vào page, bắt được gọi tin tìm user trong db ![image](https://hackmd.io/_uploads/HywlnNRAJg.png) Đầu tiên vẫn phải check xem có inject được không ![image](https://hackmd.io/_uploads/r1zWhN0RJx.png) Tiếp theo kiểm tra bằng `' && this.password[0] == 'p' || 'a'=='b `vì mình đã biết được pass của user này rồi, nếu trả về như lúc được lookup nghĩa là inject thành công > Khi gửi payload đi, nếu user=wiener và password = p thì sẽ trả về True và loại bỏ điều kiện OR phía sau. Còn nếu mình chỉnh password != p thì cả 2 điều kiện đều False Tìm theo brute-force user admin ![image](https://hackmd.io/_uploads/SkYS2VACyl.png) ![image](https://hackmd.io/_uploads/HyWCh400yg.png) Nhập password và hoàn thành bài lab ## Exploiting NoSQL operator injection to extract unknown fields ### Target Goal Chức năng đăng nhập của bài lab được dựng lên bởi MongoDB NoSQL database và nó có lỗ hổng NoSQLi Để giải bài lab, login được với tài khoản `carlos` ### Analysis and exploit Bài này sẽ vận dụng vào response của nó ![image](https://hackmd.io/_uploads/SkdNTNA01e.png) Mail reset này sẽ lấy ở đâu? ![image](https://hackmd.io/_uploads/H1wSpN0AJe.png) Ta dùng payload `"password":{"$ne":"invalid"}` để login vào user wierner thì thành công ![image](https://hackmd.io/_uploads/SJjeGU0CJx.png) Nhưng dùng cho user `carlos` thì chúng ta lại nhận được response là `Account locked: please reset password`. ![image](https://hackmd.io/_uploads/ryNfzLCAJx.png) Vì nhận được thông báo trên nên chúng ta sẽ gửi request POST `/forgot-password` của user `carlos`. Giờ ta phải tìm token reset password để đổi password của user `carlos` ![image](https://hackmd.io/_uploads/rkDOGL0CJe.png) Chúng ta phải tìm được trường chứa token reset password trong database. Sử dụng `$where` và thử lần lượt với '0' và '1', chúng ta sẽ thấy khi `"$where":"0"` thì kết quả là `Invalid username or password` nhưng khi `"$where":"1"` thì nó trả về `Account locked` như lúc nãy ![image](https://hackmd.io/_uploads/ByE3MI00kx.png) ![image](https://hackmd.io/_uploads/B1s2zL0C1e.png) Sau khi xác định được vị trí các trường thì chúng ta sẽ đi tìm tên trường đó. Gửi request qua Intruder, thêm vào `"$where":"Object.keys(this)[1].match('^.{$$}$$.*')"` để tìm tên trường, payload đầu tiên là các số từ 0 đến 20, payload thứ hai là gồm các kí tự thường, in hoa với số ![image](https://hackmd.io/_uploads/ryaRMUC0Jx.png) Kết quả trả về thì ta được trường `username` ![image](https://hackmd.io/_uploads/Hy5JQUAAJl.png) Tăng vị trí từ 1 lên 2. Sau khi tấn công chúng ta được trường `password` ![image](https://hackmd.io/_uploads/rkweQLC01l.png) Tăng vị trí lên 4. Sau khi tấn công, chúng ta được một trường mới là `pwResetTkn`. Đây có thể là trường chứa token reset password Ta thêm trường này vào request GET `/forgot-password`, cho giá trị là tùy ý. Sau khi gửi chúng ta sẽ nhận được response là `Invalid token`, vậy đúng là trường chứa token reset pasword ![image](https://hackmd.io/_uploads/SyGEXICRJx.png) Giờ chúng ta sẽ phải đi tìm token đó, sử dụng payload `"$where":"this.pwResetTkn.match('^.{$$}$$.*')"`, payload đầu tiên là các số từ 0 đến 20, payload thứ hai là gồm các kí tự thường, in hoa với số ![image](https://hackmd.io/_uploads/H1hrQIRCyx.png) Sau khi tấn công, chúng ta đã tìm được token gồm 16 kí tự ![image](https://hackmd.io/_uploads/B158Q80Ayl.png) Thay token vừa tìm được vào request GET `/forgot-password?pwResetTkn=...` và gửi. Chúng ta đã nhận được response chứa form đổi password ![image](https://hackmd.io/_uploads/rJqvmUCAkl.png) Đổi password và login vào user `carlos`. Hoàn thành bài lab.