Правильно ли я написал защиту от csrf-атак?

HellWalk

Новичок
Для самописного фреймворка продумываю защиту от csrf-атак.
Подскажите, правильно ли она сделана в этом примере:

PHP:
<?php
 
function getRandomString($length = 15) {
    $chars = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    $numChars = strlen($chars);
    $string = '';
    for ($i = 0; $i < $length; $i++) {
        $string .= substr($chars, rand(1, $numChars) - 1, 1);
    }
    return $string;
}
 
session_start();
 
if ($_POST) {
    echo '<pre>';
    print_r($_POST);
    echo '</pre>';
 
    if ($_POST['csrf'] === $_SESSION['csrf']) {
        echo '<p>Токены идентичны. Ваши данные приняты.</p>';
    } else {
        echo '<p>Токены не совпадают. Ваши данные отклонены.</p>';
    }
} else {
    $_SESSION['csrf'] = getRandomString();
}
?>
 
<form method="POST" action="">
    <input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">
 
    <input type='text' maxlength='15' value='' name='Name'  />
 
    <input type="submit">
</form>
Результат:

 

Adelf

Administrator
Команда форума
А прежде чем писать свой, ты не хотел бы ознакомиться с другими фреймворками?

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

HellWalk

Новичок
А прежде чем писать свой, ты не хотел бы ознакомиться с другими фреймворками?
Я с ними знаком - Yii2, Laravel.
Но одно дело делать на них стандартный функционал. Другое - понимать как они устроены на самом базовом уровне. И третье - как можно написать тот же функционал в минимально рабочем варианте.
Так как мне нужен именно третий вариант - идти к нему только через изучение современных популярных фреймворков - очень долго. Например по работе с базой в Yii2 15 000 строк кода. Разбираться как оно работает в деталях (каждое свойство, каждый метод) - занимает просто уйму времени. Рабочий же вариант работы с бд в минимальной комплектации занимает 80 строк.

Возможно кто-то делал сам, или знает микрофреймворк где можно посмотреть реализацию защиты от csrf - мой вопрос именно в этом направлении.

А насчет защиты - подумай что будет если юзер откроет другую вкладку с твоим сайтом а потом закроет и захочет запостить форму, которая была открыта до этого. Или не вкладка, а аякс запрос.
Что нужно доработать?
 

Фанат

oncle terrible
Команда форума
Заметь, он не сказал "доработай". Он употребил другой глагол.
 

Adelf

Administrator
Команда форума
@HellWalk, давай не умничай тут :)

Ты зря так о фреймворках. Код в yii и ларке конечно неидеальный, но и не самый плохой. НО это крайне важный скилл программиста - уметь разобраться в коде. Даже если он 15 или 150 тысяч строк. Найти нужные классы, методы. Найти взаимосвязи. Возьми PHPStorm. Он позволит тебе навигацию удобную по коду.
 

Фанат

oncle terrible
Команда форума
Для начала, с чего ты взял, что этот форум для новичков?
И, если ты считаешь себя новичком, то как собираешься переставать им быть, если не будешь думать над своими ошибками?
 

WMix

герр M:)ller
Партнер клуба
Подскажите, правильно ли она сделана в этом примере:
вне зависимости от задачи на GET изменять состояние (в твоем случае SESSION) не советую. это не только усложняет код но и ведет к блокировочным запросам
 

HellWalk

Новичок
Второй вариант, можно открывать сколько угодно страниц с формой - везде будет корректная проверка. Но, при каждом обновлении страницы создается новая запись - массив $_SESSION захламляется данными, не знаю, на сколько это плохо.

PHP:
<?php

function getRandomString($length = 15) {
    $chars = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    $numChars = strlen($chars);
    $string = '';
    for ($i = 0; $i < $length; $i++) {
        $string .= substr($chars, rand(1, $numChars) - 1, 1);
    }
    return $string;
}

session_start();

