April 25, 2023

ONGL表达式注入浅析

参考:

Java表达式注入之OGNL

Struts2漏洞集合分析与梳理 - 跳跳糖

远程调试:

Struts2-001 远程代码执行漏洞浅析-安全客 - 安全资讯平台

不错的bypass:

https://github.blog/2023-01-27-bypassing-ognl-sandboxes-for-fun-and-charities/

OGNL基础

学好OGNL注入的第一步,先学OGNL基础,其实这一部分拖欠了挺久的,一直给其他事情冲散了,今天就给他补上,换了新头像:可爱的奈月(OneRoom)
2292797302_c29fd95e59616d66343b28b9ff14b929.jpg
先导入一下依赖:

1
2
3
4
5
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.1.19</version>
</dependency>

(1)OGNL三要素

下面是一个比较简单的Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;

public class Tester {
public User user;

public User getUser() {
return user;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package org.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;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;

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 boogipop = new User("Boogipop", "11");
tester.setUser(boogipop);
//创建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);
}
}

运行上述代码即可获取org.example.Tester.user.name的值,注意是.user.name的值,上述我们是创建了一个tester,并且让他的user属性为一个User对象,表达式为user.name也就是获取tester的user属性的name属性。
这一点和网上一些demo有些许不同,在这里给他纠正了
image.png

(2)OGNL语法

如下:
image.png
可以发现它执行的方式有点类似递归,他把.前面的表达式当做结果给后面的表达式执行了
这里需要注意一下#前我们用括号包裹起来了,这是为了符合语法,假如去掉那一层包裹会报错:
image.png

用于调用非root对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package org.example;

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 boogipop = new User("Boogipop", "11");
tester.setUser(boogipop);
//创建contex、设置root
OgnlContext context=new OgnlContext();
//context.setRoot(tester);
context.put("user",boogipop);
//设置表达式
//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.png
倘若我们不加那个#,那么就会报错
image.png
因为它并不是根对象
用于创建Map
#{"name": "chenlvtang", "level": "noob"}
用于定义变量:
如一开始的例子#a=new java.lang.String("calc"),定义了一个字符串常量

(3)OGNL版本限制

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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("........");
}

(4)投影与选择

OGNL 支持类似数据库当中的选择与投影功能。

OGNL Expression解析流程

这一点还是比较重要的,因为过后一些Bypass可能是和这个有关的
image.png
断点直接这样给。
image.png
进入第一个getValue方法,在这里调用了另一个getValue,并且注意这个ASTchain,在OGNL表达式中,解析和执行就是通过ASTXXXX这些方法去解析执行的,一共有ASTChainASTConstASTCtorASTInstanceofASTListASTMethodASTStaticFieldASTStaticMethod…..等多种方法,其中最根本的就是Chain
image.png
调用Chain的getValue,接下里做一个链式调用
image.png
进入evaluateGetValueBody方法里面去。
image.png
判断是不是const,这里并不是
image.png
获取子节点,并且调用子节点的getValue,随后就这样不断的重复流程
image.png
image.png
最后进入OgnlRuntime.callMethod
image.png
image.png
最终执行命令。

S2-001分析

环境搭建

摆了()不太想复现,就是一个OGNL注入罢了
环境搭建采用vulhub进行搭建,vulhub

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#Java#CTF#OGNL