PHÂN TÍCH VÀ XÂY DỰNG POC KHAI THÁC CVE-2021-22911

01/09/2021

Kết quả cần đạt: Phân tích được lỗ hổng của CVE này. Xây dựng được PoC khai thác CVE. Hiểu rõ bản chất của CVE
Chi tiết Báo cáo: PHÂN TÍCH CVE-2021-22911
Quá trình khai thác lỗ hổng CVE-2021-22911 – Rocket Chat NoSQL Injection
[+] Introduction:
CVE-2022-22980 là một lỗ hổng bảo mật ứng dụng Rocket Chat với version 3.12.1, cho phép kẻ tấn công có thể khai thác Blind NoSQL Injection trên server mà không cần đăng nhập.
Các phân tích về CVE-2021-22911
Để dễ dàng dựng lài ứng dụng Rocket Chat version bị lỗi, tôi sử dụng file docker-compose.yml ở trang theo đường dẫn

Nội dung file docker-compose.yml:

 
anh-1.png
anh-2.png
Hình ảnh ứng dụng Rocket Chat trước khi đăng nhập:
 
hinh-3.png
Ảnh 13: Giao diện Login của Rocket Chat
 
[+] NoSQL Injection 1:
Theo report của Sonar Source, để có thể tiến hành đăng nhập mà không biết password của bất kì account nào, ta cần phải sử dụng chức năng Forgot Password để tiến hành tạo token và từ đó, sử dụng API ở hình trên để lấy token này ra.
Sử dụng chức năng Forgot Password với account admin khoiminhvo32@gmail.com

 
hinh-4.png
Ảnh 14: Sử dụng chức năng Forgot Password cho account khoiminhvo32@gmail.com
 
Để bắt đầu phân tích và tìm cách lấy được token ra, tôi truy cập vào Github của Rocket Chat để xem đoạn code bị lỗi:
 
hinh-5.png
Ảnh 15: Đoạn code bị lỗi của ứng dụng Rocket Chat
 
Ngoài ra, trong report trên HackerOne cũng cho ta biết rằng:
The getPasswordPolicy method does not properly validate or sanitize the token parameter and can thus be used to perform a blind NoSQL injection. It can be called without authentication (which seems intended), e.g. by using the /api/v1/method.callAnon API endpoint
-> Như vậy, chúng ta có thể tiến hành gọi API này mà không cần đăng nhập.

 
hinh-6.png
Ảnh 16: Tiến hành gọi tới API /getPasswordPolicy mà không cần Cookie
 
Nhìn vào đoạn code bị lỗi ở trên, có thể thấy nó giống với đoạn code tôi đã dựng lên ở level 1, ta có thể sử dụng $regex để đoán được cả chuỗi token của admin.
Payload:
{"message":"{\"msg\":\"method\",\"method\":\"getPasswordPolicy\",\"params\":[{\"token\":{\"$regex\":\"^A\"}}],\"id\":\"2\"}"}

 
anh-7.png
Ảnh 17: Sử dụng Regex để đoán xem token có bắt đầu bằng chữ A hay không
 
Có thể thấy trong Response trả về có dòng Invalid user [error-invalid-user]
-> Như vậy token reset password của account khoiminhvo32@gmail.com không phải bắt đầu bằng chữ “A”.
-> Thử dùng chữ “v”
Payload: 
{"message":"{\"msg\":\"method\",\"method\":\"getPasswordPolicy\",\"params\":[{\"token\":{\"$regex\":\"^v\"}}],\"id\":\"2\"}"}

 
hinh-8.png
Ảnh 18: Sử dụng $regex để đoán xem chữ đầu tiên trong token reset password có phải là "v" hay không
 
-> Dễ dàng thấy được trong response trả về không còn dòng Invalid user [error-invalid-user]
-> Như vậy chữ cái đầu tiên trong token reset password của account khoiminhvo32@gmail.com là “v”
-> Sử dụng cách khai thác của bài lab level 1 của tôi, chúng ta sẽ có thể lấy được full token và reset thành công password của admin.
[+] NoSQL Injection 2:
Đây là đối với ứng dụng chỉ có 1 account admin và 1 account thường => ta mới có thể đoán được token này của admin.
Vậy giả sử nếu 1 ứng dụng Rocket Chat có 80.000 users thì sao? Làm sao ta biết được token ta lấy ra là của user nào?
Điều này dẫn đến lỗi NoSQL Injection 2 cũng được tìm ra bởi Sonar Source.
Tuy nhiên, lỗ hổng này chỉ thực hiện được khi đã login bằng account bình thường. (Không Pre-Auth)
Hình ảnh ứng dụng sau khi tạo 1 tài khoản bình thường và login

 
hinh-9.png
Ảnh 19: Account lowminkhoy là account thường
 
Tiếp tục đọc report của Sonar Source kết hợp với đọc documentation:
 
hinh-10.png
Ảnh 20: Document về Query và Field trong Rocket Chat. Ref: https://developer.rocket.chat/reference/api/rest-api/endpoints/other-important-endpoints/query-and-fields-info
 
Như vậy, ứng dụng cho phép ta tìm user khác bằng cách sử dụng API Endpoint /api/v1/users.list?query= và cho phép ta truyền vào đó syntax của NoSQL.
Đến đây ta đã có thể nghĩ tới việc NoSQL Injection ở chỗ này.
Tuy nhiên, để xác định chắc chắn thì ta cần phải xem đoạn code này được xử lý như thế nào:

 
hinh-11.png
Ảnh 21: Đoạn code xử lý user.list
 
Ở đây, ứng dụng sử dụng hàm find() để tìm kiếm user bên trong database mà không có bất kỳ 1 lớp validate hay filter nào
-> Có thể ở đây sẽ bị NoSQL Injection
Thử dùng cách khai thác của bài lab level 2 của tôi – Sử dụng $where để ép ứng dụng throw error là thông tin token của user khác.
Payload: {"$where":"this.username+===+'MinKhoy'+&&+(()+=>+{throw+JSON.stringify(this)})()"}

 
hinh-12.png
Ảnh 22: Sử dụng $where để ép ứng dụng throw ra thông tin của user khác trong database
 
-> Như vậy chúng ta đã có thể lấy được toàn bộ thông tin của account MinKhoy có email là khoiminhvo32@gmail.com với quyền admin
Những công cụ được sử dụng:

1. Visual Studio Code
2. MongoDB image latest cho 2 bài lab
3. MongoDB version 3.4 cho ứng dụng Rocket Chat
4. Brave 
5. Chromium
6. Docker
7. Burp Suite
REFERENCES
Phân tích và Reproduce lỗi Pre-Auth NoSQL Injection. Viblo. Ref: (theo đường dẫn)
Pre-Auth Blind NoSQL Injection leading to RCE – HackerOne. Ref: (theo đường dẫn)
Đoạn code /getPasswordPolicy bị lỗi. Github. Ref: (theo đường dẫn)
Đoạn code /users.list bị lỗi. Github. Ref: (theo đường dẫn)
Mailtrap – Email Server Testing. Ref:  (theo đường dẫn)