T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/wecom_webhook_server.py:155- Finding
Unauthenticated Public Webhook Allows Identity Spoofing and Unauthorized State Changes
- Content
View full analysis
Vulnerability Details
File Location:
scripts/wecom_webhook_server.py:155, 855-899, 1152-1238, 1651-1667, 1748
Vulnerability Type: Missing webhook authentication and authorization
Risk Level: HighVulnerable Code
python SERVER_HOST = "0.0.0.0"python def handle(self, callback): msgid = callback.get("msgid", "") msgtype = callback.get("msgtype", "") userid = callback.get("from", {}).get("userid", "") content = callback.get("voice", {}).get("content", "") or callback.get("text", {}).get("content", "") # ... chattype = callback.get("chattype") or callback.get("chat_type") or "" chatid = callback.get("chatid") or callback.get("chat_id") or "" if chattype == "group" or chatid: return self._handle_group(callback, chattype=chattype, chat_id=chatid) if msgtype == "voicemail": return self._handle_voicemail(callback) if msgtype == "voice": return self._handle_voice(callback) elif msgtype == "text": return self._handle_text(callback)python def _handle_group(self, callback, chattype="group", chat_id=""): msgtype = callback.get("msgtype", "") userid = callback.get("from", {}).get("userid", "") speaker_role = "admin" if userid in self.group_admins else "member" # ... if self.entity_extractor and self.todo_mgr: assign = self.entity_extractor.extract_assignment(content, userid) if assign.get("is_assignment") and assign.get("assignee"): return self._dispatch_group_assignment(assign, content)python ok = self.todo_mgr.register_todo( todo, assigner=assign["assigner"], assignee=assign["assignee"], chat_id=self._chat_ctx.get("chat_id", ""), source_group=True, auto_schedule=False, )python def do_POST(self): parsed = urlparse(self.path) content_length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(content_length) try: callback_data = json.loa ...[truncated 3222 chars]- Remediation
View remediation
Remediation Suggestions
-
Authenticate every callback before parsing business data
- Implement WeCom's documented signature verification using the configured callback token.
- Validate the signature over the required timestamp, nonce, and encrypted payload fields.
- Reject requests with missing, malformed, or invalid authentication data.
-
Use authenticated callback encryption
- Decrypt callbacks using the configured
EncodingAESKey. - Validate the decrypted corporate/application identifier against the expected configured value.
- Do not accept equivalent identity fields from an unauthenticated plaintext body.
- Decrypt callbacks using the configured
-
Prevent replay attacks
- Enforce a narrow timestamp tolerance.
- Persist or cache recently accepted nonce/signature/message-ID tuples.
- Reject duplicate authenticated requests without periodically clearing the entire replay cache in a way that immediately re-enables old messages.
-
Constrain network exposure
- Bind to loopback by default.
- Place the service behind an authenticated TLS reverse proxy when public exposure is necessary.
- Restrict ingress to trusted proxy or WeCom source networks where operationally feasible.
-
Enforce authorization independently of callback fields
- Resolve user identity and role only after callback authentication.
- Validate administrative actions against a server-side identity and role mapping.
- Verify that task confirmation or rejection is performed by the authenticated assignee.
-
Harden HTTP handling
- Add a strict request-body size limit before reading
Content-Length. - Apply rate limiting after authenticating the source.
- Accept callbacks only on the intended path and reject unsupported content types.
- Return generic errors without disclosing internal processing details.
- Add a strict request-body size limit before reading
-
Add security tests
- Test rejection of unsigned, incorrectly signed, stale, replayed, and improperly encrypted callbacks.
- Test that sp ...[truncated 101 chars]
-
