Безопасность это горячая тема. Обеспечение безопасности ваших сайтов чрезвычайно важно для любого веб-приложения. На самом деле, я трачу 70% своего времени на защиту своих приложений. Одной из самых важных вещей, которые мы должны обеспечить, являются формы. Сегодня мы рассмотрим метод предотвращения XSS (межсайтовый скриптинг) и подделки межсайтовых запросов в формах.
Почему?
Данные POST могут быть отправлены с одного сайта на другой. Почему это плохо? Простой сценарий …
Пользователь, вошедший на ваш сайт, во время сеанса посещает другой сайт. Этот веб-сайт сможет отправлять данные POST на ваш веб-сайт, например, с помощью AJAX. Поскольку пользователь вошел в систему на вашем сайте, другой веб-сайт также сможет отправлять данные публикации в защищенные формы, которые доступны только после входа в систему.
Мы также должны защитить наши страницы от атак с использованием cURL
Как мы это исправим?
С ключами формы! Мы добавим специальный хеш (ключ формы) к каждой форме, чтобы гарантировать, что данные будут обрабатываться только тогда, когда они были отправлены с вашего веб-сайта. После отправки формы наш PHP-скрипт проверит отправленный ключ формы по ключу формы, который мы установили в сеансе.
Что мы должны сделать:
- Добавьте ключ формы к каждой форме.
- Сохраните ключ формы в сеансе.
- Подтвердите ключ формы после отправки формы.
Шаг 1: Простая форма
Сначала нам нужна простая форма для демонстрационных целей. Одна из наиболее важных форм, которую мы должны защитить, — это форма для входа. Форма входа уязвима для атак методом перебора . Создайте новый файл и сохраните его как index.php в своем веб-корне. Добавьте следующий код в тело:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
<!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-файле поместите следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
<?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 (), пользователь не может видеть, что мы использовали для генерации ключа. Вся функция:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
//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 и в сеансе.
- Выведите поле HTML.
Мы называем нашу функцию outputKey () и делаем ее общедоступной, потому что мы должны использовать ее вне нашего класса. Наша функция вызовет приватную функцию generateKey (), чтобы сгенерировать новый ключ формы и сохранить его локально в сеансе. Наконец, мы создаем код XHTML. Теперь добавьте следующий код в наш класс PHP:
1
|
//Function to output the form key public function outputKey() { //Generate the key and store it inside the class $this->formKey = $this->generateKey();
|
Теперь мы собираемся добавить ключ формы в нашу форму входа, чтобы защитить его. Мы должны включить класс в наш файл index.php . Мы также должны начать сеанс, потому что наш класс использует сеансы для хранения сгенерированного ключа. Для этого мы добавим следующий код над тегом doctype и head:
1
2
3
4
5
6
7
8
|
<?php
//Start the session
session_start();
//Require the class
require(‘formkey.class.php’);
//Start the class
$formKey = new formKey();
?>
|
Код выше довольно понятен. Мы запускаем сессию (потому что мы храним ключ формы) и загружаем файл класса PHP. После этого мы запускаем класс с новой formKey () , это создаст наш класс и сохранит его в $ formKey . Теперь нам нужно только отредактировать нашу форму, чтобы она содержала ключ формы:
1
2
3
4
5
6
7
8
9
|
<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 () , нам нужно только включить ее в нашу форму. Мы можем использовать ключи формы в любой форме, просто добавив <? Php $ formKey-> outputKey (); ?> Теперь просто просмотрите исходный текст вашей веб-страницы, и вы увидите, что к форме прикреплен ключ формы. Единственным оставшимся шагом является проверка запросов.
Шаг 4: Проверка
Мы не будем проверять всю форму; только ключ формы. Проверка формы — это базовый PHP, а учебники можно найти по всему Интернету. Давайте проверим ключ формы. Поскольку наша функция «generateKey» перезаписывает значение сеанса, мы добавляем конструктор в наш класс PHP. Конструктор будет вызван, когда наш класс будет создан (или создан). Конструктор сохранит предыдущий ключ внутри класса, прежде чем мы создадим новый; поэтому у нас всегда будет предыдущий ключ формы для проверки нашей формы. Если мы этого не сделаем, мы не сможем проверить ключ формы. Добавьте следующую функцию PHP в ваш класс:
1
2
3
4
5
6
7
8
9
|
//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:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
//Function that validated the form key POST data
public function validate()
{
//We use the old formKey and not the new generated version
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 ();
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
$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 вы можете разместить следующий код для отображения сообщения об ошибке:
1
|
<div><?php if($error) { echo($error);
|
Это отобразит переменную $ error, если она установлена.
Если вы запустите свой сервер и зайдете в index.php , вы увидите нашу форму и сообщение «Нет ошибок». Когда вы отправляете форму, вы увидите сообщение «Ошибка ключа формы отсутствует», поскольку это действительный запрос POST. Теперь попробуйте перезагрузить страницу и примите запрос, когда ваш браузер запросит повторную отправку данных POST. Вы увидите, что наш скрипт вызывает сообщение об ошибке: «Ошибка ключа формы!» Ваша форма теперь защищена от ввода с других сайтов и ошибок при перезагрузке страницы! Ошибка также отображается после обновления, поскольку новый ключ формы был сгенерирован после того, как мы отправили форму. Это хорошо, потому что теперь пользователь не может случайно опубликовать форму дважды.
Полный код
Вот весь код PHP и HTML:
index.php
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
<?php
//Start the session
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
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
<?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 session
$_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 version
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 Nonces
- Семь привычек для написания безопасных приложений PHP
- Подпишитесь на нас в Твиттере или подпишитесь на RSS-канал NETTUTS, чтобы получать ежедневные обзоры и статьи о веб-разработке.