JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

admin 2024年5月15日09:31:39评论6 views字数 7697阅读25分39秒阅读模式

点击蓝字 关注我们

(本文由小貂快跑代发,原作者为“IntSheep”)

00

前言

Java代码审计的学习过程中离不开TemplatesImpl这个类。如果你恰好跟着P牛的《Java安全漫谈》学习代码审计的话,你刚学完Common Collection 6利用链后,在学习Common Collection3之前,就得学习Templatesmpl如何加载动态字节码。而学习这一块内容的时候,P牛有一些点并没有细讲,只能靠你字节深入代码领悟。
如果你恰好学到这边并且卡住了(这很正常,毕竟学习安全的同学很多并没有花费太多精力学习Java),不妨看看这篇文章,或许对你有些许帮助。

01

加载动态字节码

动态字节码,大白话讲,就是存放Java文件编译后的指令的文件。Java语言借助虚拟机JVM具有跨设备特性,只要设备上装有JVM就能运行Java代码。而JVM上面运行的就是Java编译后的文件。Java编译后的文件的后缀名为“.class”。
例如我有个Hello.java文件,通过编译后就变成了Hello.class,然后Hello.class就能够交给虚拟机进行运行了。
Intell j下Hello.java文件编译成Hello.class的方法直接右键点击Build Module就可以。生成的文件在out目录下。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

02

类加载器

我们现在拥有了一个编译后的.class文件。如果class文件是恶意代码,那么就能够加载完成后就能够完成攻击了。而加载class文件的这个东西就是叫做类加载器。《Java安全漫谈》里面就是使用URLClassLoader这个类加载器作为例子演示从远端加载一个class类并在本地运行。
另外,不论是什么类加载器,加载资源的时候都会经历调用loadClass()->findClass()->defineClass这个过程,如下图:

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

每个方法大概的作用如下:

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

我们学习TemplatesImpl正是因为其会调用defineClass方法。defineClass决定了如何将一段字节流转变成一个Java类。而原生的ClassLoader#defineClass作用域是不对外开放的,攻击者很少能直接利用它。

03

利用TemplatesImpl加载字节码

接下来我们要做的就是让TemplatesImpl加载字节码。为了达成这一目的,我会简单的讲一下我们利用链的调用流程(具体调用查看源码自己走一遍印象会更深刻)。接着我会详细讲一下参数的设置,因为这部分《Java安全漫谈》并没有展开讲为什么这样设置。设置参数需要从利用链回推过来看有什么地方限制住了链子的调用。最后就是把能做的都做了以后运行一遍,根据运行的报错信息再来修改。

3.1

明确利用链的调用

我们直接跳转到TemplatesImpl对defineClass()的调用。defineClass在TemplatesImpl重写,并且没有显示地定义域,没有显式就是default类型。default类型就可以被外部调用。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

从defineClass向前追溯一下调用链
TemplatesImpl#getOutputProperties()-TemplatesImpl#newTransformer()-TemplatesImpl#getTransletInstance()-TemplatesImpl#defineTransletClasses()-TransletClassLoader#defineClass()
因为getOutputProperties()和newTransformer()都是public方法,所以我们直接从newTransformer()调用就可以了。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

newTransformer()调用了getTransletInstance()方法。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

然后到getTransletClasses(),再到defineClass()。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

这里有个小技巧可以帮助看代码分析不会乱掉,就是找到利用链被调用的关键点的时候,可以将其添加进书签,这样后续回头分析只需要点一下书签就可以了。
比如我们分析TransletClassLoader#defineClass()被谁调用的时候,找到了TemplatesImpl#defineTransletClasses() ,这个时候可以在调用代码处_class[i] = loader.defineClass(_bytecodes[i], pd)添加书签,如下:

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

这样就能在左边轻易的点击跳转过来:

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

