Защитите свои формы с помощью форм-ключей

8 января 2018

Безопасность - это горячая тема. Обеспечение безопасности ваших веб-сайтов чрезвычайно важно для любого веб-приложения. Фактически, я трачу 70% своего времени на обеспечение своих приложений. Одной из наиболее важных вещей, которые мы должны обеспечить, являются формы. Сегодня мы рассмотрим метод предотвращения XSS (межсайтовый скриптинг) и подделок подбора сайтов на бланках.

Зачем?

POST-данные могут быть отправлены с одного сайта на другой. Почему это плохо? Простой сценарий... Пользователь

A, вошедший на ваш сайт, посещает другой сайт во время его сеанса. Этот веб-сайт сможет отправлять данные POST на ваш сайт - например, с AJAX. Поскольку пользователь зарегистрирован на вашем сайте, другой веб-сайт также сможет отправлять почтовые данные в защищенные формы, которые доступны только после входа в систему.

Мы также должны защищать наши страницы от атак с помощью cURL

Как мы это исправим?

С помощью клавиш формы! Мы добавим специальный хеш (ключ формы) к каждой форме, чтобы убедиться, что данные будут обрабатываться только тогда, когда они были отправлены с вашего сайта. После отправки формы наш PHP-скрипт будет проверять представленный ключ формы на тот ключ формы, который мы установили в сеансе.

Что мы должны делать:

    Добавить ключ формы к каждой форме.nУстановить ключ формы в session.nУказать ключ формы после формы submit.

Шаг 1: Простая форма

Сначала нам нужна простая форма для демонстрационных целей. Одной из наиболее важных форм, которую мы должны обеспечить, является форма входа в систему. Форма входа является уязвимой атакой с использованием силы атаки. Создайте новый файл и сохраните его как index.php в своем веб-корне. Добавьте следующий код в тело:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <title>Securing forms with form keys</title>
</head>
<body>
    <form action="" method="post">
    <dl>
        <dt><label for="username">Username:</label></dt>
        <dd><input type="text" name="username" id="username" /></dd>
        <dt><label for="username">Password:</label></dt>
        <dd><input type="password" name="password" id="password" /></dd>
        <dt></dt>
        <dd><input type="submit" value="Login" /></dd>
    </dl>
    </form>
</body>
</html>

Теперь у нас есть простая страница XHTML с формой входа. Если вы хотите использовать клавиши формы на своем веб-сайте, вы можете заменить сценарий выше своей собственной страницей входа. Теперь давайте продолжим действительное действие.

Шаг 2: Создание класса

Мы собираемся создать класс PHP для наших ключей формы. Поскольку каждая страница может содержать только один ключ формы, мы можем сделать одноэлемент нашего класса, чтобы убедиться, что наш класс используется правильно. Поскольку создание синглонов является более продвинутой темой ООП, мы пропустим эту часть. Создайте новый файл с именем formkey.class.php и поместите его в свой веб-корень. Теперь нам нужно подумать о тех функциях, которые нам нужны. Во-первых, нам нужна функция для генерации ключа формы, чтобы мы могли разместить его в нашей форме. В вашем файле PHP введите следующий код:

<?php
//You can of course choose any name for your class or integrate it in something like a functions or base class
class formKey
{
    //Here we store the generated form key
    private $formKey;
    //Here we store the old form key (more info at step 4)
    private $old_formKey;
    //Function to generate the form key
    private function generateKey()
    {
    }
}
?>

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

Теперь мы должны думать о способе генерации ключа формы. Поскольку наш ключ формы должен быть уникальным (в противном случае у нас нет никакой безопасности), мы используем комбинацию IP-адреса пользователей для привязки ключа к пользователю, mt_rand (), чтобы сделать его уникальным, а функция uniqid () чтобы сделать его еще более уникальным. Мы также шифруем эту информацию с помощью md5 (), чтобы создать уникальный хеш, который мы можем вставить в наши страницы. Поскольку мы использовали md5 (), пользователь не может видеть, что мы использовали для генерации ключа. Вся функция:

//Function to generate the form key
private function generateKey()
{
    //Get the IP-address of the user
    $ip = $_SERVER['REMOTE_ADDR'];
    //We use mt_rand() instead of rand() because it is better for generating random numbers.
    //We use 'true' to get a longer string.
    //See http://www.php.net/mt_rand for a precise description of the function and more examples.
    $uniqid = uniqid(mt_rand(), true);
    //Return the hash
    return md5($ip . $uniqid);
}

Вставьте код, указанный выше, в файл formkey.class.php. Замените функцию новой функцией.

Шаг 3: Вставка ключа формы в нашу форму

