T05 · Unauthorized Access and Privilege Escalation
- Location
index.ts:199- Finding
Inbound DingTalk callbacks are accepted without authentication or access-policy enforcement
- Content
View full analysis
, payload: unknown ): Promise { // Handle incoming webhook from DingTalk const data = payload as Record; // DingTalk callback format if (data.msgtype === "text" && data.text) { const textData = data.text as { content?: string }; return { id: String(data.msgId || Date.now()), channel: "dingtalk", content: { type: "text", text: textData.content || "" }, authorId: String(data.senderStaffId || data.staffId || "unknown"), authorName: String(data.senderNick || "Unknown"), conversationId: String(data.conversationId || data.chatId || "private"), timestamp: new Date(Number(data.createTime) || Date.now()), }; } return null; } ``` ### Technical Analysis The callback handler casts an untrusted payload to a record and creates an `InboundMessage` based solely on attacker-controlled fields. It does not verify a DingTalk callback signature, timestamp, nonce, encryption wrapper, or replay status. The configuration declares `encryptKey`, `dmPolicy`, `allowFrom`, `groupPolicy`, and `groupAllowFrom`, but this handler does not use any of them. Consequently, a payload can claim an arbitrary `senderStaffId`, `staffId`, `conversationId`, or `chatId`, and the configured allowlists do not provide effective protection within the audited implementation. The repository includes a `DingTalkCallbackPayload` type containing `encrypt`, `msg_signature`, `timestamp`, and `nonce`, but the callback implementation does not process those security fields. ### Attack Path 1. An attacker identifies or gains network access to the OpenClaw DingTalk callback route. 2. The attacker submits a forged payload such as a text message with an ...[truncated 1021 chars]- Remediation
View remediation
