Justin
Justin

Reputation: 3634

How to protect against CSRF on a static site?

I have a static website, being served from a CDN, that communicates with an API via AJAX. How do I protect against CSRF?

Since I do not have control over how the static website is served, I cannot generate a CSRF token when someone loads my static website (and insert the token into forms or send it with my AJAX requests). I could create a GET endpoint to retrieve the token, but it seems like an attacker could simply access that endpoint and use the token it provides?

Is there an effective way to prevent against CSRF with this stack?


Additional details: authentication is completely separate here. Some of the API requests for which I want CSRF protection are authenticated endpoints, and some are public POST requests (but I want to confirm that they are coming from my site, not someone else's)

Upvotes: 17

Views: 5751

Answers (4)

julianH
julianH

Reputation: 119

My solution is as follows

Client [static html]

<script>
// Call script to GET Token and add to the form
fetch('https:/mysite/csrf.php')
.then(resp => resp.json())
.then(resp => {
    if (resp.token) {
        const csrf = document.createElement('input');
        csrf.name = "csrf";
        csrf.type = "hidden";
        csrf.value = resp.token;
        document.forms[0].appendChild(csrf);
    }
});
</script>

The above can be modified to target a pre-existing csrf field. I use this to add to may pages with forms. The script assumes the first form on the page is the target so this would also need to be changed if required.

On the server to generate the CSRF (Using PHP : assumes > 7)

[CSRFTOKEN is defined in a config file. Example]

define('CSRFTOKEN','__csrftoken');

Server:

$root_domain = $_SERVER['HTTP_HOST'] ?? false;
$referrer = $_SERVER['HTTP_REFERER'] ?? false;

// Check that script was called by page from same origin
// and generate token if valid. Save token in SESSION and
// return to client
$token = false;
if ($root_domain && 
    $referrer && 
    parse_url($referrer, PHP_URL_HOST) == $root_domain) {
  $token = bin2hex(random_bytes(16));
  $_SESSION[CSRFTOKEN] = $token;
}

header('Content-Type: application/json');
die(json_encode(['token' => $token]));

Finally in the code that processes the form

session_start();

// Included for clarity - this would typically be in a config
define('CSRFTOKEN', '__csrftoken');

$root_domain = $_SERVER['HTTP_HOST'] ?? false;
$referrer = parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_HOST);

// Check submission was from same origin
if ($root_domain !== $referrer) {
    // Invalid attempt
    die();
}

// Extract and validate token
$token = $_POST[CSRFTOKEN] ?? false;
$sessionToken = $_SESSION[CSRFTOKEN] ?? false;
if (!empty($token) && $token === $sessionToken) {
  // Request is valid so process it
}

// Invalidate the token  
$_SESSION[CSRFTOKEN] = false;
unset($_SESSION[CSRFTOKEN]);

Upvotes: 0

Vishal Bhandare
Vishal Bhandare

Reputation: 105

There is very good explanation for same, Please check
https://cloudunder.io/blog/csrf-token/

from my understanding it seems static site won't face any issue with CSRF due to CORS restriction, if we have added X-Requested-With flag.
There is one more issue i would like to highlight here,

How to protect your api which is getting called from Mobile app as well as Static site?

As api is publicly exposed and you want to make sure only allowed user's should be calling it.
There is some check we can add at our API service layer for same

1) For AJAX request(From Static site) check for requesting domain, so only allowed sites can access it
2) For Mobile request use HMAC token, read more here
http://googleweblight.com/i?u=http://www.9bitstudios.com/2013/07/hmac-rest-api-security/&hl=en-IN

Upvotes: -1

Matt S
Matt S

Reputation: 15374

I could create a GET endpoint to retrieve the token, but it seems like an attacker could simply access that endpoint and use the token it provides?

Correct. But CSRF tokens are not meant to be secret. They only exist to confirm an action is performed in the order expected by one user (e.g. a form POST only follows a GET request for the form). Even on a dynamic website an attacker could submit their own GET request to a page and parse out the CSRF token embedded in a form.

From OWASP:

CSRF is an attack that tricks the victim into submitting a malicious request. It inherits the identity and privileges of the victim to perform an undesired function on the victim's behalf.

It's perfectly valid to make an initial GET request on page load to get a fresh token and then submit it with the request performing an action.

If you want to confirm the identity of the person making the request you'll need authentication, which is a separate concern from CSRF.

Upvotes: 17

Related Questions