Для этого шага мы создаем новую функцию, которая выводит скрытое поле HTML с помощью нашего ключа формы. Функция состоит из трех этапов:

    Подробить ключ формы с помощью нашей функции generateKey (). Сохраните ключ формы в нашей переменной $ formKey и в session.nОткройте поле HTML.

Назовите нашу функцию outputKey () и сделать его общедоступным, потому что мы должны использовать его вне нашего класса. Наша функция вызовет частную функцию generateKey (), чтобы сгенерировать новый ключ формы и сохранить его локально в сеансе. Наконец, мы создаем код XHTML. Теперь добавьте следующий код внутри нашего класса PHP:

//Function to output the form key
public function outputKey()
{
    //Generate the key and store it inside the class
    $this->formKey = $this->generateKey();
    //Store the form key in the sessio
    $_SESSION['form_key'] = $this->formKey;
    //Output the form key
    echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
}

Теперь мы добавим ключ формы к нашей форме входа, чтобы защитить его. Мы должны включить класс в наш файл index.php. Мы также должны начать сеанс, потому что наш класс использует сеансы для хранения сгенерированного ключа. Для этого мы добавляем следующий код над тегом doctype и head:

<?php
//Start the sessio
session_start();
//Require the class
require('formkey.class.php');
//Start the class
$formKey = new formKey();
?>

Вышеприведенный код довольно понятен. Мы запускаем сеанс (потому что мы храним ключ формы) и загружаем файл класса PHP. После этого мы запускаем класс с помощью new formKey (), это создаст наш класс и сохранит его в $ formKey. Теперь нам нужно только отредактировать нашу форму, чтобы она содержала ключ формы:

<form action="" method="post">
<dl>
    <?php $formKey->outputKey(); ?>
    <dt><label for="username">Username:</label></dt>
    <dd><input type="text" name="username" id="username" /></dd>
    <dt><label for="username">Password:</label></dt>
    <dd>input type="password" name="password" id="password" /></dd>
<dl>
</form>

И это все! Поскольку мы создали функцию outputKey (), нам нужно включить ее в нашу форму. Мы можем использовать ключи формы в каждой форме, просто добавивoutputKey ();?> Теперь просто просмотрите источник своей веб-страницы, и вы увидите, что к форме добавлен ключ формы. Единственным оставшимся шагом является проверка запросов.

Шаг 4: Проверка

Мы не будем проверять всю форму; только ключ формы. Проверка формы - это базовый PHP, а учебные пособия можно найти по всему Интернету. Давайте проверим ключ формы. Поскольку наша функция «generateKey» перезаписывает значение сеанса, мы добавляем конструктор в наш класс PHP. Конструктор будет вызываться, когда наш класс будет создан (или построен). Конструктор сохранит предыдущий ключ внутри класса, прежде чем мы создадим новый; поэтому у нас всегда будет предыдущий ключ формы для проверки нашей формы. Если бы мы этого не сделали, мы бы не смогли проверить ключ формы. Добавьте в свой класс следующую функцию PHP:

//The constructor stores the form key (if one exists) in our class variable.
function __construct()
{
    //We need the previous key so we store it
    if(isset($_SESSION['form_key']))
    {
        $this->old_formKey = $_SESSION['form_key'];
    }
}

Конструктор всегда должен называться __construct (). Когда вызывается конструктор, мы проверяем, установлен ли сеанс, и если это так, мы храним его локально в нашей переменной old_formKey.

Теперь мы можем проверить наш ключ формы. Мы создаем базовую функцию внутри нашего класса, которая проверяет ключ формы. Эта функция также должна быть общедоступной, потому что мы будем использовать ее вне нашего класса. Функция будет проверять значение POST ключа формы на сохраненное значение ключа формы. Добавьте эту функцию в класс PHP:

//Function that validated the form key POST data
public function validate()
{
    //We use the old formKey and not the new generated versio
    if($_POST['form_key'] == $this->old_formKey)
    {
        //The key is valid, return true.
        return true;
    }
    else
    {
        //The key is invalid, return false.
        return false;
    }
}

Внутри index.php мы проверяем ключ формы, используя функцию, которую мы только что создали в нашем классе. Конечно, мы проверяем только после запроса POST. Добавьте следующий код после $ formKey = new formKey ();

$error = 'No error';
//Is request?
if($_SERVER['REQUEST_METHOD'] == 'post')
{
    //Validate the form key
    if(!isset($_POST['form_key']) || !$formKey->validate())
    {
        //Form key is invalid, show an error
        $error = 'Form key error!';
    }
    else
    {
        //Do the rest of your validation here
        $error = 'No form key error!';
    }
}

