Chapter 11. Cross-Site Request Forgery (CSRF)

Sometimes we already know an API endpoint exists that would allow us to perform an operation we wish to perform but we do not have access to that endpoint because it requires privileged access (e.g. an admin account).

In this chapter, we will be building cross-site forgery (CSRF) exploits that result in an admin or privileged performing an operation on behalf of us rather than a JavaScript code snippet.

CSRF attacks take advantage of the way browsers operate, and the trust relationship between a website and the browser. By finding API calls that rely on this relationship to ensure security, but yield too much trust to the browser—we can craft links and forms that with a little bit of effort can cause a user to make requests on his or her own behalf—unknown to the user generating the request.

Often times, CSRF attacks will go unnoticed by the user that is being attacked—as requests in the browser occur behind the scenes. This means, this type of attack can be used to take advantage of a privileged user and perform operations against a server without the user ever knowing. It is one of the most stealthy attacks, and has caused havoc throughout the web since it’s inception in the early 2000’s.

Query Parameter Tampering

csrf-get
Figure 11-1. CSRF GET—a malicious link is spread that causes state-changing HTTP GET requests to be performed on behalf of the authenticated user when clicked.

Let’s consider the most basic form of CSRF attack—parameter tampering via a hyperlink.

Most forms of hyperlink on the web correspond with HTTP GET requests. The most common of whom is simply an <a href="https://my-site.com"></a> embedded in an HTML snippet.

The anatomy of an HTTP GET request is simple, and consistent regardless of where it is sent from, read from or how it travels over the network. For an HTTP GET to be valid, it must follow a supported version of the HTTP specification—so we can rest assured that the structure of a GET request is the same across applications.

The anatomy of an HTTP GET request is as follows:

GET /resource-url?key=value HTTP/1.1
Host: www.mega-bank.com

Every HTTP GET includes the HTTP method (GET), followed by a resource URL and than followed by an optional set of query parameters. The start of the query params is dictated by the question mark ? and continues whitespace is found. After this comes the HTTP specification, and on the next line the host at which the resource URL can be located.

When a web server gets this request it will be routed to the appropriate handler class which will receive the query parameters alongside some additional information to identify the user that made the request, the type of browser they requested from and what type of data format they expect in return.

Let’s look at an example in order to make this concept more concrete.

The first example is a server-side routing class which is written on top of ExpressJS—the most popular NodeJS-based WebServer:

/*
 * An example route.
 *
 * Returns the query provided by the HTTP request, back to the requester.
 * Returns an error if a query is not provided.
 */
app.get('/account', function(req, res) {
  if (!req.query) { return res.sendStatus(400); }
  return res.json(req.query);
});

This is an extremely simple route which will do only a few things:

  • Accept only HTTP GET Request to /account

  • Return an HTTP 400 error if no query params are provided

  • Reflect query params to the sender in JSON format if they are provided

Let’s make a request to this endpoint from a web browser:

/*
 * Generate a new HTTP GET request with no query attached.
 *
 * This will fail and an error will be returned.
 */
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  console.log(xhr.responseText);
}
xhr.open('GET', 'https://www.mega-bank.com/account', true);
xhr.send();

Here from the browser we initiate an HTTP GET request to the server, which will result in a 400 error being returned because we did not provide any query parameters.

We can add the query parameters to get a more interesting result:

/*
 * Generate a new HTTP GET request with a query attached.
 *
 * This will succeed and the query will be reflected in the response.
 */
const xhr = new XMLHttpRequest();
const params = 'id=12345';
xhr.onreadystatechange = function() {
  console.log(xhr.responseText);
}
xhr.open('GET', `https://www.mega-bank.com/account?${params}`, true);
xhr.send();

Shortly after making this request, a response will be returned with the content:

{
  id: 12345
}

It will also include an HTTP 200 status code if you check out the network request in your browser.

It is crucial to understand the flow of these requests in order to find and make use of CSRF vulnerabilities. Let’s backtrack a bit and talk about CSRF again.

