Сообщений 10    Оценка 77 [+1/-0]         Оценить  
Система Orphus

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

Автор: Landgraph
www.landgraph.ru
Опубликовано: 11.01.2008
Исправлено: 10.12.2016
Версия текста: 1.0
Введение
Чем грозит ошибка?
Как исправить?
Заключение

Введение

Очень часто при анализе сторонних скриптов обнаруживается одна и та же распространенная ошибка: отсутствие проверки передаваемых данных в «неизменяемых» полях, таких как <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>
<selectname="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 (рисунок 1).


Рисунок 1.

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


Рисунок 2.

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


Рисунок 3.

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


Рисунок 4.

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


Рисунок 5.

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

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

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

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

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

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

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

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

<form action="test.php"method="post">
Выберите пункт<br>
<selectname="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>
<selectname="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>, но и для любых других, где вы явно задаете выбор из возможных вариантов.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 10    Оценка 77 [+1/-0]         Оценить