if ($_POST) {

    $token = explode('_', $_POST['csrf']);

    if (hash_equals($token[0].'_'.$token[1].'_'.$token[2], $_SESSION['csrf'].'_'.$token[1].'_'.$_SESSION['form'][$token[1]])) {
        echo '<p>Токены идентичны. Ваши данные приняты.</p>';
    } else {
        echo '<p>Токены не совпадают. Ваши данные отклонены.</p>';
    }
} else {

    // Создаем ключ сесси, если его нет. Не перезаписывается
    if (!isset($_SESSION['csrf'])) {
        $_SESSION['csrf'] = getRandomString();
    }

    // Создаем массив с хешами форм - при каждом обновлении страницы с формой создается новая запись со своим хешем
    for ($i = 0;; $i++) {
        if (!isset($_SESSION['form'][$i])) {
            $_SESSION['form'][$i] = getRandomString();
            break;
        }
    }
}

?>

<form method="POST" action="">
    <input type="hidden" name="csrf" value="<?= $_SESSION['csrf'].'_'.$i.'_'.$_SESSION['form'][$i] ?>">

    <label for="name">Имя:</label>
    <input id="name" maxlength='15' value='' name='Name'  />

    <input type="submit">
</form>
Возьми PHPStorm. Он позволит тебе навигацию удобную по коду.
Сижу на phpStorm. Пару часов поковырялся в том, как генерируются и проверяются csrf-токены... ничего не понятно. Может для кого-то потратить пару недель, чтобы разобраться в каком-то одном элементе фреймворка это нормально, но я ищу пути решения задач побыстрее.
 

WMix

герр M:)ller
Партнер клуба
PHP:
if ($_POST) {
//...
} else {
    if (!isset($_SESSION['csrf'])) {
        $_SESSION['csrf'] = getRandomString();
    }
вне зависимости от задачи на GET изменять состояние (в твоем случае SESSION) не советую. это не только усложняет код но и ведет к блокировочным запросам
10 асинхронных GET запросов выстроятся в очередь, ожидая разблокировки файла сессии - результат timeout
 

HellWalk

Новичок
for ($i = 0;; $i++) {

Ты хоть запускал?
Что вам так не понравилось в этой строчке?

10 асинхронных GET запросов выстроятся в очередь, ожидая разблокировки файла сессии - результат timeout
Вы предлагаете хранить токен в кукуах лучше? На сколько мне известно, на куках не сделать защиту от csrf.
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
изменять после каждого POST а не гет?
 

HellWalk

Новичок
изменять после каждого POST а не гет?
В этом случае, если открыть несколько форм в разных вкладках (и пока ничего не отправлять, т.е. будут только GET-запросы), а затем перейти во вкладку открытой первой, и попробовать отправить форму - будет ошибка. Об этом намекнул Adelf в самом начале:
что будет если юзер откроет другую вкладку с твоим сайтом а потом закроет и захочет запостить форму, которая была открыта до этого. Или не вкладка, а аякс запрос.
 

HellWalk

Новичок
@HellWalk, почему? на GET ничего не меняется же
Эм... вы код смотрели?

PHP:
if ($_POST) {
// ...
} else {
//...

    // Создаем массив с хешами форм - при каждом обновлении страницы с формой создается новая запись со своим хешем
    for ($i = 0;; $i++) {
        if (!isset($_SESSION['form'][$i])) {
            $_SESSION['form'][$i] = getRandomString();
            break;
        }
    }
}
10 асинхронных GET запросов выстроятся в очередь, ожидая разблокировки файла сессии - результат timeout
Поговорил с нашим тим-лидом по поводу опасностей использования сессий с хранением в файлах - ничего страшного, он сказал, не произойдет: "ну заблокируюется сессия пользователя, который спамит запросы, и что?"
 

Adelf

Administrator
Команда форума
Вообще, насколько я знаю в ларке например токен живет долго. вне зависимости от гетов и постов. Похоже только при первом старте сессии он генерится. и живет всю сессию.
 

WMix

герр M:)ller
Партнер клуба
@Adelf, мы то понимаем, что это все одно не решает проблему, (так сложность захода увеличивает) ну да пусть, мне бы намекнуть чтоб обращали на внимание на яму которую себе копают
"ну заблокируюется сессия пользователя, который спамит запросы, и что?"
да просто, нахрен этот ajax нужен
 
Сверху