Как защититься от CSRF на статичном сайте?

У меня есть статический веб-сайт, обслуживаемый CDN, который взаимодействует с API через AJAX. Как защититься от CSRF?

Поскольку у меня нет контроля над тем, как обслуживается статический веб-сайт, я не могу генерировать токен CSRF, когда кто-то загружает мой статический веб-сайт (и вставляет токен в формы или отправляет его с моими запросами AJAX). Я мог бы создать конечную точку GET для получения токена, но похоже, что злоумышленник может просто получить доступ к этой конечной точке и использовать предоставленный ею токен?

Есть ли эффективный способ предотвратить CSRF с помощью этого стека?


Дополнительные детали: аутентификация здесь совершенно отдельная. Некоторые API-запросы, для которых я хочу защитить CSRF, являются аутентифицированными конечными точками, а некоторые — публичными POST-запросами (но я хочу подтвердить, что они исходят с моего сайта, а не с чьего-то другого)


person Justin    schedule 28.06.2017    source источник
comment
связывается с API через AJAX... сервер не задействован. Есть сервер, для API. Разве это не ваш собственный сервер/API?   -  person Matt S    schedule 28.06.2017
comment
Да, я уточню это в вопросе. Я предполагаю, что для CDN тоже есть серверы? Но я их не контролирую. У меня есть полный контроль над сервером API.   -  person Justin    schedule 28.06.2017


Ответы (3)


Я мог бы создать конечную точку GET для получения токена, но похоже, что злоумышленник может просто получить доступ к этой конечной точке и использовать предоставленный ею токен?

Правильный. Но токены CSRF не должны быть секретными. Они существуют только для подтверждения того, что действие выполняется в порядке, ожидаемом одним пользователем (например, форма POST следует только за запросом GET для формы). Даже на динамическом веб-сайте злоумышленник может отправить свой собственный запрос GET на страницу и проанализировать токен CSRF, встроенный в форму.

Из OWASP:

CSRF — это атака, которая обманом заставляет жертву отправить вредоносный запрос. Он наследует личность и привилегии жертвы для выполнения нежелательной функции от имени жертвы.

Вполне допустимо сделать первоначальный запрос GET при загрузке страницы, чтобы получить новый токен, а затем отправить его вместе с запросом, выполняющим действие.

Если вы хотите подтвердить личность человека, делающего запрос, вам потребуется аутентификация, что является отдельной задачей CSRF.

person Matt S    schedule 28.06.2017
comment
Отличные очки. Похоже, токен CSRF, даже если он встроен в HTML-страницу при загрузке страницы, не может полностью защитить от подделки межсайтового запроса, потому что злоумышленник может разобрать его и использовать в POST запросе. Выделенную конечную точку GET для предоставления токена определенно легче захватить/использовать злоумышленнику, но она обеспечит большую безопасность, чем вообще не применять токены CSRF. Это правильно? Спасибо. - person Justin; 28.06.2017
comment
Да, выделенный запрос GET, по крайней мере, подтвердит, что пользователь не был обманут при отправке запроса POST. - person Matt S; 28.06.2017
comment
Спасибо. Это очень полезно. - person Justin; 28.06.2017
comment
Одно быстрое продолжение — мне любопытно, почему в этой статье говорится не создавать конечную точку GET для получения токена. Ваша логика мне понятна; Я не понимаю, почему они говорят не делать этого здесь: ">github.com/pillarjs/ - person Justin; 28.06.2017
comment
Кажется, они смешивают аутентификацию с CSRF. И они предполагают, что каждый пользователь аутентифицирован. Но в любом случае, поскольку ваш сайт статичен, вам просто нужно пойти на компромисс, чтобы получить токен после загрузки страницы с помощью AJAX. Если каждый запрос AJAX также аутентифицируется, никакая третья сторона не может получить токен. Поскольку некоторые из ваших POST-сообщений являются общедоступными, вам придется разрешить общедоступный запрос GET для CSRF. Злоумышленник может получить токен, а затем выполнить POST для вашего API, но если API является общедоступным, вы ничего не можете с этим поделать. - person Matt S; 28.06.2017
comment
Извините, что оживляю этот пост: токен CSRF должен быть связан с пользователем? В противном случае любой вошедший в систему пользователь может сгенерировать токен CSRF и использовать его в чужой учетной записи. - person Kaymaz; 11.06.2020
comment
@Kaymaz Да, токен должен быть связан с одним и только одним пользователем. И каждый должен быть просрочен сразу после его использования. - person Matt S; 12.06.2020

