DEV Community

Cover image for Pwned Together: Hacking dev.to

Pwned Together: Hacking dev.to

Antony Garand on August 31, 2018

Intro After reading this great post regarding the source of Dev.to, I got inspired. In order to celebrate my 500th follower on here, I ...
Collapse
 
ben profile image
Ben Halpern

Thanks for your awesome work. I promise we’ll keep upping the bug bounty program as we go, so keep up with the disclosures!

Collapse
 
michaelgv profile image
Mike

Is there a hackerone-style disclosure program?

Collapse
 
n1nj4sec profile image
Nicolas Verdier • Edited

Nice finding Antony,
For your information, the latest commit was still exploitable :) here is the poc to bypass the regex :
gist.github.com/n1nj4sec/9fc83e8bc... /../9fc83e8bc780e5c10739933ec3347460/raw/b46eef9822a00473f720680ed664873c3e20af9f/test.js" (the trick is to use /../)
and the fix implemented :
github.com/thepracticaldev/dev.to/...

Collapse
 
antogarand profile image
Antony Garand

This patch was also vulnerable ;)

As the regex ended with $, we could bypass it with a newline, then /../../.. + raw gist

github.com/thepracticaldev/dev.to/...

This was fixed by using \A and \Z instead of ^ and $!

Collapse
 
antogarand profile image
Antony Garand

Nice one!

Collapse
 
defman profile image
Sergey Kislyakov

@ben , isn't there an URI class or something like that in Ruby? I think it should handle parsing links much better than custom regex.

Collapse
 
rhymes profile image
rhymes

Probably you can slightly simplify the code using URI::regexp, is this what you mean?

Collapse
 
defman profile image
Sergey Kislyakov

Kinda. I think Ruby provides something like:

# pseudocode
link = URI.parse "http://evil.com/#gist.github.com/whatever"
link.host # evil.com
Thread Thread
 
andy profile image
Andy Zhao (he/him) • Edited

This would work for preventing non-gist-github.com hosts, but I think we strayed away from this because it wouldn't prevent Bypass 2, where JS is injected via a raw gist link.

We could do something like this:

URI.parse(link).host == "gist.github.com" &&
  (link =~ /^https\:\/\/gist\.github\.com\/([a-zA-Z0-9\-]){1,39}\/([a-zA-Z0-9]){32}\s/)
    &.zero?

I think with the regex though it would be redundant to check the host.

Thread Thread
 
joshcheek profile image
Josh Cheek • Edited

How about something like this?

require 'uri'

def valid?(link)
  uri = URI.parse link
  return false if uri.scheme != 'https'
  return false if uri.userinfo
  return false if uri.host != 'gist.github.com'
  return false if uri.port != 443 # I think it has to be this if its https?
  return false if uri.fragment
  return false if uri.query

  # idk if old gist ids could be arbitrary chars,
  # but the 10 or so I looked at all seemed to be hex
  path, gist = File.split uri.path
  return false unless gist && gist.match?(/\A[0-9a-f]{32}\z/)

  path, user = File.split path
  return false unless user && user.match?(/\A[a-zA-Z0-9\-]{1,39}\z/)

  path == '/'
end

Plus, a bunch of test cases:

valid = [
  # suggested one
  "https://gist.github.com/QuincyLarson/4bb1682ce590dc42402b2edddbca7aaa",
  # max name size (based on the existing regex, I didn't verify that it matches Github's rules)
  "https://gist.github.com/aaaaaaaaaabbbbbbbbbbccccccccccddddddddd/4bb1682ce590dc42402b2edddbca7aaa",
  # min name size
  "https://gist.github.com/a/4bb1682ce590dc42402b2edddbca7aaa",
  # each of the gist id char values (seems to be hex)
  "https://gist.github.com/a/0123456789abcdef0123456789abcdef",
  # user can have a dash in their name
  "https://gist.github.com/quincy-larson/4bb1682ce590dc42402b2edddbca7aaa",
  # just for contrast with some of the tests below
  "https://gist.github.com/a/0000000000111111111122222222223a",
]

