T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/index.js:509
- Finding
- Whitelist authorization is not bound to the actual document mutation target<![CDATA[ ## Vulnerability Details **File Location**: `scripts/index.js:509-528`; equivalent vulnerable flows also occur at `scripts/index.js:547-563`, `scripts/index.js:575-591`, and `scripts/index.js:603-619` **Vulnerability Type**: Authorization target mismatch **Risk Level**: High ### Vulnerable Code ```javascript async function updateDocContent(nodeId, markdown, config = null, senderId = null, workspaceId = null, docKey = null) { if (!config) { config = loadConfig(); } const token = await getAccessToken(); const operatorId = await getCurrentOperatorId(token, senderId); // Authorization is performed against nodeId. const { docPath, permission } = await checkFullWritePermission( nodeId, operatorId, config, workspaceId ); // The mutation can independently target a caller-supplied docKey. const actualDocKey = docKey || nodeId; const result = await overwriteContent(actualDocKey, markdown, operatorId); return { success: true, data: { ...result, path: docPath, permissionRule: permission.matchedRule } }; } ``` The same identity mismatch is present in the block operations: ```javascript await checkFullWritePermission(nodeId, operatorId, config, workspaceId); const actualDocKey = docKey || nodeId; const result = await deleteBlock(actualDocKey, blockId, operatorId); ``` ```javascript await checkFullWritePermission(nodeId, operatorId, config, workspaceId); const actualDocKey = docKey || nodeId; const result = await modifyBlock(actualDocKey, blockId, element, operatorId); ``` ```javascript await checkFullWritePermission(nodeId, operatorId, config, workspaceId); const actualDocKey = docKey || nodeId; const result = await insertBlock(actualDocKey, element, operatorId, position); ``` ### Technical Analysis The whitelist decision is made using `nodeId`. That identifier is resolved through the Wiki API to obtain its workspace and node name. However, after authorization succeeds, ...[truncated 1855 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Use one canonical document identity for both authorization and mutation. 2. Resolve the actual `docKey` from the authorized `nodeId` through a trusted DingTalk API response rather than accepting it independently from the caller. 3. If the DingTalk API requires callers to provide both identifiers, retrieve authoritative metadata and reject the operation unless the supplied `docKey` is proven to belong to the supplied `nodeId`. 4. Replace logic such as: ```javascript const actualDocKey = docKey || nodeId; ``` with a verified resolution flow: ```javascript const authorizedDocument = await resolveDocumentIdentity(nodeId, operatorId); if (docKey && docKey !== authorizedDocument.docKey) { throw new Error('The supplied docKey does not belong to the authorized nodeId'); } await overwriteContent(authorizedDocument.docKey, markdown, operatorId); ``` 5. Apply the same binding requirement to overwrite, insert, modify, and delete operations. 6. Add negative tests that pair an allowed `nodeId` with an unrelated `docKey` and verify that every write operation is rejected. ]]>
