Cross-site request forgery is an attack that executes malicious requests to a website where the victim has logged into their account. Although the requests seem to be executed on the victim's behalf, they are hidden from the victim. The attack is possible because cookies associated with the resource are automatically used for sending requests to the resource. The browser does not distinguish whether the request was made from this particular website or from a third-party one. Thus, the attack can be executed without an access to the user's credentials or cookies.
Usually, an application is vulnerable to CSRF if the interaction between the client and the server is built in an insecure way. For example, a client and a server interact via GET requests. These requests are made via accessing the URL with parameters. The URL may look like this:
domainname/DoAction?ActionName=DeleteReport&ReportName=ThatReport
The browser makes a GET request to the server, passing it the name of the method and the method's arguments. This example deletes a report ('ActionName=DeleteReport'). This already looks strange: GET requests shouldn't delete anything.
The report name is passed in the 'ReportName' parameter. If an attacker knows the report name, they can enter any suitable value.
The request executed at this URL deletes the report. The attacker doesn't even need an access to the victim's computer or account. Although, it looks like the victim deleted the report.
To perform an attack, a hacker generates a URL, an access to which starts malicious actions.
The attack may be performed via HTML markup. This means the attack can be performed implicitly when a page is loading. For example, an attacker can insert any URL in the 'src' attribute of the 'img' tag. While the page loads, a browser tries to get an image and sends a request to this URL.
So, the attacker creates their page with a zero-size 'img' element and uses the following request as the image URL:
<img
src="domainname/DoAction?ActionName=DeleteReport&ReportName=ThatReport"
width="0"
height="0">
Next, they apply social engineering and encourages a victim to load malicious markup — open a web page or an email made in HTML. A request is executed implicitly, and the victim doesn't notice anything suspicious.
This way of performing CSRF is called a GET scenario. The attack is possible if the website developers violated the standard — they didn't follow the instructions about the idempotency of GET requests. You shouldn't use GET requests for state-changing actions.
Attackers can use other HTTP methods. For example, they can create a page with JavaScript code. The code makes a request with any HTTP method (like GET, POST, PUT and others). To execute the request, attackers can user the fetch API or the 'XMLHttpRequest'. Malicious code can be executed implicitly for a victim. For example, the code can be executed when the 'onload' page lifecycle event is handled. Below is the code fragment that executes the request:
<script>
function sendRequest(){
var req = new XMLHttpRequest();
var url = 'domainname/DoAction';
var args = 'ActionName=DeleteReport&ReportName=ThatReport';
req.open('POST', url, false);
req.('Content-type', 'application/x-www-form-urlencoded');
req.send(args);
}
window.onload = sendRequest;
</script>
The attacker, using social engineering, encourages the victim to open a malicious page. The consequences of opening the page are similar to those for the GET script. The request to the vulnerable server will be implicit and the victim will not notice it being sent.
There is also another CSRF scenario that uses the POST method with HTML markup. It can be used if JavaScript is turned off in the victim's browser. The attacker also does not need access to the victim's computer or account. For example, the attacked website expects that interaction of the client and server parts is held through sending data. The website makes POST requests using the HTML 'form' tag. This tag allows to specify the URL of the request handler and the HTTP method used. The server expects to receive a POST request with a payload — the action name and parameters for this action. For example, the handler URL defined in the 'action' attribute looks like this:
domainname/DoAction
The value of the 'method' attribute is responsible for the HTTP method. Then the markup can look like this:
<form action="domainname/DoAction" method="post">
<input type="hidden" name="ActionName" value="DeleteReport"/>
<input type="hidden" name="ReportName" value="ThatReport"/>
<input type="submit" value="Some clickbaiting text"/>
</form>
The attacker, having information about names of actions and reports, can substitute any suitable value. The values are stored in hidden input fields. The request payload will be generated from these values.
When a victim clicks on the form submission button, a POST request to the URL is generated. This POST request has a payload of the hidden fields described above.
Again, the attacker uses social engineering to encourage the victim to go to the page. Besides opening a malicious page, the script requires actions on it — clicking the form submission button.
These examples show that even correctly implemented APIs are vulnerable. Moreover, disabling JavaScript doesn't help fight the problem.
Don't use GET requests for state-changing actions. Besides GET and POST, the HTTP standard describes many methods designed for different purposes: PUT, DELETE, etc. For example, to delete something, use the HTTP 'DELETE' method. Thus, the first thing to do against CSRF is to correctly implement the API. It can be REST API, for example.
The correct implementation of the API should not confuse obtaining resources and performing actions. The HTTP methods used should have semantics compliant with those described in the standard. However, the correct implementation alone does not protect against CSRF. Using JavaScript, you can make any request to any API endpoint. This requires additional security checks — identification of the environment in which the request was made.
The article from the OWASP website offers several patterns to solve the CSRF problems. They all have the requirement of a verification token. When a user proceeds to a vulnerable element, for example, to a form to fill out, the server returns a verification token along with the content. The number of tokens and their location (in markup, cookies, etc.) depend on the security pattern used. The choice of pattern depends on how the vulnerable element is loaded (static page, AJAX, etc.) and the characteristics of the server (stateful or stateless). When a user performs actions that send data to the server, the verification token is sent along with them. If there's no token in the incoming request or it's incorrect, the server won't process such request.
0