Struts2漏洞自查实战:从原理到工具,手把手教你安全检测

1. 项目概述:为什么Struts2漏洞自查刻不容缓

如果你负责的网站或应用系统还在使用Struts2框架,那么“安全自查”这件事,可能比你想象的要紧迫得多。我见过太多团队,直到被安全部门通报、甚至被外部攻击者“光顾”之后,才手忙脚乱地开始排查。Struts2的漏洞,尤其是远程代码执行(RCE)这类高危漏洞,其危害性不亚于在服务器上给攻击者开了一扇“任意门”。攻击者利用一个精心构造的HTTP请求,就能在你的服务器上执行任意命令,窃取数据、植入后门、甚至将服务器变成“矿机”或攻击跳板。这绝不是危言耸听,安全圈内因Struts2漏洞导致的数据泄露、服务瘫痪乃至巨额勒索的案例比比皆是。

因此,掌握一套行之有效的自查方法,配备趁手的检测工具,是每一位网站管理员、开发者和安全运维人员的必备技能。这不仅仅是应付合规检查,更是对自己负责、对业务负责的体现。今天,我就结合自己多年在甲方做安全建设和应急响应的经验,手把手带你走一遍Struts2漏洞的自查流程。我们会从原理入手,理解漏洞的根源,然后聚焦于如何选择和使用靠谱的检测工具,最后落实到具体的操作步骤和问题排查上。即使你之前没有深入接触过安全测试,跟着步骤走,也能独立完成一次有效的安全体检。

2. 核心原理:Struts2漏洞的“病根”在哪里

要有效检测,必须先理解漏洞是如何产生的。Struts2框架的核心魅力在于其强大的表达式语言(OGNL, Object-Graph Navigation Language)和灵活的拦截器机制,但恰恰是这些强大的特性,在安全设计不当时,成了最大的安全隐患。

2.1 OGNL表达式注入:漏洞的通用“发动机”

绝大多数Struts2的高危漏洞,其本质都是OGNL表达式注入。你可以把OGNL理解为一门在Struts2内部使用的、功能强大的“小语言”。开发者原本用它来动态地访问和操作Java对象属性,比如在JSP页面上显示${user.name}。问题在于,如果攻击者能够控制传入的、最终会被OGNL解析器执行的数据,那么他就可以注入恶意的OGNL表达式。