明确了调用链,我们就能写一个调用transform()的例子了。
public static void main(String[] args) throws Exception { byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE="); TemplatesImpl obj = new TemplatesImpl(); obj.newTransformer(); }
这个例子的code是讲class文件转化为Base64格式,转换的方法可以使用python,python代码如下:
import base64import iowith open("<class文件路径>","rb")as f1:    bin_date=f1.read()Byte_data=io.BytesIO(bin_date)base64_data=base64.b64encode(Byte_data.read()).decode()print(base64_data)
可以很明显看到上述虽说调用了newTransformer(),但是还没把字节文件code传进去让其调用,下面我将介绍如何传入参数。

3.2

明确参数的输入

TemplatesImpl类构造方法是不用传参的,也就是说它的参数都是私有类型,因此我们必须使用反射进行传参。首先看一下它所需要什么参数,这就需要从利用链反向推回去。

defineClass()

首当其冲的就是defineClass(),它负责加载动态字节码,因此它的参数必须是动态字节码。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程
JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

在defineTransletClasses()调用的defineClass()传入的参数是 _bytecodes,是一个默认值为null的变量。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

因此我们使用反射设置_bytecodes的值为动态字节码。

setFieldValue(obj, "_bytecodes", new byte[][] {code});

当然以上设置参数不能直接使用setFieldValue,要将其封装成一个函数:

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {   Class<?> objClass = obj.getClass();   Field field = objClass.getDeclaredField(fieldName);   field.setAccessible(true);   field.set(obj, value);}

defineTransletClasses()

没有一定要传什么参数不然不能执行的。

getTransletInstance()

这里就要求_name不能为null,并且_class得为null。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

newTransformer()

这个方法的调用不仅要确保getTransletInstance()会被执行,而且还得确保return这一步会被执行,毕竟这是利用链的开头,我们是要创建实例对象的,必须确保能获取实例对象。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

好像没有什么参数必须设置的。

直接运行。

3.3

根据报错调整

第一次调整

运行之后报错了,发现报的是有参数为null的错误。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

我们跟进第一个报错看一下。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

发现_tfactory不能为null。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

这个参数是个TransformerFactoryImpl类型的,直接使用反射给他一个初始值。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程
setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());

第二次调整

再上面调整的基础上再运行一遍,发现和前面一样的错误。直接定位到第一个报错继续查看。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

发现的报错其实是在defineClass方法下面的,不会影响defineClass方法的调用,我们不必理会!

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

第三次调整

上面运行没有问题的话,为什么字节码不会被加载呢?查阅了资料发现TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。

因此只需创建一个新的类继承AbstractTranslet,并将其转化为字节码就好了。

继承后可以跟进IntellJ提示添加父类的构造函数。并自己写一个构造方法,打印出“Hello Templatempl”。

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

完整代码如下

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.util.Base64;public class HelloDefineClass {   public HelloDefineClass() {   }   public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {       Class<?> objClass = obj.getClass();       Field field = objClass.getDeclaredField(fieldName);       field.setAccessible(true);       field.set(obj, value);   }   public static void main(String[] args) throws Exception {       byte[] code = Base64.getDecoder().decode("yv66vgAAADcALAoABgAdCQAeAB8IACAKACEAIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQATTEhlbGxvVGVtcGxhdGVzbXBsOwEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAlAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAY8aW5pdD4BAAMoKVYBAApTb3VyY2VGaWxlAQAWSGVsbG9UZW1wbGF0ZXNtcGwuamF2YQwAGQAaBwAmDAAnACgBABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwApDAAqACsBABFIZWxsb1RlbXBsYXRlc21wbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAAAsACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABAACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAEACQAAAD8AAgABAAAADSq3AAGyAAISA7YABLEAAAACAAoAAAAOAAMAAAASAAQAEwAMABQACwAAAAwAAQAAAA0ADAANAAAAAQAbAAAAAgAc");       TemplatesImpl obj = new TemplatesImpl();       setFieldValue(obj, "_bytecodes", new byte[][]{code});       setFieldValue(obj, "_name", "HelloTemplatesImpl");       setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());       obj.newTransformer();   }}

运行结果如下:

JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

成功运行!

总结

SUMMARY

TemplatesImpl这个类在Java代码审计里面有着举足轻重的地位,许多利用链都使用到了它。Java代码审计,特别是反序列方面的审计,调用的函数很多,需要正向和反向多次看一下利用链,才明白为什么这样调用、参数为什么这样设置。另外,大家可以尝试一下笔者所说的书签功能,在链子一长的情况下真的很好用。

  // 作者:IntSheep

在校大学生,绿盟科技暑期优秀渗透测试实习生。熟悉Web安全、应急响应、Java代码审计以及内网渗透。2023演练在某单位进行主防,参与多次应急响应。

原文始发于微信公众号(赛博游民营):JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月15日09:31:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA代码审计:深入分析Java中TemplatesImpl类加载动态字节码的过程http://cn-sec.com/archives/2499356.html

发表评论

匿名网友 填写信息