z3
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

介绍
本篇学习ClassLoader加载类。之前学习过反序列化,只要构造一段序列化数据,被java反序列化就会造成rce。而加载类,只要这个类被加载,且被初始化,就可以执行我们想要的代码,它们两个区别:
反序列化漏洞利用是通过构造利用链,利用当前环境中已有类的组合,达到调用Runtime.exec实现命令执行的目的。
加载恶意类是将恶意类加载到JVM中,当这个类被初始化时,就会执行类内的静态代码块,造成代码执行。
通过代码执行,可以调用Runtime.exec,也可以执行其它代码,所以这里的代码执行的灵活度比命令执行更高。
例如当前环境下有杀软/edr,命令执行被拦截,那我们可以执行java代码,做一些简单操作,如一些文件操作,读文件,写入webshell,做代理进内网等等。
序列化数据是对象,它包含了对象的属性,类名,但不包含方法。而ClassLoader加载的.class是字节码文件,它是类,包含了一个类的所有信息,当然也包括方法,变量(只有变量名,只有类被初始化时,变量才有值)。
URLClassLoader
测试
URLClassLoader可以加载远程class文件。
注意:在本地测试时,要删除本地生成的class,不然会先在本地查找class,如果找到,就不会去远程获取。
如图,在idea中,删除out目录下的class就可以,注意别把Main类的class删掉。
例:
将下面类编译为Test.class,放到服务器,python-m http.server 8080
编写main类,调用远程class文件,通过反射调用start函数。
运行
报错找不到Test类。在服务器上看,已经收到了请求,说明确实从远程加载了Test.class,但为什么会报错找不到Test类?
调试分析
调试下URLClassLoader.loadClass过程,看它如何加载类。首先调用了URLClassLoader父类的loadClass,然后调用了上级的loadClass(双亲委派过程)
又调用了上级loadClass
调用了最顶层的loadClass方法findBootstrapClassOrNull
然后再一层层退出,直到Application ClassLoader层,获取到类Test类
再调试看下findClass获取到Test类的过程。如图,获取到资源对象res,然后传给defineClass
在获取Resource对象res时,会经过getLoader方法时,对url判断,如果/结尾,则读取当前地址后的Test.class文件,如果不是/结尾,则把最后的文件当做压缩包读取,然后解压,读取压缩包内的Test.class文件。
当地址使用http://127.0.0.1:1234/Test.class时,会将Test.class下载到本地,并将它当做jar包(zip包)解压,获取里面的Test类。因为Test.class不是压缩包,所以抛出打开压缩包错误的异常。
继续跟踪URLClassPath类的defineClass方法。先获取到Test.class的字节码,然后将字节码信息,和字节码来源,传给了父类SecureClassLoader的defineClass。
然后就是SecureClassLoader的defineClass调用ClassLoader的defineClass,最后给了native方法defineClass1,反回一个Class对象。
所以,开始的问题解决方法就是把地址改为http://127.0.0.1:1234/
成功运行。
当通过反射调用start函数,两句话同时输出,说明loadClass过程没有对类初始化,当使用类(本例中调用了类的函数)时才对类初始化。
注意:使用Class.forName加载类,会对类进行初始化。
总结
使用URLClassLoader,传入的地址,如果以/结尾,则会当做目录,去这个目录下找类,否则就会将这个地址后的文件当做jar包,从jar包中找类。
可以直接将字节码传给SecureClassLoader或ClassLoader的defineClass方法,创建Class对象。
loadClass过程没有对类初始化,当使用类时才对类初始化。
为什么要学习URLClassLoader?因为后面学习从远程加载,很多类本质上都是通过URLClassLoader加载类。
加载字节码
上个例子调试过程中已经发现了,可以直接将字节码传给SecureClassLoader或ClassLoader的defineClass方法,创建Class对象。下面实际测试下。
首先获取字节码,由于Test.class内的二进制不能直接用可见字符串表示,所以将它使用base64编码,在调用时将它解码。
使用python将Test.class文件内的字节码通过base64编码输出(read_bin是我自定义的读取二进制的函数)。
java代码如图
成功运行,第一句话在实例化Test对象时输出,第二句在调用start方法时输出
利用TemplatesImpl加载字节码
实战漏洞利用中,很少有机会可以让我们执行代码,通过反射调用defineClass去创建类。
最常见的还是反序列化漏洞。回忆下之前学习过的CC链,其中有几条链中就遇到过,在CC2中第一次遇到,并做了详细分析。
在ysoserial中封装好了,直接调用Gadgets.createTemplatesImpl就可以获取一个命令执行的对象,
这Gadgets.createTemplatesImpl方法中,使用Javaassist动态生成字节码,并在字节码中插入静态代码
块,写入Runtime.exec方法,然后将字节码写入到TemplatesImpl对象的_bytecode变量中,还设置了_name变量。如图
构造好的这个TemplatesImpl对象只要调用newTransformer方法,就会加载字节码,就会命令执行
调用链是newTransformer->getTransletInstance->defineTransletClasses加载class
_name不设置行不行?
如图,如果不设置_name,在getTransletInstance中就会直接return,
具体TemplatesImpl对象加载字节码的过程原理,在前面CC2文章有详细分析。
下面测试下手动构造TemplatesImpl对象(之前CC2中试过用Gadgets.createTemplatesImpl构造)这里有两个坑。
在调用newTransformer方法时会用到_tfactory变量,如果不设置直接调用newTransformer方法就会报错。但是这个变量在TemplatesImpl对象反序列化时会自动生成,所以可以直接生成_tfactory变量或把TemplatesImpl对象先序列化再反序列化
字节码对应的类,要继承AbstractTranslet,才能被成功初始化,执行静态代码块
Test类代码如图
Main类如图
成功执行静态代码块的内容
本例中的字节码时我们手动编译的,但在Gadgets.createTemplatesImpl方法中可以看到,ysoserial库通过Javaassist库生成的字节码。Javaassist库暂时就先不研究了。
BCEL ClassLoader
p神文章写的很好,直接看吧 https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html
总结
1、加载类需要用到ClassLoader,而ClassLoader有双亲委派机制,一般使用的都是AppClassLoader。
2、loadClass时会对对象初始化,但是Class.forName会对对象初始化。
3、可以通过反射,调用ClassLoader的defineClass方法,使用字节码创建Class对象。
4、可以构造TemplatesImpl对象,在反序列化时加载并初始化构造好的字节码,执行静态代码块。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