invalid = [
  # the same as the valid one, except it's http. I actually thought browsers
  # would refuse to make http requests from https sites, but either way, it
  # should be disallowed as it's MITMable
  "http://gist.github.com/QuincyLarson/4bb1682ce590dc42402b2edddbca7aaa",
  # host
  "https://evil.com/QuincyLarson/4bb1682ce590dc42402b2edddbca7aaa",
  # auth credentials
  "https://user:pass@gist.github.com/QuincyLarson/4bb1682ce590dc42402b2edddbca7aaa",
  # http port (not totally sure this matters)
  "https://gist.github.com:80/QuincyLarson/4bb1682ce590dc42402b2edddbca7aaa",
  # query
  "https://gist.github.com/QuincyLarson/4bb1682ce590dc42402b2edddbca7aaa?q",
  # fragment
  "https://gist.github.com/QuincyLarson/4bb1682ce590dc42402b2edddbca7aaa#f",
  # this one exceeds the max name size
  "https://gist.github.com/aaaaaaaaaabbbbbbbbbbccccccccccddddddddde/4bb1682ce590dc42402b2edddbca7aaa",
  # less than min name size
  "https://gist.github.com//4bb1682ce590dc42402b2edddbca7aaa",
  # invalid gist id chars (outside the hex range)
  "https://gist.github.com/a/0000000000111111111122222222223x",
  # quick test showed that GH was case sensitive about the hex in the gist id
  "https://gist.github.com/a/0000000000111111111122222222223A",
  # too few path segments
  "https://gist.github.com",
  "https://gist.github.com/",
  "https://gist.github.com/a",
  # too many path segments
  "https://gist.github.com/QuincyLarson/4bb1682ce590dc42402b2edddbca7aaa/4bb1682ce590dc42402b2edddbca7aaa",
  # these ones were used to bypass validity in the blog
  "//evil.com/script#gist.github.com",
  "https://gist.github.com@evil.com/",
  "https://gist.github.com/AntonyGarand/a8a0b4a36a040edc6051e888afce8fab/raw/4deb366ddaf0597e82fea808f7f4cb3ad763d98f/poc.js",
]

valid.all?    { |link| valid? link } # => true
invalid.none? { |link| valid? link } # => true
Thread Thread
 
joshcheek profile image
Josh Cheek

Although, we should probably check whether you're allowed to have unicode in your username 🤔

Thread Thread
 
andy profile image
Andy Zhao (he/him)

@joshcheek gonna take that valid? method for checking giphy links. :)

Thread Thread
 
andy profile image
Andy Zhao (he/him)

Also that's pretty great. Might end up implementing it for the gist Liquid tag.

Collapse
 
gary_woodfine profile image
Gary Woodfine

What an excellent post!

I learned so much from this, and I'm not even a ruby developer!

Really informative and really, really well explained without going over the top with the geekness!

Well done!

Collapse
 
sl0badob profile image
sl0badob

Great info and writeup! Thank you for sharing. I have to ask a few questions if you wouldnt mind answering. How much time did you spend on this? What is your primary motivation; curiosity, cash, just because? Was the meager $150 reward worth you efforts?

Collapse
 
antogarand profile image
Antony Garand

I found the initial XSS within 15 minutes, but the variations and bypasses took few hours.

The primary motivation is to make the internet more secure, and fun part of breaking websites. The challenges and the reward of having an alert is fun.

The 150$ reward is plenty, I'm doing this for fun, and I like this website, so having a reward is only a nice bonus.

Collapse
 
math2001 profile image
Mathieu PATUREL

Nice! This is really awesome!

Collapse
 
rhymes profile image
rhymes

Great job Antony and thanks for the detailed explanation!

Collapse
 
joshcheek profile image
Josh Cheek

Oh wow, I was sweating as I read this! lol.

Collapse
 
rattanakchea profile image
Rattanak Chea • Edited

If dev.to was not open source, would you still be able to find this discovery? How much more effort? Using different approach? Thanks

Collapse
 
antogarand profile image
Antony Garand

Without the website being open source, I would have to perform a black box audit, and finding those vulnerabilities is definitely possible but might require more time.

Collapse
 
ajitkamath profile image
Ajit Kamath

Brilliant catch !!