Мы создали переменную $ error, которая хранит наше сообщение об ошибке. Если запрос POST был отправлен, мы проверяем наш ключ формы с помощью $ formKey-> validate (). Если это возвращает false, ключ формы недействителен, и мы выводим сообщение об ошибке. Обратите внимание, что мы проверяем только ключ формы - вы должны сами проверить остальную часть формы.

В своем HTML вы можете поместить следующий код, чтобы отобразить сообщение об ошибке:

<div><?php if($error) { echo($error); } ?></div>

Это будет отражать переменную $ error, если она установлена.

Если вы запустите свой сервер и перейдите на index.php, вы увидите нашу форму и сообщение «Нет ошибки». Когда вы отправляете форму, вы увидите сообщение «Нет ошибки ключа формы», потому что это действительный запрос POST. Теперь попробуйте перезагрузить страницу и принять, когда ваш браузер запросит отправку данных POST. Вы увидите, что наш скрипт вызывает сообщение об ошибке: «Ошибка ключа ключа!» Ваша форма теперь защищена от ввода с других сайтов и ошибок при перезагрузке страницы! Ошибка также отображается после обновления, потому что после того, как мы отправили форму, был создан новый ключ формы. Это хорошо, потому что теперь пользователь не может случайно отправить форму дважды.

Полный код

Вот весь код PHP и HTML:

index.php

<?php
//Start the sessio
session_start();
//Require the class
require('formkey.class.php');
//Start the class
$formKey = new formKey();
$error = 'No error';
//Is request?
if($_SERVER['REQUEST_METHOD'] == 'post')
{
    //Validate the form key
    if(!isset($_POST['form_key']) || !$formKey->validate())
    {
        //Form key is invalid, show an error
        $error = 'Form key error!';
    }
    else
    {
        //Do the rest of your validation here
        $error = 'No form key error!';
    }
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <title>Securing forms with form keys</title>
</head>
<body>
    <div><?php if($error) { echo($error); } ?>
    <form action="" method="post">
    <dl>
        <?php $formKey->outputKey(); ?>
        <dt><label for="username">Username:</label></dt>
        <dd><input type="text" name="username" id="username" /></dd>
        <dt><label for="username">Password:</label></dt>
        <dd><input type="password" name="password" id="password" /></dd>
        <dt></dt>
        <dd><input type="submit" value="Submit" /></dd>
    <dl>
    </form>
</body>
</html>

fomrkey.class.php

<?php
//You can of course choose any name for your class or integrate it in something like a functions or base class
class formKey
{
    //Here we store the generated form key
    private $formKey;
    //Here we store the old form key (more info at step 4)
    private $old_formKey;
    //The constructor stores the form key (if one excists) in our class variable
    function __construct()
    {
        //We need the previous key so we store it
        if(isset($_SESSION['form_key']))
        {
            $this->old_formKey = $_SESSION['form_key'];
        }
    }
    //Function to generate the form key
    private function generateKey()
    {
        //Get the IP-address of the user
        $ip = $_SERVER['REMOTE_ADDR'];
        //We use mt_rand() instead of rand() because it is better for generating random numbers.
        //We use 'true' to get a longer string.
        //See http://www.php.net/mt_rand for a precise description of the function and more examples.
        $uniqid = uniqid(mt_rand(), true);
        //Return the hash
        return md5($ip . $uniqid);
    }
    //Function to output the form key
    public function outputKey()
    {
        //Generate the key and store it inside the class
        $this->formKey = $this->generateKey();
        //Store the form key in the sessio
        $_SESSION['form_key'] = $this->formKey;
        //Output the form key
        echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
    }
    //Function that validated the form key POST data
    public function validate()
    {
        //We use the old formKey and not the new generated versio
        if($_POST['form_key'] == $this->old_formKey)
        {
            //The key is valid, return true.
            return true;
        }
        else
        {
            //The key is invalid, return false.
            return false;
        }
    }
}
?>

Заключение

Добавление этого кода в каждую важную форму на вашем сайте значительно увеличит безопасность вашей формы. Это даже останавливает освежающие проблемы, как мы видели в шаге 4. Поскольку ключ формы действителен только для одного запроса, двойной пост невозможен.

Это был мой первый учебник, я надеюсь, вам понравится и использовать его для улучшения вашей безопасности! Пожалуйста, дайте мне знать свои мысли, используя комментарии. Есть лучший метод? Дайте нам знать.

Дальнейшее чтение

WordPress также использует формы ключей (называя его Nonces): Wordpress NoncesSeven привычки для написания защищенных приложений PHP

Найти нас в Twitter или подписаться на RSS-канал NETTUTS для более ежедневных веб-разработок tuts и articles.