Мое решение выглядит следующим образом

Клиент [статический 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>

Вышеупомянутое можно изменить, чтобы настроить таргетинг на уже существующее поле csrf. Я использую это, чтобы добавлять страницы с формами. Сценарий предполагает, что первая форма на странице является целевой, поэтому при необходимости ее также необходимо изменить.

На сервере для создания CSRF (использование PHP: предполагается › 7)

[CSRFTOKEN определяется в файле конфигурации. Пример]

define('CSRFTOKEN','__csrftoken');

Сервер:

$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]));

Наконец, в коде, который обрабатывает форму

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]);
person julianH    schedule 19.05.2021

Для этого есть очень хорошее объяснение. Пожалуйста, проверьте
https://cloudunder.io/blog/csrf-token/

Насколько я понимаю, статический сайт не столкнется с какими-либо проблемами с CSRF из-за ограничений CORS, если мы добавили флаг X-Requested-With.
Есть еще одна проблема, на которую я хотел бы обратить внимание:

Как защитить API, который вызывается как из мобильного приложения, так и из статического сайта?
br> Поскольку API является общедоступным, и вы хотите убедиться, что только разрешенные пользователи должны его вызывать.
Мы можем добавить некоторую проверку на наш сервисный уровень API для того же самого

1) Для запроса AJAX( Со статического сайта) проверьте запрашиваемый домен, чтобы к нему могли получить доступ только разрешенные сайты
2) Для мобильного запроса используйте токен HMAC, подробнее читайте здесь
http://googleweblight.com/i?u=http://www.9bitstudios.com/2013/07/hmac-rest-api-security/&hl=en-IN

person Vishal Bhandare    schedule 14.02.2018
comment
-1 ограничение CORS не защитит от CSRF. CORS не позволяет вам прочитать результат запроса, но не позволяет сделать запрос. Это невозможно, потому что CORS отправляется в заголовках ответа, поэтому ответ должен быть сгенерирован до того, как браузер сможет прочитать данные CORS. Таким образом, вредоносный сайт может выполнять CSRF-атаки на конечные точки, которые изменяют состояние, несмотря на CORS (например, bank.com/pay?amount=1000&to=Alice). Они не могут прочитать результат, но могут наблюдать за изменением состояния другими способами, например, есть ли на счету Алисы 1000 долларов. - person Jansky; 20.08.2018
comment
@Jansky Использование пользовательского заголовка запроса является одним из рекомендуемых способов OWASP. Даже если запрос сделан, сервер проверит пользовательский заголовок запроса. Вы можете добавить только заголовок с XHR, в то время как CORS не позволяет другой стороне сделать запрос с настраиваемым заголовком запроса. - person Franz Wong; 11.01.2020
comment
Я не уверен, что понимаю; что бы вы поместили в этот собственный заголовок запроса? Нет ничего, что могло бы помешать злоумышленнику добавить определенный настраиваемый заголовок в клиентский запрос из своего домена, если только вы не используете веб-токены, сгенерированные из вашего домена. Создайте веб-токен из вашего домена, пользователь сможет прочитать его только в том случае, если они пройдут CORS, они сделают последующий запрос с токеном, после чего вы узнаете, что они законны, потому что они не могли получить действительный токен запроса, не находясь в вашем домене. из-за КОРС. - person Jansky; 13.01.2020