Operator origin: …
| Target: https://mcp.canva.com
| Receiver: https://bb-canva-poc.pages.dev/r/
| Header: X-Bug-Bounty: bugcrowdninja Authorized testing under Canva's Bugcrowd program. Test account
afrozenkiwi@gmail.com consents to itself.
1 · Register a public OAuth client cross-origin
redirect_uri:
no auth required, ACAO reflects this page's origin
(idle)
2 · Open Canva consent screen
→ 302 to www.canva.com — URL bar must show real canva.com
(idle)
3 · Captured codes (server-side receiver)
Endpoint:polling every 2s…
received
code
state
ip / country
action
no codes captured yet — open consent in step 2 and click Allow as the victim
Manual paste fallback (if the receiver is unreachable)
(idle)
4 · Exchange code for bearer (cross-origin, no client_secret, no code_verifier)
response body must be JS-readable (ACAO reflected)
(idle)
5 · List MCP tools (cross-origin Bearer)
expect 22 tools, ~46 KB body
(idle)
6 · Read real victim data (search-designs, read-only)
click "Render" on any row to load that design's thumbnail (presigned S3 URL — no auth needed)
Exfiltrated victim account contents
#
Design ID
Title
Pages
Created
Edit URL
render
Raw search-designs JSON
(idle)
(idle)
7 · Persistence — refresh-token grant cross-origin
(idle)
Show variables
{}
Architecture of this PoC
This page is the OPERATOR CONSOLE. The full attack chain is split across
three origins — exactly how a real attacker would deploy it:
1. attacker.example (this page, the operator console)
- registers a public OAuth client at mcp.canva.com /register
- opens the /authorize consent screen in a victim browser
- polls bb-canva-poc.pages.dev/api/codes for incoming captures
- on a hit, exchanges the code for a bearer (no client_secret, no PKCE)
and reads victim designs via the MCP API
2. claude.com/redirect/<URL> (open redirect — pure proxy)
- registered as redirect_uri at /authorize, so Canva trusts it
- 302s the browser onward to the attacker's receiver, preserving ?code=
3. bb-canva-poc.pages.dev/r/ (victim landing — Cloudflare Pages)
- silently POSTs the OAuth code to /api/codes
- location.replace()s the victim to https://www.canva.com so the
flow ends on Canva and looks like a normal login
The victim never sees attacker.example. The browser address bar walks:
attacker.example → www.canva.com → claude.com → bb-canva-poc.pages.dev → www.canva.com
and lands on Canva, while the operator console (this page) silently picks
up the code from the receiver and completes the takeover.
Notes on redirect_uri values that work
The Canva /authorize allowlist accepts these hosts (for any registered client):
https://127.0.0.1:<port>/<path> — needs a localhost listener
https://localhost/<path>
https://www.cursor.com/<path> — code lands on a public domain (browser, history, extensions)
https://chatgpt.com/<path>
https://platform.openai.com/<path>
https://claude.ai/<path>
https://claude.com/<path> — and this includes:
https://claude.com/redirect/<FULL URL> — open redirect to ANY host. Use this to deliver
the code to an attacker-controlled server with no
victim cooperation.
This PoC chains claude.com/redirect → bb-canva-poc.pages.dev/r/ which silently
forwards the code to /api/codes and bounces the victim to www.canva.com.