Friday, February 7, 2014

Paperclip vulnerability leading to XSS or RCE.

Paperclip is the most popular upload tool for Ruby on Rails, and I found a way to upload a file with arbitrary extension, which can lead to XSS (file.html) or RCE (file.php/

By default Paperclip allows all types of files, and I believe it's a vulnerability on its own, "insecure-by-default". Developer is supposed to write validates_attachment :avatar, :content_type => { :content_type => "image/jpg" } and even paperclip_demo was misconfigured.

io_adapters/uri_adapter.rb looked like an interesting file. It's a built-in adapter to download a remote file from user-supplied URL. When Paperclip downloads a remote file it validates the Content-Type header instead of the actual file extension:
      @original_filename = @target.path.split("/").last
      @original_filename ||= "index.html"
      self.original_filename = @original_filename.strip

      @content_type = @content.content_type if @content.respond_to?(:content_type)
      @content_type ||= "text/html"

I crafted special URL serving a JPEG file with content-type = image/jpg, but having .htm extension in the path:  (also it serves - might be useful)

Paperclip thinks the file we supplied with URL is an image and saves it with original filename (file.jpg.htm). Furthermore, to make Paperclip download your remote file no configuration is required! Just remove type="file" from <input>. Omakase Rails Magic.

If you send a URL instead of a file, Paperclip automatically switches to another (vulnerable) URI adapter, which downloads and saves it like /PaperclipPath/01/02/03/file.jpg.htm

Finally, when Apache/nginx/%webserver% is serving the static file.jpg.htm it responds with according text/html Content-Type and JPG's internals.

We can hide our XSS (or PHP <?=code();?>)  payload in the EXIF header.

ÛßÙ4Ù¬ıPıfiˆmˆ˚˜ä¯ ¯®˘8˘«˙W˙Á˚w¸ ¸ò˝)˝∫˛K˛‹ˇmˇˇˇ· ‚ExifII*
Ü å ¢ ™( 1 ≤2 «£ €iá ¯CanonCanon DIGITAL IXUS 70¥ ¥ f-spot version 0.3.52008:09:08 11:29:26<img src=x onerror=alert(0)> öÇ Z ùÇ b 'à Pê 0220

To get a RCE (code execution) you would need file.php/.pl/.cgi to be executed by the web server, IMO it is a rare case for regular Rails apps, I didn't research it though.

Reported to thoughtbot: 11 Dec 2013
Thoughtbot releases new major Paperclip 4 version: 2 Feb 2014

You definitely should bundle-update and check:
1) You have properly whitelisted content-type
2) Your Paperclip version > 4.0 now
3) Look for suspicious .html/.%smth% in your paperclip uploads folder.

How I hacked Github again.

This is a story about 5 Low-Severity bugs I pulled together to create a simple but high severity exploit, giving me access to private repositories on Github.

These vulnerabilities were reported privately and fixed in timely fashion. Here is the "timeline" of my emails.

More detailed/alternative explanation.

A few days ago Github launched a Bounty program which was a good motivator for me to play with Github OAuth.

Bug 1. Bypass of redirect_uri validation with /../ 

First thing I noticed was:
If provided, the redirect URL’s host and port must exactly match the callback URL. The redirect URL’s path must reference a subdirectory of the callback URL
I then tried path traversal with /../ — it worked. 

Bug 2. Lack of redirect_uri validation on get-token endpoint

The first bug alone isn't worth much. There's protection in OAuth2 from "leaky" redirect_uri's, every 'code' has corresponding 'redirect_uri' it was issued for. To get an access token you must supply exact redirect_uri you used in the authorization flow.
redirect_uristringThe URL in your app where users will be sent after authorization. See details below about redirect urls.
Too bad. I decided to find out whether the protection was implemented properly.

It was flawed: no matter what redirect_uri the Client sent to get a token, the Provider responded with valid access_token.
Without the first bug, the second would be worth nothing as well. But together they turn into a powerful vulnerability — the attacker could hijack the authorization code issued for a "leaky" redirect_uri, then apply the leaked code on real Client's callback to log in Victim's account. Btw it was the same bug I found in

It's a serious issue and can be used to compromise "Login with Github" functionality on all websites relying on it. I opened Applications page to see what websites I should check. This section got my attention:

Gist, Education, Pages and Speakerdeck are official pre-approved OAuth clients. I couldn't find client_id of Pages/Education, Speakerdeck was out of Bounty scope (I found account hijacking there and was offered $100). Let's find a Referer-leaking page on Gist then.

Bug 3. Injecting cross domain image in a gist.

Basically, there are two vectors for leaking Referers: user clicks a link (requires interaction) or user agent loads some cross domain resource, like <img>.
I can't simply inject <img src=> because it's going to be replaced by Camo-proxy URL, which doesn't pass Referer header to attacker's host. To bypass Camo-s filter I used following trick: <img src="///">
You can find more details about this vector in Evolution of Open Redirect Vulnerability.
/// is parsed as a path-relative URL by Ruby's URI library but it's treated as a protocol-relative URL by Chrome and Firefox. Here's our crafted URL:

When the user loads this URL, Github 302-redirects him automatically.


But the user agent loads

Then user agent leaks CODE sending request to our <img>:
As soon as we get victim's CODE we can hit and voila, we are logged into the victim's account and we have access to private gists.

Bug 4. Gist reveals github_token in cookies

I was wondering how Gist persists the user session and decoded _gist_session cookie (which is regular Rails Base64 encoded cookie):
Oh my, another OAuth anti-pattern! Clients should never reveal actual access_token to the user agent. Now we can use this github_token to perform API calls on behalf of the victim's account, without the Gist website. I tried to access private repos:
Damn it, the token's scope is just "gists", apparently...

Bug 5. Auto approval of 'scope' for Gist client.

Final touch of my exploit. Since Gist is a pre-approved Client, I assumed Github approves any scope the Gist Client asks for automatically. And I was right.

All we need now is to load the crafted URL into the victim's browser:,gists,user,delete_repo,notifications

The user-agent leaks the victim's CODE, Attacker uses leaked CODE to log into the victim's Gist account, decodes _gist_session to steal github_token and ...
NoScript is not going to help. The exploit is script-less.
Private repos, read/write access, etc — all of it in stealth-mode, because the github_token belongs to Gist client. Perfect crime, isn't it?


$4000 reward is pretty good. Interestingly, it would be even cheaper for them to buy 4-5 hours of my consulting services at $400/hr which would have cost them $1600 instead. Crowdsourced-security is also an important thing to have. It's better to use them both :)

I'd love to help your company & save you a lot of money.

P.S. I have two other posts about Github vulnerabilities: mass assignment and cookie tossing.