Скрытая угроза: «неизменяемые» поля

Введение

Очень часто при анализе сторонних скриптов обнаруживается одна и та же распространенная ошибка: отсутствие проверки передаваемых данных в «неизменяемых» полях, таких как <input type=”hidden”>, <input type=”radio”>, <input type=”checkbox”> и, конечно же, <select></select>.

Интересно то, что никто не забывает проверять переданные данные полей, которые пользователь задает явно в тех или иных окнах ввода: <input type=”text”>, <textarea></textarea>. Здесь все отлично помнят, что переданные данные необходимо проверить на соответствие шаблону, или, хотя бы, на длину:

if(strlen($_REQUEST[‘field’])>64)
    {die(‘Слишком длинная строка!’);}

Почему-то начинающие и более опытные программисты, считают, что «неизменяемые» явно поля – никак нельзя отредактировать. Поверьте, это далеко не так!

Если вы на форме указываете «неизменяемое» поле – потрудитесь проверить, правильное значение передано скрипту или подложное. Вот вам простой пример, как можно стандартными средствами Mozilla Firefox «обмануть» скрипт.

Создадим файл HTML с формой выбора суммы, которую мы хотим внести на счет:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
	<title>Тест неизменяемых полей</title>
</head>
<body>
<form action="test.php" method="post">
Какую сумму вы хотите внести на счет?<br>
<select name="testselect">
    <option value="500">500 рублей</option>
    <option value="1000">1000 рублей</option>
</select>
<input type="submit" value="Внести сумму">
</form>
</body>
</html>

Казалось бы, что можно сделать? Ведь пользователь может выбрать только два варианта, 500 и 1000. Не все так просто, обычной проверкой «переданности» переменной обойтись нельзя, даже если вы явно указываете массив переменных $_POST, а не $_REQUEST.

if(empty($_POST[‘testselect’]))
    {die(‘Не указана сумма для внесения на счет!’);}

Некоторые, проверяют еще поле referer, чтобы удостовериться, что страница не была сохранена на локальный компьютер и потом отправлен запрос скрипту с «подправленными» данными.

if($_SERVER[‘HTTP_REFERER’]!=’http://www.landgraph.ru/script/test.html’)
    {die(‘Выполнение скрипта запрещено!’);}

Что теперь может сделать взломщик? Кроме варианта вручную написать запрос к скрипту и передать «левые» данные существует еще одна интересная возможность. Открываем нужную страницу в браузере FireFox.


Кажется, скрипт хорошо защищен от внешнего воздействия, потому что пользователь «не может» исправить данные. Но, в меню «Инструменты» есть такая вещь – DOM Inspector. Открываем его.


Нажимаем на первую кнопку слева, потом на наше поле выбора на странице. Смотрим что у нас теперь в инспекторе DOM:


Видим наше поля выбора и оба варианта – в виде объектов Option. Выбираем первый из них.


И в правой части окна мы видим значение этого варианта – 500. Жмем на нем правой кнопкой мыши и выбираем в открывшемся меню пункт Edit, в открывшемся окне пишем 1, нажимам OK, закрываем инспектор объектов и нажимаем кнопку «Внести сумму».


Как вы думаете, что мы увидим? Правильно, скрипт отработал и внес нам на счет 1 рубль, вместо «выбранных» 500 рублей!


Мы не использовали ничего, кроме стандартных средств предоставляемых браузером и «кинули» систему на 499 рублей.

Чем грозит ошибка?

Ну и чем это грозит, спросите вы, ведь все равно деньги зачислены на счет! А представьте, если бы это была сумма, которую нужно заплатить за товар, и вместо 500 рублей вам заплатили рубль и «купили» вашу разработку?

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

Вот вам пример из жизни, который имел место быть. Мне нужно было скачать одну песню. Сайт, на котором она лежала, целиком и полностью платный. Но проблема была не в деньгах, а в их количестве, которое запрашивал сайт. Песня стоит 10 центов, а минимальная сумма пополнения счета, которую мне предлагает внести система 10 долларов! Конечно же, такую сумму вносить на счет из-за одной песни очень не хотелось. Описанным выше способом я поменял в одном из полей выбора 10 долларов на 1 доллар… И что вы думаете? Система спокойно выписала мне счет на 1 доллар, который я оплатил и скачал себе необходимую песню! В итоге, владелец сайта недополучил 9 долларов. Конечно же, для меня это плюс, но владелец врядли так считает.

Как исправить?

Исправление очень простое – проверять все поля, которые передает пользователь на правильность данных. Сделать это можно несколькими способами. Способ первый, когда передается числовое значение в заданном диапазоне, например, форма выглядит так:

<form action="test.php" method="post">
Выберите пункт<br>
<select name="testselect">
    <option value="1">Пункт 1</option>
    <option value="2">Пункт 2</option>
    <option value="3">Пункт 3</option>
</select>
<input type="submit" value="Выбор">
</form>

Нам необходимо удостовериться, что пользователь выбрал именно предложенный пункт:

if(empty($_POST[‘testselect’])||
intval($_POST[‘testselect’])!=$_POST[‘testselect’]||
$_POST[‘testselect’]<1||
$_POST[‘testselect’]>3)
    {die(‘Нет такого пункта!’);}

Логика такая: если переданное поле пусто, или передано не число, или переданное число меньше 1, или переданное число больше 3 – вывести сообщение об ошибке.

Другой вариант – если передается произвольный набор чисел, которые не попадают под определение непрерывного диапазона, или возможен выбор текстовых вариантов, например:

<form action="test.php" method="post">
Выберите пункт<br>
<select name="testselect">
    <option value="1">Пункт 1</option>
    <option value="5">Пункт 5</option>
    <option value="asd">Пункт 3</option>
</select>
<input type="submit" value="Выбор">
</form>

Либо необходимо все записать в виде

if(empty($_POST[‘testselect’])||
($_POST[‘testselect’]!=1&&$_POST[‘testselect’]!=5&&$_POST[‘testselect’]!=’asd’))
    {die(‘Нет такого пункта!’);}

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

$variants=Array(1,5,’asd’);
if(empty($_POST[‘testselect’])||!in_array($_POST[‘testselect’], $variants))
    {die(‘Нет такого пункта!’);}

Если же варианты лежат в БД, то необходимо сделать выборку из таблицы и проверить существование переданного варианта:

$query=mysql_query(‘SELECT `id` FROM `variants` WHERE `id`=”’.
(get_magic_quotes_gpc() ? $_POST[‘testselect’]:addslashes($_POST[‘testselect’])).’”’);
if(mysql_num_rows($query)<=0)
    {die(‘Нет такого пункта!’);}
mysql_free_result($query);

Заключение

Конечно, кроме описанных выше вариантов проверки правильности ввода «неизменяемых» полей существуют и другие. Но, важно запомнить, что неизменяемых полей нет! Так или иначе они могут быть изменены, и вы, или ваш заказчик, может понести убытки из-за того что вы решили, что пользователь выберет именно предложенный вами вариант, а не подставит свой. Все, написанное выше, справедливо не только для полей <select></select>, но и для любых других, где вы явно задаете выбор из возможных вариантов.



Опубликовал admin
24 Сен, Понедельник 2007г.



Программирование для чайников.