例如,一个典型的漏洞利用payload可能长这样:%{(#_memberAccess[\"allowStaticMethodAccess\"]=true,#a=@java.lang.Runtime@getRuntime(),#a.exec('calc'))}。这段代码看似复杂,其逻辑却很清晰:先修改安全上下文,允许调用静态方法,然后获取Runtime对象,最后执行系统命令calc。当Struts2在处理用户输入(如某个参数name)时,未经过滤就直接将其代入OGNL上下文进行解析,这段恶意代码就会被执行。

2.2 漏洞触发点:参数处理流程中的“失守”

漏洞的具体触发点往往在参数处理环节。Struts2的拦截器栈(Interceptor Stack)负责处理HTTP请求,将参数绑定到Action的属性上。在一些历史版本中,对于某些特殊参数(如redirect:redirectAction:)或Content-Type(如multipart/form-data,用于文件上传)的处理逻辑存在缺陷,导致用户输入被错误地送入了OGNL解析流程。

例如,S2-045漏洞(CVE-2017-5638)就是基于Jakarta Multipart解析器的一个缺陷。攻击者可以在上传文件的HTTP请求头中的Content-Type字段里嵌入恶意OGNL表达式。由于框架错误地将整个头部内容用于构造错误信息,并最终将其解析为OGNL,导致了RCE。这个漏洞的影响之所以巨大,是因为它不需要任何表单字段,只需要一个构造错误的HTTP头即可触发,让很多基于参数过滤的WAF(Web应用防火墙)形同虚设。

注意:理解漏洞原理的最大价值不在于让你去构造攻击,而在于让你明白自查时应该关注什么。你的检测工具发送的每一个Payload,其实都是在模拟攻击者尝试向这些“可能的失守点”注入OGNL表达式的过程。

2.3 漏洞的延续性:为什么Struts2漏洞“野火烧不尽”

Struts2漏洞之所以层出不穷,有深层次原因:

  1. 框架设计复杂性:强大的功能带来了复杂的内部状态机和上下文环境,每一处对用户输入的处理都可能成为新的攻击面。
  2. 修复的副作用:官方针对一个漏洞的补丁,有时会通过黑名单方式禁止某些字符或关键字,但攻击者总能找到绕过黑名单的新语法或新上下文,从而衍生出新漏洞。
  3. 历史版本存量巨大:企业级应用升级框架版本成本高、风险大,导致大量存在已知漏洞的旧版本Struts2应用仍在线上运行,成为攻击者的“金矿”。

因此,自查不能是一次性的。它应该是一个持续的过程,伴随着你对所用Struts2版本的漏洞公告保持关注,并定期使用更新了检测规则的工具进行扫描。

3. 工具选型:如何挑选你的“安全探针”

市面上Struts2检测工具很多,从命令行脚本到图形化平台,从开源工具到商业产品。选择不当,要么漏报(有漏洞没扫出来),要么误报(正常功能被误判为漏洞),白白浪费时间和制造恐慌。根据我的经验,选型要看重以下几个维度:

3.1 开源工具:灵活高效的“手术刀”

对于技术人员,尤其是开发和安全运维,我首推开源工具。它们透明、可定制、学习价值高。

  • Struts2-Scan: 这是社区里非常经典的一款,用Python编写。它的优势在于漏洞覆盖全面(集成多个历史漏洞检测模块),并且Payload设计精巧,能有效绕过一些简单的WAF规则。它通常以命令行方式运行,输出结果清晰,适合集成到自动化脚本或CI/CD流程中。
  • 其他脚本与PoC集合:在GitHub、Exploit-DB等平台,存在大量安全研究人员发布的针对单个Struts2漏洞的检测脚本(Proof of Concept)。这些脚本极度轻量,针对性强,适合在已经明确怀疑存在某个特定漏洞(如S2-057)时进行精准验证。

使用开源工具的心得

  1. 环境依赖:确保你的操作环境(Python版本、Java环境)满足工具要求。我遇到过因为Python库版本不对而导致检测逻辑失效的情况。
  2. 源码审查:在使用前,花点时间简单看看核心检测代码。这不仅能让你更信任工具,还能帮你理解它的检测逻辑,在结果存疑时能自己进行初步判断。
  3. 更新与维护:关注工具的GitHub仓库,看它是否还在活跃更新。Struts2新漏洞出现后,社区工具通常能较快跟进。

3.2 商业漏洞扫描器:全面自动化的“体检中心”

如果你需要负责整个企业大量Web资产的安全巡检,或者团队里安全技能相对薄弱,那么商业化的Web漏洞扫描器(如AWVS、Nessus、AppScan等)是更合适的选择。

  • 优势:它们通常有友好的图形界面,可以调度批量扫描任务,生成详细的合规性报告,并且漏洞库由专业团队维护,更新及时。
  • 劣势:成本高,且对于Struts2这种需要精细Payload测试的漏洞,有时不如专精的开源工具检测深度强。扫描策略如果设置不当,可能对生产环境造成较大负载。

商业扫描器使用技巧

  • 创建精准扫描策略:不要总是用“全量扫描”模板。可以创建一个专门针对“Struts2漏洞”的扫描策略,只启用相关的检测插件,这样能大幅缩短扫描时间,减少对业务的影响。
  • 关注误报:商业扫描器出于“宁可错杀”的考虑,误报率可能较高。对于它报告的所有Struts2漏洞,都需要人工进行二次验证。验证方法就是使用我们后面会讲到的开源工具或手动测试进行复现。

3.3 在线检测平台:快速初筛的“试纸”

一些安全服务商提供了在线的Struts2漏洞检测页面。你只需要输入目标URL,点击检测即可。

  • 优点:绝对的无脑、快捷,无需任何环境搭建。
  • 缺点也是最致命的极度不安全。你将自家系统的URL和可能存在的漏洞信息,拱手送给了第三方平台。这些平台的安全性、数据保密性完全不可控。在绝大多数企业安全规范中,向未经授权的第三方在线平台提交内部系统URL进行安全检测,是严重的安全违规行为。因此,我强烈不建议对任何内部系统、预发布环境或生产环境使用在线检测工具,仅可用于对完全无关的、公开的测试目标进行原理性学习。

核心原则:工具是为目的服务的。对于内部自查,我推荐“开源工具为主,商业扫描器为辅,严禁使用在线平台”的策略。先使用Struts2-Scan这类工具进行深度、精准检测,再用商业扫描器做广度覆盖和报告输出。

4. 实战演练:手把手使用Struts2-Scan进行自查

理论说再多,不如动手做一遍。我们以最常用的开源工具Struts2-Scan为例,演示一个完整的自查流程。假设我们有一个待检测的内部测试系统,地址是http://internal-test-app:8080

4.1 环境准备与工具获取

首先,你需要一个测试环境。绝对不要直接在线上生产环境进行漏洞扫描!应在与生产环境一致的测试环境、预发布环境或专门的安全测试环境中进行。

  1. 获取工具:访问GitHub上可靠的Struts2-Scan项目仓库(请注意甄别,选择Star数多、近期有更新的项目)。通常可以通过git clone命令下载。
    git clone https://github.com/某个可靠仓库/Struts2-Scan.git cd Struts2-Scan
  2. 安装依赖:该工具通常基于Python2.7或Python3。使用pip安装所需库。
    pip install -r requirements.txt
    如果遇到网络问题,可以使用国内镜像源,如pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

4.2 扫描执行与参数详解

工具的基本用法很简单,但理解每个参数的意义,能让你更有效地使用它。

python struts2-scan.py -u http://internal-test-app:8080/login.action
  • -u:指定目标URL。这里最好指向一个具体的、动态的Action地址,而不是首页。因为很多漏洞检测需要与后端交互。
  • 其他常用参数
    • -n:指定并发线程数,提高扫描速度。但注意线程数过高可能对目标服务器造成压力,在测试环境也建议保守设置,如-n 5
    • -d:指定数据参数(POST参数)。例如-d “username=test&password=123”,工具会在发送Payload时携带这些参数,模拟已登录或带参数请求的状态,这对于检测某些需要特定上下文(如已认证)的漏洞很有帮助。
    • -v:指定漏洞编号,进行针对性扫描。如-v s2-045只检测S2-045漏洞。
    • --proxy:设置代理。如果你的测试环境需要通过代理访问,或者你想通过Burp Suite等抓包工具观察扫描流量,这个参数就非常有用。例如--proxy http://127.0.0.1:8080

一次完整的深度扫描命令示例

python struts2-scan.py -u http://internal-test-app:8080/user/profile.action -n 3 --proxy http://127.0.0.1:8080 --random-agent

这个命令会:以3个线程并发,扫描profile.action;所有流量经过本机8080端口的代理(方便用Burp Suite分析);使用随机的User-Agent头,以规避一些简单的基于Agent的拦截。

4.3 结果解读与验证

工具运行后,会在控制台输出结果。结果通常分为几类:

  1. [+] Vulnerable!:明确检测到漏洞。工具会显示漏洞编号(如S2-045)、触发的Payload和可能的影响。这是最高优先级的发现,必须立即处理
  2. [-] Not Vulnerable:针对某个特定漏洞检测未发现异常。这不能证明系统绝对安全,只说明对该漏洞的检测为阴性。
  3. [*] Checking for ...:检测过程中的状态提示。
  4. [!] Timeout[!] Connection Error:网络超时或连接错误。这需要排查是目标服务不稳定,还是Payload触发了对方的防护机制导致连接被重置。

对于“Vulnerable”的结果,必须进行人工验证

  1. 抓包复现:使用Burp Suite的Repeater模块,将工具检测到的成功Payload原样发送一次,观察响应。真正的RCE漏洞,其响应内容、响应时间或返回的错误信息中通常会有特征(如命令执行后的回显、延迟等)。
  2. 无害化验证:为了最终确认,可以尝试执行一个无害的、但能产生明显外部交互的命令。例如,在Linux系统上尝试ping -c 1 your-monitor-server-ip,然后在监控服务器上看是否能收到ICMP包;或者执行sleep 5观察请求响应是否有明显延迟。绝对不要执行rm -rf /wget下载恶意程序或任何破坏性命令。

5. 自查方案设计与执行要点

单点扫描只是开始,一个完整的自查方案需要系统性的设计。

5.1 资产梳理:你知道自己有多少个“Struts2”吗?

这是最基础也最容易出错的一步。很多企业只知道主站用了Struts2,却忽略了那些历史遗留的、边缘的业务系统、后台管理系统、合作伙伴对接接口等。

  • 方法
    1. 从CMDB(配置管理数据库)、项目文档、部署清单中梳理所有Java Web应用。
    2. 使用网络空间测绘技术(如内部版的FOFA、Shodan理念),在授权范围内对内部IP段进行Web服务探测,通过指纹识别(如特定的HTTP响应头、错误页面特征、静态资源路径/struts/等)来发现隐藏的Struts2应用。
    3. 检查依赖文件。检查项目中的pom.xmllib目录,看是否包含struts2-core的jar包。

5.2 检测策略:分而治之,循序渐进

不要一上来就对所有资产进行全漏洞、高强度的扫描。

  1. 分级检测
    • 高危资产优先:对外提供服务的网站、核心业务系统、存放敏感数据的后台,优先安排深度检测。
    • 测试环境先行:先在测试环境进行完整的、攻击性较强的扫描,评估漏洞存在性和修复方案。
    • 生产环境灰度:对生产环境的扫描,必须在业务低峰期进行,并使用最低的线程数(-n 1),避免DoS风险。可以考虑先针对几个已知的、危害最大的漏洞(如S2-045, S2-057)进行快速扫描。
  2. 漏洞库对应:根据你梳理出的Struts2版本号,去Apache官方安全公告页面,列出所有影响该版本的CVE编号。然后确保你的检测工具支持这些CVE的检测。

5.3 扫描时机与风险控制

  • 时机:选择业务量最低的时间窗口(如深夜),并提前通知相关业务和运维团队。
  • 风险控制
    • 备份:扫描前,确保目标系统有可用的、最近的备份。
    • 监控:扫描过程中,密切观察目标服务器的CPU、内存、线程池、应用日志和网络流量监控。一旦发现异常飙升,立即停止扫描。
    • 限速:务必使用工具的参数限制请求速率和并发数。
    • 白名单:如果扫描IP可能被WAF或主机防火墙拦截,需要提前将扫描器IP地址加入到白名单中。

6. 常见问题与排查技巧实录

在实际操作中,你肯定会遇到各种问题。下面是我踩过坑后总结的一些经验。

6.1 工具运行报错或无结果

  • 问题:执行python struts2-scan.py后报错,提示缺少模块或语法错误。
    • 排查:确认Python版本。很多老工具基于Python2.7,在Python3环境下需要调整。使用python --version查看。尝试用python2python3命令分别执行。
  • 问题:扫描长时间无结果,或大量提示Timeout
    • 排查
      1. 网络连通性:先用curl或浏览器手动访问目标URL,确认服务正常。
      2. 防火墙/安全组:检查扫描机到目标服务器的网络策略是否开放。
      3. 目标应用防护:可能目标部署了WAF或应用自身有频率限制,拦截了扫描请求。尝试降低线程数(-n 1),增加请求超时时间(如果工具支持),或添加--delay参数在请求间增加延迟。
      4. 代理干扰:如果你设置了--proxy但代理服务(如Burp)未开启或配置错误,也会导致全部超时。

6.2 扫描结果疑似误报或漏报

  • 疑似误报:工具报告漏洞,但人工无法复现。
    • 排查
      1. 分析响应:用Burp Suite抓取工具发送的成功请求和响应包。仔细看响应体,有时工具是根据响应中的特定字符串(如“ognl”错误信息)判断的。可能是应用自定义的错误页面包含了这些字符串,导致误判。
      2. 检查Payload:查看工具使用的具体Payload。有些Payload是执行echo命令并匹配回显,如果目标服务器的命令执行被限制或环境不同,可能执行成功但无回显,导致人工验证时以为失败。
      3. 环境差异:工具在测试时可能使用了默认的Linux命令(如/bin/sh),而你的服务器是Windows,导致命令不兼容。尝试使用通用性更高的Payload验证,如通过DNS外带信息(如ping -c 1 your-unique-domain.com)来确认。
  • 疑似漏报:你觉得有风险,但工具没扫出来。
    • 排查
      1. 版本针对性:确认工具是否支持你所怀疑的特定漏洞检测。你可能需要寻找专门的PoC脚本。
      2. 触发路径:某些漏洞可能需要特定的访问路径、HTTP方法(如PUT)或请求头。工具默认的检测路径可能不完整。需要你根据漏洞公告,手动构造请求测试。
      3. WAF/防护绕过:目标可能存在WAF,拦截了工具的常规Payload。需要尝试使用编码、混淆、分块传输等绕过技术。一些高级的扫描工具会内置绕过特性,或者你需要手动调整Payload。

6.3 修复后的验证扫描

修复漏洞(如升级Struts2版本、打补丁)后,必须再次扫描验证。

  • 重点:验证扫描不仅要使用同样的工具,最好能换用另一款工具进行交叉验证。因为修复可能不彻底,或者引入了新的兼容性问题。
  • 方法:除了漏洞扫描,还应进行回归测试,确保修复没有破坏正常的业务功能。检查应用的核心业务流程是否依然通畅。

7. 超越工具:手动检测与深度排查思维

工具不是万能的,尤其是面对高度定制化或经过加固的系统时。培养手动检测能力至关重要。

7.1 基于错误信息的探测

最简单的手动检测,就是故意触发Struts2的错误页面。在URL参数或表单中提交一些特殊的OGNL表达式片段,如%{${#等,观察返回的错误信息。如果错误信息中包含了“OGNL”、“Expression”等关键词,甚至直接打印出了表达式内容,那么该系统存在OGNL表达式注入的风险就极高。例如,尝试访问:http://target/xxx.action?test=%{1+1},观察返回内容。

7.2 利用DNS外带验证无回显RCE

有些漏洞执行命令后,结果不会直接返回在HTTP响应里(无回显)。这时,DNS外带(DNS Exfiltration)是一种非常有效的验证手段。原理是让目标服务器执行一条解析特定域名的命令,如果漏洞存在,服务器会向你的域名服务器发起DNS查询,从而被你感知。

操作步骤

  1. 准备一个你拥有日志查询权限的域名,例如your-monitor.com
  2. 构造一个Payload,让目标服务器执行pingnslookup命令,查询一个唯一的子域名,如unique-id.your-monitor.com
    • Linux:ping -c 1 unique-id.your-monitor.com
    • Windows:nslookup unique-id.your-monitor.com
  3. 在你的域名服务商或DNS服务器上,查看是否有来自目标服务器IP的对unique-id.your-monitor.com的查询记录。如果有,则证实RCE存在。

这种方法非常隐蔽,对目标影响极小,是验证无回显漏洞的黄金标准。

7.3 代码审计:终极的排查手段

如果条件允许,对Struts2应用的源代码进行安全审计是最彻底的方法。重点审查:

  1. struts.xml等配置文件:检查是否使用了已知存在安全问题的拦截器栈或配置项。
  2. 自定义拦截器:检查是否有自定义的拦截器对参数进行了不安全处理。
  3. Action中的参数接收:检查是否有Action直接接收复杂对象(如Map、List),并在后续流程中未经验证就使用,这可能为OGNL注入创造条件。

手动检测和代码审计需要更深的专业知识和经验,但它们能帮你发现那些自动化工具无法触及的、深层次的逻辑漏洞。将工具扫描与人工深度排查结合,才能构建起真正有效的Struts2应用安全防线。安全自查不是一次任务,而是一个需要持续投入、不断学习和改进的过程。从今天开始,就为你负责的系统做一次全面的Struts2漏洞体检吧。