The two main components of a CSRF attack are:

  • Privilege Escalation

  • The user account who initiates the request typically does not know it occurred (it is a stealthy attack)

Most create-read-update-delete (CRUD) web applications that follow HTTP spec make use of many HTTP verbs, and GET is only one of them. Unfortunately, GET requests are the least secure of any request and one of the easiest ways to craft a CSRF attack.

The last GET endpoint we analyzed just reflected data back, but the important part is it did read the query params we sent it. The URL bar in your browser initiates HTTP GET requests, so do <a></a> links in the browser or in a phone.

Furthermore, when we click on links throughout the internet we rarely evaluate the source to see where the link is taking us.

The following link:

<a href="https://www.my-website.com?id=123">My Website</a>

Would appear literally in the browser as a “My Website”. Most users would not know a parameter was attached to the link as an identifier. Any user that clicks that link will initiate a request their browser that will send a query param to the associated server.

Let’s imagine our fictional banking website, MegaBank made use of GET requests with params. Look at this server-side route:

import session from '../authentication/session';
import transferFunds from '../banking/transfers';

/*
 * Transfers funds from the authenticated user's bank account,
 * to a bank account chosen by the authenticated user.
 *
 * The authenticated user may choose the amouint to be transfered.
 *
app.get('/transfer', function(req, res) {
  if (!session.isAuthenticated) { return res.sendStatus(401); }
  if (!req.query.to_user) { return res.sendStatus(400); }
  if (!req.query.amount) { return res.sendStatus(400); }

  transferFunds(session.currentUser, req.query.to_user, req.query.amount, (error) => {
  if (error) { return res.sendStatus(400); }
    return res.json({
       operation: 'transfer',
       amount: req.query.amount,
       from: session.currentUser,
       to: req.query.to_user,
       status: 'complete'
     });
  });
});

To the untrained eye, this route looks pretty simple. It checks that the user has the correct privileges, and checks that another user has been specified for the transfer. Because the user had the correct privileges, the amount specified should be accurate considering the user had to be authenticated to make this request (it assumes the request is made on behalf of the requesting user). Similarly we assume that the transfer is being made to the right person.

Unfortunately because this was made using an HTTP GET request a hyperlink pointing to this particular route could be easily crafted and sent to an authenticated user.

The way CSRF attacks involving HTTP GET param tampering usually go are as follows:

  1. A hacker figures out that a webserver uses HTTP GET params to modify it’s flow of logic (in this case, determining the amount and target of a bank transfer).

  2. The hacker crafts a URL string with those params: <a href="https://www.mega-bank.com/transfer?to_user=<hacker's account>&amount=10000">click me</a>

  3. The hacker develops a distribution strategy: usually either targeted (who has the highest chance of being logged in and having the correct amount of funds) or bulk (how can I hit as many people with this in a short period of time before it is detected).

Often these are distributed via email or social media. Due to the ease of distribution, the effects can be devastating to a company. Hackers have even taken out web-advertising campaigns to seed their links in the hands of as many people as possible when these have been discovered in the past.

Alternate GET Payloads

csrf-img
Figure 11-2. CSRF IMG—inside of the target application, a <img> tag is posted that forced an HTTP GET when loaded.

Because the default HTTP request in the browser is a GET request, many HTML tags that accept a URL parameter will automatically make GET requests when interacted with or when loaded into the DOM. As a result of this, GET requests are the easiest to attack via CSRF.

In the prior examples, we used a hyperlink <a></a> tag in order to trick the user into executing a GET request in their own browser.

Alternatively, we could have crafted an image to do the same thing:

<!--
 Unlike a link, an image performs an HTTP GET right when it loads into the DOM.
 This means it requires no interaction from the user loading the webpage.
 -->
<img src="https://www.mega-bank.com/transfer?
to_user=<hacker's account>&amount=10000" width="0" height="0" border="0">

When image tags are detected in the browser, the browser will initiate a GET request to the src endpoint included in the <img> tag. This is how the image objects are loaded into the browser.

As such, an image tag (in this case an invisible 0 x 0 pixel) image can be used to initiate a CSRF without any user interaction required.

Likewise, most other HTML tags that allow a URL parameter can also be used to make malicious GET requests. Consider the HTML5 <video></video> tag:

<!-- Videos typically load into the DOM immediately, depending on the browser's configuration. Some mobile browsers will not load until the element is interacted with. -->
<video width="1280" height="720" controls>
  <source src="https://www.mega-bank.com/transfer?
  to_user=<hacker's account>&amount=10000" type="video/mp4">
</video>

The video above functions identically to the image tag used prior. As such, it’s important to be on the lookout for any type of tag that requests data from a server via an src attribute. Most of these can be used to launch a CSRF attack against an unsuspecting end user.

CSRF Against POST Endpoints

csrf-post
Figure 11-3. CSRF POST—A form is submitted targeting another server that is not accessible to the creator of the form, but it is to the submitter of the form.

Typically CSRF attacks take place against GET endpoints, as it is much easier to distribute a CSRF via an hyperlink, image or other HTML tag that initiates an HTTP GET automatically.

However, it is still possible to deliver a CSRF payload that targets a POST, PUT or DELETE endpoint. Delivery of a POST payload just requires a bit more work as well as some mandatory user interaction.

Typically CSRF delivered by POST request are created via browser forms, as the <form></form> object is one of the few HTML objects which can initiate a POST request without any script required.

<form action="https://www.mega-bank.com/transfer" method="POST">
  <input type="hidden" name="to_user" value="hacker">
  <input type="hidden" name="amount" value="10000">
  <input type="submit" value="Submit">
</form>

In the case of CSRF via POST form, we can make use of the “hidden” type attribute on form inputs in order to seed data that will not be rendered inside of the browser.

We can further manipulate the user by offering legitimate form fields in addition to the hidden fields which are required to design the CSRF payload:

<form action="https://www.mega-bank.com/transfer" method="POST">
  <input type="hidden" name="to_user" value="hacker">
  <input type="hidden" name="amount" value="10000">
  <input type="text" name="username" value="username">
  <input type="password" name="password" value="password">
  <input type="submit" value="Submit">
</form>

In this example, the user will see a login form—perhaps to a legitimate website. But when the form is filled out, a request will actually be made against MegaBank—no login attempt to anything will be initiated.

This is an example of how legitimate looking HTML components can be used to send requests taking advantage of the user’s current application state in the browser. In this case, the user is signed into MegaBank, and although they are interacting with an entirely different website we are able to take advantage of their current session in MegaBank to perform elevated operations on their behalf.

This technique can also be used to make requests on behalf of a user who has access to an internal network. The creator of a form cannot make requests to servers on an internal network, but if a user who is on the internal network fills out and submits the form the request will be made against the internal server as a result of the target user’s elevated network access.

Naturally, this type of CSRF (POST) is more complex than seeding a CSRF GET request via an <a></a> tag—but sometimes you must make an elevated request against a POST endpoint in which case forms are the easiest way of successfully making an attack.

Summary

CSRF attacks exploit the trust relationship that exists between a web browser, a user, and a webserver/API.

By default, the browser trusts that actions performed from the user’s device are on behalf that user.

In the case of CSRF—this is partially true because the user initiates the action, but does not understand what the action is doing behind the scenes.

When a user clicks on a link, the browser initiates an HTTP GET request on their behalf—regardless of where this link came from.

Because the link is trusted, valuable authentication data can be sent alongside the GET request.

At it’s core, CSRF attacks work as a result of the trust model developed by browser standards committees like WhatWG.

It’s possible these standards will change in the future, making CSRF-style attacks much more difficult to pull off.

But for the time being, these attacks are here to stay—plus they are common on the web and easy to exploit.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.140.5