Phân tích và xây dựng POC khai thác cve-2021-22911

Kết quả: 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]

Sau đây tôi xin trình bày về 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.

Bây giờ HPT xin trình bày bài phân tích CVE-2021-22911.

Để dễ dàng dựng lài ứng dụng Rocket Chat version bị lỗi, HPT sử dụng file docker-compose.yml ở trang này: https://viblo.asia/p/phan-tich-va-reproduce-loi-pre-auth-nosql-injection-lead-to-rce-tren-rocketchat-3121-Qbq5QEDX5D8

Nội dung file docker-compose.yml:

version: "2"

services:

rocketchat:

image: rocketchat/rocket.chat:3.12.1

restart: unless-stopped

volumes:

- ./uploads:/app/uploads

environment:

- PORT=3000

- ROOT_URL=http://localhost:3000

- MONGO_URL=mongodb://mongo:27017/rocketchat

- MONGO_OPLOG_URL=mongodb://mongo:27017/local

- MAIL_URL=smtp://f6f21a9a7976d5:[email protected]:2525

# Format: 'smtp://$user:[email protected]:587'

#       - HTTP_PROXY=http://proxy.domain.com

#       - HTTPS_PROXY=http://proxy.domain.com

depends_on:

- mongo

ports:

- 3000:3000

labels:

- "traefik.backend=rocketchat"

- "traefik.frontend.rule=Host: your.domain.tld"

mongo:

image: mongo:3.4

restart: unless-stopped

volumes:

- ./data/db:/data/db

#- ./data/dump:/dump

command: mongod --smallfiles --oplogSize 128 --replSet rs0

labels:

- "traefik.enable=false"

# this container's job is just run the command to initialize the replica set.

# it will run the command and remove himself (it will not stay running)

mongo-init-replica:

image: mongo:3.4

command: 'mongo mongo/rocketchat --eval "rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})"'

depends_on:

- mongo

# hubot, the popular chatbot (add the bot user first and change the password before starting this image)

hubot:

image: rocketchat/hubot-rocketchat:latest

restart: unless-stopped

environment:

- ROCKETCHAT_URL=rocketchat:3000

- ROCKETCHAT_ROOM=GENERAL

- ROCKETCHAT_USER=bot

- ROCKETCHAT_PASSWORD=botpassword

- BOT_NAME=bot

# you can add more scripts as you'd like here, they need to be installable by npm

- EXTERNAL_SCRIPTS=hubot-help,hubot-seen,hubot-links,hubot-diagnostics

depends_on:

- rocketchat

labels:

- "traefik.enable=false"

volumes:

- ./scripts:/home/hubot/scripts

# this is used to expose the hubot port for notifications on the host on port 3001, e.g. for hubot-jenkins-notifier

ports:

- 3001:8080

Hình ảnh ứng dụng Rocket Chat trước khi đăng nhập:

Ả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 [email protected]

Ảnh 14: Sử dụng chức năng Forgot Password cho account [email protected]

Để bắt đầu phân tích và tìm cách lấy được token ra, HPT truy cập vào Github của Rocket Chat để xem đoạn code bị lỗi:


Ả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.

Ả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 HPT đã 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\"}"}

Ả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 [email protected] 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\"}"}

Ả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 [email protected] là “v
  • Sử dụng cách khai thác của bài lab level 1 của HPT, 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


Ả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:


Ả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:


Ả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 HPT – 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)})()"}

Ả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à [email protected] với quyền admin

Những công cụ được sử dụng:

  • Visual Studio Code

  • MongoDB image latest cho 2 bài lab

  • MongoDB version 3.4 cho ứng dụng Rocket Chat

  • Brave

  • Chromium

  • Docker

  • Burp Suite

REFERENCES:

Pre-Auth Blind NoSQL Injection leading to RCE – HackerOne. Ref: https://hackerone.com/reports/1130721
Mailtrap – Email Server Testing. Ref: https://mailtrap.io/

Tổng kết:

Phía trên chính là bài phần tích của HPT về CVE-2021-22911. Mong các anh / chị sẽ xem xét và đóng góp ý kiến cho HPT. HPT xin chân thành cảm ơn.