HackMyVM-translate

昨天群里发的靶机,很遗憾没能做成功,现在来复盘一下。

靶机ip:192.168.56.108

信息搜集


nmap:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
nmap -T4 -A -v 192.168.56.108
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-05-28 23:25 EDT
NSE: Loaded 156 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 23:25
Completed NSE at 23:25, 0.00s elapsed
Initiating NSE at 23:25
Completed NSE at 23:25, 0.00s elapsed
Initiating NSE at 23:25
Completed NSE at 23:25, 0.00s elapsed
Initiating ARP Ping Scan at 23:25
Scanning 192.168.56.108 [1 port]
Completed ARP Ping Scan at 23:25, 0.05s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 23:25
Completed Parallel DNS resolution of 1 host. at 23:25, 0.02s elapsed
Initiating SYN Stealth Scan at 23:25
Scanning 192.168.56.108 [1000 ports]
Discovered open port 80/tcp on 192.168.56.108
Discovered open port 22/tcp on 192.168.56.108
Discovered open port 5001/tcp on 192.168.56.108
Completed SYN Stealth Scan at 23:25, 0.06s elapsed (1000 total ports)
Initiating Service scan at 23:25
Scanning 3 services on 192.168.56.108
Completed Service scan at 23:26, 91.31s elapsed (3 services on 1 host)
Initiating OS detection (try #1) against 192.168.56.108
NSE: Script scanning 192.168.56.108.
Initiating NSE at 23:26
Completed NSE at 23:26, 0.83s elapsed
Initiating NSE at 23:26
Completed NSE at 23:26, 1.02s elapsed
Initiating NSE at 23:26
Completed NSE at 23:26, 0.00s elapsed
Nmap scan report for 192.168.56.108
Host is up (0.0012s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey:
| 3072 f6:a3:b6:78:c4:62:af:44:bb:1a:a0:0c:08:6b:98:f7 (RSA)
| 256 bb:e8:a2:31:d4:05:a9:c9:31:ff:62:f6:32:84:21:9d (ECDSA)
|_ 256 3b:ae:34:64:4f:a5:75:b9:4a:b9:81:f9:89:76:99:eb (ED25519)
80/tcp open http Apache httpd 2.4.62 ((Debian))
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET POST
|_http-title: \xE6\x88\x91\xE7\x9A\x84\xE9\xA1\xB5\xE9\x9D\xA2
|_http-server-header: Apache/2.4.62 (Debian)
5001/tcp open commplex-link?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 405 METHOD NOT ALLOWED
| Server: Werkzeug/3.1.3 Python/3.9.2
| Date: Thu, 29 May 2025 03:25:35 GMT
| Content-Type: text/html; charset=utf-8
| Allow: POST, OPTIONS
| Content-Length: 153
| Access-Control-Allow-Origin: *
| Connection: close
| <!doctype html>
| <html lang=en>
| <title>405 Method Not Allowed</title>
| <h1>Method Not Allowed</h1>
| <p>The method is not allowed for the requested URL.</p>
| HTTPOptions:
| HTTP/1.1 200 OK
| Server: Werkzeug/3.1.3 Python/3.9.2
| Date: Thu, 29 May 2025 03:25:35 GMT
| Content-Type: text/html; charset=utf-8
| Allow: POST, OPTIONS
| Access-Control-Allow-Origin: *
| Content-Length: 0
| Connection: close
| Help:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <head>
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request syntax ('HELP').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
| </html>
| RTSPRequest:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <head>
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request version ('RTSP/1.0').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port5001-TCP:V=7.94SVN%I=7%D=5/28%Time=6837D3B0%P=x86_64-pc-linux-gnu%r
SF:(GetRequest,18C,"HTTP/1\.1\x20405\x20METHOD\x20NOT\x20ALLOWED\r\nServer
SF::\x20Werkzeug/3\.1\.3\x20Python/3\.9\.2\r\nDate:\x20Thu,\x2029\x20May\x
SF:202025\x2003:25:35\x20GMT\r\nContent-Type:\x20text/html;\x20charset=utf
SF:-8\r\nAllow:\x20POST,\x20OPTIONS\r\nContent-Length:\x20153\r\nAccess-Co
SF:ntrol-Allow-Origin:\x20\*\r\nConnection:\x20close\r\n\r\n<!doctype\x20h
SF:tml>\n<html\x20lang=en>\n<title>405\x20Method\x20Not\x20Allowed</title>
SF:\n<h1>Method\x20Not\x20Allowed</h1>\n<p>The\x20method\x20is\x20not\x20a
SF:llowed\x20for\x20the\x20requested\x20URL\.</p>\n")%r(HTTPOptions,E1,"HT
SF:TP/1\.1\x20200\x20OK\r\nServer:\x20Werkzeug/3\.1\.3\x20Python/3\.9\.2\r
SF:\nDate:\x20Thu,\x2029\x20May\x202025\x2003:25:35\x20GMT\r\nContent-Type
SF::\x20text/html;\x20charset=utf-8\r\nAllow:\x20POST,\x20OPTIONS\r\nAcces
SF:s-Control-Allow-Origin:\x20\*\r\nContent-Length:\x200\r\nConnection:\x2
SF:0close\r\n\r\n")%r(RTSPRequest,1F4,"<!DOCTYPE\x20HTML\x20PUBLIC\x20\"-/
SF:/W3C//DTD\x20HTML\x204\.01//EN\"\n\x20\x20\x20\x20\x20\x20\x20\x20\"htt
SF:p://www\.w3\.org/TR/html4/strict\.dtd\">\n<html>\n\x20\x20\x20\x20<head
SF:>\n\x20\x20\x20\x20\x20\x20\x20\x20<meta\x20http-equiv=\"Content-Type\"
SF:\x20content=\"text/html;charset=utf-8\">\n\x20\x20\x20\x20\x20\x20\x20\
SF:x20<title>Error\x20response</title>\n\x20\x20\x20\x20</head>\n\x20\x20\
SF:x20\x20<body>\n\x20\x20\x20\x20\x20\x20\x20\x20<h1>Error\x20response</h
SF:1>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20code:\x20400</p>\n\x20\
SF:x20\x20\x20\x20\x20\x20\x20<p>Message:\x20Bad\x20request\x20version\x20
SF:\('RTSP/1\.0'\)\.</p>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20code
SF:\x20explanation:\x20HTTPStatus\.BAD_REQUEST\x20-\x20Bad\x20request\x20s
SF:yntax\x20or\x20unsupported\x20method\.</p>\n\x20\x20\x20\x20</body>\n</
SF:html>\n")%r(Help,1EF,"<!DOCTYPE\x20HTML\x20PUBLIC\x20\"-//W3C//DTD\x20H
SF:TML\x204\.01//EN\"\n\x20\x20\x20\x20\x20\x20\x20\x20\"http://www\.w3\.o
SF:rg/TR/html4/strict\.dtd\">\n<html>\n\x20\x20\x20\x20<head>\n\x20\x20\x2
SF:0\x20\x20\x20\x20\x20<meta\x20http-equiv=\"Content-Type\"\x20content=\"
SF:text/html;charset=utf-8\">\n\x20\x20\x20\x20\x20\x20\x20\x20<title>Erro
SF:r\x20response</title>\n\x20\x20\x20\x20</head>\n\x20\x20\x20\x20<body>\
SF:n\x20\x20\x20\x20\x20\x20\x20\x20<h1>Error\x20response</h1>\n\x20\x20\x
SF:20\x20\x20\x20\x20\x20<p>Error\x20code:\x20400</p>\n\x20\x20\x20\x20\x2
SF:0\x20\x20\x20<p>Message:\x20Bad\x20request\x20syntax\x20\('HELP'\)\.</p
SF:>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20code\x20explanation:\x20
SF:HTTPStatus\.BAD_REQUEST\x20-\x20Bad\x20request\x20syntax\x20or\x20unsup
SF:ported\x20method\.</p>\n\x20\x20\x20\x20</body>\n</html>\n");
MAC Address: 08:00:27:32:28:EA (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.8
Uptime guess: 1.066 days (since Tue May 27 21:52:29 2025)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=264 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE
HOP RTT ADDRESS
1 1.20 ms 192.168.56.108

NSE: Script Post-scanning.
Initiating NSE at 23:26
Completed NSE at 23:26, 0.00s elapsed
Initiating NSE at 23:26
Completed NSE at 23:26, 0.00s elapsed
Initiating NSE at 23:26
Completed NSE at 23:26, 0.00s elapsed
Read data files from: /usr/share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 95.00 seconds
Raw packets sent: 1023 (45.806KB) | Rcvd: 1015 (41.290KB)

dirsearch:

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
Target: http://192.168.56.108/

[23:32:46] Starting:
[23:32:47] 403 - 279B - /.ht_wsr.txt
[23:32:47] 403 - 279B - /.htaccess.bak1
[23:32:47] 403 - 279B - /.htaccess.orig
[23:32:47] 403 - 279B - /.htaccess.sample
[23:32:47] 403 - 279B - /.htaccess.save
[23:32:47] 403 - 279B - /.htaccess_extra
[23:32:47] 403 - 279B - /.htaccessBAK
[23:32:47] 403 - 279B - /.htaccess_orig
[23:32:47] 403 - 279B - /.htaccess_sc
[23:32:47] 403 - 279B - /.htaccessOLD
[23:32:47] 403 - 279B - /.htaccessOLD2
[23:32:47] 403 - 279B - /.htm
[23:32:47] 403 - 279B - /.html
[23:32:47] 403 - 279B - /.htpasswds
[23:32:47] 403 - 279B - /.htpasswd_test
[23:32:47] 403 - 279B - /.httr-oauth
[23:32:48] 403 - 279B - /.php
[23:32:50] 200 - 644B - /admin.php
[23:33:06] 403 - 279B - /server-status/
[23:33:06] 403 - 279B - /server-status

Task Completed

可以看到除了80端口和22端口以外还开放了一个5001端口,服务用的是Werkzeug/3.1.3 Python/3.9.2,当时上网搜了一下好像是个web框架的底层库,里面提到了一般和flask集成,我就想会不会是flask模板的ssti注入或者算pin值进console之类的,不过事实证明我方向完全错了。

(其实也不是完全错,事后一看B神的wp里提供了更改pin码设置后开启dubug进入console直接反弹shell提权的思路。)

之后我再去找这个库有没有什么现成的洞可以打,但是找到的全是3.0.x版本的,这条路也走不通了。

访问web主页。

看了源码,没啥东西,没有key,没有提示,之后访问/admin.php,是个登录界面,也是光秃秃的啥都没有。

在yakit用rockyou爆了半小时之后我放弃了。

Getshell

什么路都走不通,只能去访问刚刚的5001端口。nmap的扫描报告给了一些信息,首先它必须要求POST访问。

改成POST之后还要我们接着改content-type,改成json形式

提示让我们按照它的格式填键值对。

这里第三个参数的格式是text_list,需要用方括号括起来。

返回的值是空值。

测试一下存在命令执行点,即为text_list的值。

哇这边本来想看一下当前目录结果直接给刚刚登录界面的账号密码了。

但是看了一眼没有写的权限,本来还想写个webshell上去。

那只能反弹shell了,cat一下passwd,有bash,不过没装nc,用bash弹吧。

拿到的第一件事是升级成pty

但是这里我们也只是www-data,很小的一个权限,之后想办法提权吧

拿到userflag

1
userflag:flag{user-51860845-225a-42f3-b492-0a63bc722a96}

提权

两种思路

第一种是B神wp提供的,因为/home/welcome/py里存放了flask服务的app.py源码,所以可以直接自定义一个PIN码供我们进入到console。

首先建立端口转发,这里因为不知道ssh口类所以不能用ssh,wget传一个chisel,将靶机的8000端口代理到本地的3000端口上。

访问端口,发现是一个文件移动的服务。

事实上就是利用了app.py的源码

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
www-data@translate:/home/welcome/py$ cat app.py 
from flask import Flask, request, jsonify, send_file, render_template
import os
import shutil

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

@app.route('/id')
def show_id():
try:
# 执行id命令并获取输出
output = os.popen('id').read()
return render_template('id.html', id_info=output)
except Exception as e:
return jsonify({'error': str(e)}), 500

@app.route('/read', methods=['GET'])
def read_page():
if 'path' in request.args:
return read_file()
return render_template('read.html')

@app.route('/move', methods=['GET', 'POST'])
def move_page():
if request.method == 'POST':
return move_file()
return render_template('move.html')

def read_file():
try:
file_path = request.args.get('path')
if not file_path:
return jsonify({'error': '请提供文件路径'}), 400

if not os.path.exists(file_path):
return jsonify({'error': '文件不存在'}), 404

# 返回文件内容
return send_file(file_path)
except Exception as e:
return jsonify({'error': str(e)}), 500

def move_file():
try:
data = request.get_json()
source_path = data.get('source')
target_path = data.get('target')

if not source_path or not target_path:
return jsonify({'error': '请提供源文件路径和目标路径'}), 400

if not os.path.exists(source_path):
return jsonify({'error': '源文件不存在'}), 404

# 确保目标目录存在
target_dir = os.path.dirname(target_path)
if not os.path.exists(target_dir):
os.makedirs(target_dir)

# 移动文件
shutil.move(source_path, target_path)
return jsonify({'message': '文件移动成功', 'target_path': target_path})
except Exception as e:
return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
app.run(host='127.0.0.1', port=8000, debug=True)

很明显,因为tmp目录下我们可读可写,可以利用这个服务使用我们的恶意app.py替换掉它。

接下来有两种思路。

第一种是B神wp提供的,因为/home/welcome/py里存放了flask服务的app.py源码,而且开启了debug,不需要重启服务,所以可以直接自定义一个PIN码供我们进入到console。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from flask import Flask, request, jsonify, send_file, render_template
import os
import shutil

os_environ['WERKZEUG_DEBUG_PIN'] = '123-456-789'

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

@app.route('/id')
def show_id():
try:
# 执行id命令并获取输出
output = os.popen('id').read()
return render_template('id.html', id_info=output)
except Exception as e:
return jsonify({'error': str(e)}), 500

@app.route('/read', methods=['GET'])
def read_page():
if 'path' in request.args:
return read_file()
return render_template('read.html')

@app.route('/move', methods=['GET', 'POST'])
def move_page():
if request.method == 'POST':
return move_file()
return render_template('move.html')

def read_file():
try:
file_path = request.args.get('path')
if not file_path:
return jsonify({'error': '请提供文件路径'}), 400

if not os.path.exists(file_path):
return jsonify({'error': '文件不存在'}), 404

# 返回文件内容
return send_file(file_path)
except Exception as e:
return jsonify({'error': str(e)}), 500

def move_file():
try:
data = request.get_json()
source_path = data.get('source')
target_path = data.get('target')

if not source_path or not target_path:
return jsonify({'error': '请提供源文件路径和目标路径'}), 400

if not os.path.exists(source_path):
return jsonify({'error': '源文件不存在'}), 404

# 确保目标目录存在
target_dir = os.path.dirname(target_path)
if not os.path.exists(target_dir):
os.makedirs(target_dir)

# 移动文件
shutil.move(source_path, target_path)
return jsonify({'message': '文件移动成功', 'target_path': target_path})
except Exception as e:
return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
app.run(host='127.0.0.1', port=8000, debug=True)

想法很美好,但是当我们输入/tmp/app.py时会提示我们文件不存在

这里就是这个靶场考察的一个重点:tmp隔离

如果系统的systemd开启了PrivateTmp属性,那么这个服务访问到的tmp不是真的tmp路径,而是一个私有的目录,这里就是由apache生成的。

但是我们不知道这个目录的绝对路径是到底在哪,所以不能用那个文件移动来复制它。

那么有没有方法绕过隔离呢?答案很简单,我们之所以被隔离是因为我们用的是apache这个服务下面的命令执行来反弹,只要在其他的rce点反弹就行了。

比如最开始的那个POST注入点。

(感谢HYH大佬帮我指出错误,自己当时真尼玛铸币啊,能被这个卡一两个小时,唉。)

那么就能轻松加愉快的输PIN进到console反弹shell了,剩下的明天再补吧。


HackMyVM-translate
http://example.com/2025/05/29/HackMyVM-translate/
Author
Skyarrow
Posted on
May 29, 2025
Licensed under