Разработка Faq по безопасности Php сайтов
Каждым новым постом пишем новый пункт с темой и подробным описанием. Последнее может быть с вставками в тегах [ PHP ] и пр. Пост по обсуждению определенного пункта надо писать с темой "Re: тема пункта", чтобы не запутаться.
На выходе мы будем иметь нечто среднее между мануалом, FAQ и книжечкой. Так что не смущайтесь, что в сабже звучит просто "FAQ" :)
Поехали!
Ни когда недоверяйте юзеру. Иначе горько поплатитесь за свою доверчивость. Даже если не по злому умыслу, а исключительно по криворукости он может ввести ахинею которая выдыст ошибку (это в лучшем случае), а то и совершенно завалит базу данных. И так приступим...
Вопервых никогда не доверяйте ограничение количества вводимых символов тегу:
При передачи данных формы в скрипт нужно всегда обрезать лишние символы:
таким образом вы гарантируете себе правильность длинны полученных данных.
Второе всегда проверяйте полученные данные на наличие спецсимволов. Таким образом вы присечете вставку в ваш скрип потенциально опастных добавлений. Именно с помощью них поломаны большинство сайтов. И так пишем...
теперь если злобный хацкер попробует таким образом взломать ваш сайт, мы пресечем его действия на корню. Теперь делаем проверку существует ли переменая $usrname:
//выполняем дольнейшие проверки и саму программу
}
else{
echo "Access is refused. Hi, hazker!!!";
}
Для проверки e-mail:
Продолжение следует ....
if (SAFETY=='safety')
{
code....
}
else
{
echo "хакер выпей ЙАду";
}
нужно проверять полученные данные на
1. "плохие" символы
2. соответсвие типов (можно даже придумать тип имыло 8-))
3. длинну переменной
Например, при обращении пользователя к страницам, требующим авторизации, использую проверку, скажем пароля на каждой странице.
{
if (!Auth_User($SESSION_LOGIN,$SESSION_PASSWORD))
exit(header("Location: noway.php"));
}
esle
exit(header("Location: noway.php"));
где
Auth_User (упрощенный вариант):
{
if($log=="admin"&&$pwd="admin")
return(TRUE);
else
return(FALSE);
}
Для того чтобы окончательно увеличить безопасность и исключить возможность подбора переменных и значений злоумышленником а адресной строке (что и так маловероятно), предлагаю сначала удалять переменные (unset)? а только затем запускать сессию:
unset($SESSION_LOGIN);
unset($SESSION_PASSWORD);
/*теперь запустим сессию и будем польховаться переменными из сесии (туда могли положить только мы)*/
session_start();
if(isset($SESSION_LOGIN)&&isset($SESSION_PASSWORD))
{
if (!Auth_User($SESSION_LOGIN,$SESSION_PASSWORD))
exit(header("Location: noway.php"));
}
else
exit(header("Location: noway.php"));
Регистрировать переменны в сессии так (предлагаю называть их так, чтобы самому не запутаться что они лежат в сесии-так удобнее):
$SESSION_PASSWORD=$password;
session_register("SESSION_LOGIN");
session_register("SESSION_PASSWORD");
пример :
{
global $_POST, $_GET;
if (isset($_POST[$var_name]) && (!empty($_POST[$var_name]) || $_POST[$var_name]==0))
{
if (Valid_NonZero($_POST[$var_name]) && Valid_String($_POST[$var_name]))
{
return $_POST[$var_name];
}
else
{
return false;
}
}
else if (isset($_GET[$var_name]) && (!empty($_GET[$var_name]) || $_GET[$var_name]==0))
{
if (Valid_NonZero($_POST[$var_name]) && Valid_String($_POST[$var_name]))
{
return $_GET[$var_name];
}
else
{
return false;
}
}
else
{
return false;
}
}
$LongDescr_en = InitialVar('LongDescr_en');
$Item_Name = InitialVar('Item_Name');
причем получается не важно как полученны переменные, через GET или POST.
Зачем использовать функции? допустим у вас в каком нибудь модуле написана работа с файлами, если запустить этот модуль без нужных переменных он скорее всего не отработает и выдаст ошибку в описании которой будут нужные переменные, которые можно передать в этот скрипт и... вско может быть 8-)
1. Для строковых переменных:
// Как правило можно ограничиться ограничением БД MySQL
// при создании поля: `comments` varchar(127) NOT NULL default '';
$comments = substr($HTTP_POST_VARS['comments'], 0, 127);
// Обязательно делаем проверку строки на спецсимволы
// и заменяем их на приемлимые.
// функцией mysql_escape_string
// !-! как результат в БД будет промещена та же строка,
// mysql_escape_string "экранирует" опасные символы
$sqlstring = "INSERT INTO table1 VALUES comments=\"".mysql_escape_string($comments)."\"";
2. Для числовых переменных:
// которые должны содержаться в нем - это [0-9]+
$myinteger = trim($myinteger);
$myinteger = preg_match("/^[0-9]+$/i", $myinteger) ? $myinteger : 0;
Помните, что даже если Вы считаете, что в определенной части кода просто не может возникнуть ситуации, когда хакер получает доступ к БД или к содержимому серверных файлов, это далеко не всегда так!
И использовать только один файл (index.php) для запросов к сайту, а уже этот файл будет решать какие подключать модули и какие функции вызывать.
Совершенно точно. Для того, чтобы быть абсолютно уверенным, что хакер не сможет подключиться к подчиненному файлу напрямую с заданными переменными, которые модуль сочтет за глобальные данные основного файла, необходимо использовать следующую конструкцию:
// Объявление константы. Как известно, константу невозможно передать в строке
// запроса, а значит мы в безопасности.
DEFINE('_MY_SITE_CHECKER', true);
// Код для ВСЕХ (!!) остальных php файлов сайта
// Проверка, объявлена ли константа?
// Если не объявлена, значит index.php не был вызван.
// Значит нас любят злобные хакеры. Так их:
defined( '_MY_SITE_CHECKER' )
or die( 'Direct Access to this location is not allowed.' );
с точки зрения администрирования системы безопаснее использовать PHP через CGI, а не как модуль Apache. в этом случае, безопасность сайта будет зависить от правильно выставленых прав UNIX. платой будет более низкая скорость работы и более сложное использование конечными юзерами.
с точки зрения администрирования системы безопаснее использовать PHP через CGI, а не как модуль Apache. в этом случае, безопасность сайта будет зависить от правильно выставленых прав UNIX. платой будет более низкая скорость работы и более сложное использование конечными юзерами.
в любом случае безопасность сайта будет зависить от правильно выставленых прав UNIX. Только в случае апача, права настраиваются для пользователя www_data(в эту группу входит апач) (имя группы как пример). К тому же для апача есть модуль su exec который позволяет запускать апач от имени конкретного пользователя, а не как www_data
в любом случае безопасность сайта будет зависить от правильно выставленых прав UNIX. Только в случае апача, права настраиваются для пользователя www_data(в эту группу входит апач) (имя группы как пример)
мда? в случае модуля апач все скрипты будут выполняться от пользователя апача (nobody, www, etc) как вы и сказали. а в случае CGI - можно выставлять права отдельным юзерам. пример - хостинговая площадка.
Если файл не должен вызываться напрямую:
1. Его можно положить в отдельный каталог и запретить к нему доступ с помощью .htaccess
2. Можно положить этот файл в каталог не доступный веб-серверу.
3. Можно использовать префикс в названии файла, и файлы с этим префиксом запретить в .htaccess
3. Можно назвать его начиная с .ht - это префикс в Apache запрещен по умолчанию.
Когда Вы используете один из этих способов, всегда проверяйте, работает он или нет.
-----------------------------------------------
Проверять переменные можно по упрощенной схеме, например с помощью функций intval и doubleval
То есть вместо:
// которые должны содержаться в нем - это [0-9]+
$myinteger = trim($myinteger);
$myinteger = preg_match("/^[0-9]+$/i", $myinteger) ? $myinteger : 0;
можно использовать:
Кроме того, иногда проще использовать такие конструкции:
$public=$_POST["public"]=="yes"?"yes":"no";
?>
-----------------------------------------------
При отправке сообщений через форму по почте, обязательно проверйте все почтовые поля на наличие переноса не только в конце, но и внутри:
Например, если указать в поле обратный адрес (или в теме):
То может получится такое письмо:
To: админ@codenet.ru
BCC: [email]test1@codenet.ru[/email]
Subject: ля-ля-ля
само письмо.
Таким образом, письмо уйдет не только на админ@codenet.ru, но еще и на [email]test1@codenet.ru[/email]
-----------------------------------------------
При доблении в базу, всегда используйте mysql_escape_string().
htmlspecialchars() и StripSlashes() лучше всего использовать только при выводе в браузер
$exists=1;
else
$exists=0;
if($exists!=1) {
echo("Access is refused. Hi, hazker!!!");
exit;
}
К томуже я предпочитаю использовать конструкцию:
в этом случае если вы используете еще гдето повторное подключение этого модуля ошибки не возникнет.
Продолжение следует.....
Поэтому и были созданы так называемые "сессии", которые позволяют "окольными путями" вести вместе с Клиентом по сайту дополнительную информацию, такую как, например, логин и пароль (если речь идет о пользователе), данные о реферале, какой-нибудь ключ и многое другое.
PHP имеет свой собственный движок сессий. Он достаточно удачен, но имеет некоторые недостатки, о которых мы сейчас не будем говорить. Давайте посмотрим как написать свой собственный набор функций по работе с сессиями.
Для начала, мы предполагаем что у нас есть база данных, к которой мы подключены. И что у нас есть объект $database с небольшим набором методов (функций объекта) для начала. Я предлагаю работать через объект, поскольку тип БД может варьироваться, а подобный объект написать не представляет особенной сложности.
А писать мы будем сессии для авторизованного пользователя, т.е. наиболее популярный вариант.
Поехали! (см. ниже)
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
`ip` varchar(15) NOT NULL default '',
`sid` varchar(32) NOT NULL default '',
`created` datetime default NULL,
`access` datetime default NULL,
UNIQUE KEY `sid` (`sid`)
) TYPE=MyISAM AUTO_INCREMENT=8256112 ;
Далее идут функции:
//
// Control access functions
//
//
// func. checks is active current session key and results true if yes
function isSession( $sid ) {
global $database;
$database->setQuery("SELECT id, sid, ip, UNIX_TIMESTAMP(NOW(NULL))-UNIX_TIMESTAMP(access) as howold, created, access"
. "\nFROM sessions WHERE sid='".mysql_escape_string($sid)."'");
list( $row ) = $database->loadObjectList();
if ($database->getErrorNum()) {
echo $database->stderr();
return false;
}
// is row with this key exists?
if ($row->id) {
// isn't it expires?
if (($row->howold >= 0) && ($row->howold < 3600)) {
// has it same IP that previous?
if ($row->ip == $_SERVER['REMOTE_ADDR']) {
return true;
}
} else {
deleteSession( $sid );
}
}
return false;
}
// Destroys session with this key
function deleteSession( $sid ) {
global $database;
$database->setQuery( "DELETE FROM sessions WHERE sid='".mysql_escape_string($sid)."'" );
if (!$database->query()) {
echo "<script> alert('".$database->getErrorMsg()."'); window.history.go(-1); </script>\n";
}
}
// Make new session
function makeSession() {
global $database;
$database->setQuery( "INSERT INTO sessions (id, ip, sid, created, access)"
. "\nVALUES ('','".mysql_escape_string($_SERVER['REMOTE_ADDR'])."',"
. ($newsid=md5( time() + rand(0,100000) + $_SERVER['REMOTE_ADDR'] )).", NOW(), NOW() )");
if (!$database->query()) {
echo "<script> alert('".$row->getError()."'); window.history.go(-1); </script>\n";
exit();
}
return $row->sid;
}
// обновляет сессию, устанавливая текущим время последнего обращения
function updateSession( $sid ) {
global $database;
$database->setQuery( "UPDATE sessions SET"
. "\nip='".$_SERVER['REMOTE_ADDR']."', created=created, access=NOW(NULL)"
. "\nWHERE sid='".mysql_escape_string($sid)."'" );
if (!$database->query()) {
echo "<script> alert('".$database->getErrorMsg()."'); window.history.go(-1); </script>\n";
}
}
Спасибо за интересную информацию! Добавить ничего не могу, но узнал много нового
Это далеко не конец. Надеюсь, знающие ребята подробно распишут что надо делать и как ставить Apache & PHP & (MySQL || PortageSQL), чтобы все было действительно грамотно.
Еще я хочу доработать функции по сессиям, автоматическую генерацию нового ключа для каждого перехода по странице для увеличения сложности сломать систему. Ну и работу с куками, если они включены. Лучше и проще, конечно, $sid держать в куках а не строке запроса.
Для начала создаем в базе данных таблицу для хранения информации о пользователях со следующими полями:
1) id - уникальный номер пользователя
2) login - Ну это я думаю понятно (логин)
3) psw - пароль
4) email - адрес электронной почты пользователя
5) question - вопрос который будет задан пользователю в случае если он забудет пароль
6) answer - ответ на вопрос пользователя
С полями определились теперь создаем SQL-таблицу:
id int(10) unsigned not null auto_increment,
login varchar(20) not null default '0',
`password` varchar(50) not null,
question varchar(255) not null,
answer varchar(255) not null,
email varchar(50) not null,
primary key (id))
type=MyISAM;
Теперь создадим форму регистрации (form.php):
<head>
<title>Регистрация</title>
</head>
<body>
<center><font color="#ff0000">
<?php
if (isset($HTTP_GET_VARS['result'])){
switch($HTTP_GET_VARS['result']) {
case 1: {
echo "Вы незаполнили все поля !!!";
break;
}
case 2: {
echo "Пользователь с данным логином уже существует !!!";
break;
}
case 3: {
echo "Регистрация прошла успешно !!!";
break;
}
}
}
?>
</font></center>
<form name="addUsers" method="post" action="registr.php">
Логин: <input type="Text" name="login" maxlength="20">
Пароль: <input type="Text" name="psw" maxlength="6">
E-mail: <input type="Text" name="email" maxlength="50">
Введите вопрос который будет задан вам в случае утери пароля:
<input type="Text" name="question" maxlength="255">
Введите ответ на предыдущий вопрос:
<input type="Text" name="answer" maxlength="255">
<input name="send" type="submit" id="send" value="Зарегистрироваться" style="cursor:hand;">
</form>
</body>
</html>
Затем создаем скрипт проверки и регистрации (registr.php):
//проверка что на страницу попали именно со страницы формы (упрощеный вариант)
if (isset($HTTP_POST_VARS['send'])){
//обрезаем переменные и приводим их к безопасному виду
$login = substr($HTTP_POST_VARS['login'], 0, 20);
$psw = substr($HTTP_POST_VARS['psw'], 0, 6);
$question = substr($HTTP_POST_VARS['question'], 0, 255);
$answer = substr($HTTP_POST_VARS['answer'], 0, 255);
$email = substr($HTTP_POST_VARS['email'], 0, 50);
if (preg_match("/[^(\w)|(\x7F-\xFF)|(\s)]/", $login)) $login = "";
if (preg_match("/[^(w)|(@)|(.)]/",$email)) $email = "";
//далее проверяем всели поля заполнены
if ($login!=""&&$psw!=""&&$question!=""&&$answer!=""&&$email!=""){
//проверяем наличие пользователя с данным логином в базе
$result = mysql_query("SELECT login FROM users WHERE login = '".$login."'");
if (mysql_num_rows($result) > 0) {
//если пользователь с данным именем существует отправляем пользователя на форму с предупреждением
header("Location: www.mysite.ru/form.php?result=2");
exit;
}
else{
//проверка прошла успешно вносим данные в базу
$result = mysql_query("INSERT INTO cclub_users (login,psw,question,answer,email)
values ('".$login."','".md5($psw)."','".mysql_escape_string($question)."','".md5($answer)."','".$email."')");
header("Location: www.mysite.ru/form.php?result=3");
exit;
}
}
else{
//если не все поля заполнены отправляем пользователя на форму с предупреждением
header("Location: www.mysite.ru/form.php?result=1");
exit;
}
}
else{
//если пришли не со страницы формы отправляем на нее
header("Location: www.mysite.ru/form.php");
exit;
}
?>
В следующий раз я раскажу вам о ситуации, когда пользователь забывает пароль....
2. Для числовых переменных:
// которые должны содержаться в нем - это [0-9]+
$myinteger = trim($myinteger);
$myinteger = preg_match("/^[0-9]+$/i", $myinteger) ? $myinteger : 0;
Я немного другим способом проверяю и мне кажеться он более красивый:)
//что-то делаем для вывода ошибки
}
Просто она и работает немного быстрее.
Я её всегда использую как правило вкупе с strlen() получаеться непробиваемая комбинация.
header("location:error.php?id=1");
}
с точки зрения администрирования системы безопаснее использовать PHP через CGI, а не как модуль Apache.
а с точки зрения создателей PHP:
Now that version 4.1 introduces a safer sapi module, we recommend that you configure PHP as a module in Apache. (...)
Note, we consider installing PHP as CGI binary suicidal.
Взято из install.txt php4.3.x
Не стоит вводить людей в заблуждение, это все-таки FAQ!
а с точки зрения создателей PHP:
Не стоит вводить людей в заблуждение, это все-таки FAQ!
S tochki zreniya bezopasnosti ADMINISTRIROVANIYA rekomenduetsya ispol'zovat' PHP cherez CGI, poskol'ku v protivnom sluchae readfile() i system() pomogut porytsya v papkah drugih pol'zovatelej i vypolnyat' kommandy ot imeni web-servera.
S tochki zreniya bezopasnosti ADMINISTRIROVANIYA rekomenduetsya ispol'zovat' PHP cherez CGI, poskol'ku v protivnom sluchae readfile() i system() pomogut porytsya v papkah drugih pol'zovatelej i vypolnyat' kommandy ot imeni web-servera.
Определённые функции можно просто запретить.
Есть как минимум два таких хостера, agava.ru (сервера h1-h12)и masterhost.ru, где есть возможность собрать свой интерпретатор PHP.
При этом агава - полная лажа ( http://sylabulus.h14.ru/forum/viewtopic.php?t=5 - прочитать как минимум три первых поста), а мастерхост не даёт мускула...
При этом агава - полная лажа ( http://sylabulus.h14.ru/forum/viewtopic.php?t=5 - прочитать как минимум три первых поста), а мастерхост не даёт мускула...
Народ, хватит флудить. Тема не для этого!
Народ, хватит флудить. Тема не для этого!
И верно. Завтра удалю этот и другие лишние комментарии.
По теме и конструктивно, пожалуйста.
По этой теме можно статью писать смело:).
Начнём с малого.
$num = 6;
while($num--) {
$new_pass .=(chr(mt_rand(33,126)) ;
}
$pass = md5(md5($new_pass));
//Всё, теперь у нас в $new_pass пароль для
//отправки по почте юзеру, а в $pass зашифрованный //для базы данных
Не используйте расширения файлов, в которых будут хранится важные данные, например, пароли, неизвестные броузеру (inc, dat и др). Такие файлы броузер пытается показать "как есть", и в результате ваши данные станут известны тем, кому не надо.
У меня они в .inc на моём сайте храняться, но спорим .htaccess рулит? ;)
У меня они в .inc на моём сайте храняться, но спорим .htaccess рулит? ;)
Замечу тут факю, а не ринг по выяснению крутости. лучьше бы описал этот способ
Замечу тут факю, а не ринг по выяснению крутости. лучьше бы описал этот способ
А я отмечу тот факт, что такой метод не рекомендуется использовать в переносимых скриптах. Не везде сервер настроен одинаково, не всегде есть доступ к .htaccess.
Кроме того есть человеческий фактор, при перезеде или просто обновлении файл можно просто забыть.
А я отмечу тот факт, что такой метод не рекомендуется использовать в переносимых скриптах. Не везде сервер настроен одинаково, не всегде есть доступ к .htaccess.
Кроме того есть человеческий фактор, при перезеде или просто обновлении файл можно просто забыть.
. Коротко о .htaccess.
. Не кто не запрещал переносить и .htaccess:)
. Я обычно имею дело с платными хостингами, а там таких проблем не замечал. И вообще это лишь моё ИМХО, просто мне так удобней.
Есть серверы на которых использовать .htaccess нельзя, я так понимаю, в апаче эта функция отключена. Посему, использовать .htaccess, так сказать, не дает полной совместимости со всеми серверами. Если такого требования нет то почему бы не использовать?
{
echo mysql_error();
}
Распространнёная ошибка.
Старайтесь по-меньше вешать задач на ява-скрипт, которые можно решить через пхп. Кстати, window.history работает только в ИЕ.
И еще, если вы захотите изменить какой-нить обьект родительского окна в поп-ап окне, вам это удастся не во всех браузерах. Создайте в предке функцию и вызывайте из потомка.
Для работы с сессией можно применять такие приёмы, по ужасному называть переменые и таким образом запутать хацкера.
define("SESSION_USER","qweqeqwe342r3");
...
if($_SESSION(SESSION_USER))...
При удалении или обновлении строки в таблице не забывайте ставить LIMIT 1
DELETE FROM Table WHERE uid=5 LIMIT 1
Неплохая регулярка для проверки мыла
eregi("^[a-zA-Z0-9_\-]{1,}\.{0,1}[a-zA-Z0-9_\-]{0,}@[a-zA-Z0-9_\-]{1,}\.[a-zA-Z0-9_\-\.]{2,}$",$mail)
Про это уже говорилось, но всё-же
Все свои классы и настройки сохраняйте ТОЛЬКО с разширением .php
Это уже к админам, лучше всего ставить настройки, при которых непоказываются ни ошибки, ни воринги ни ноутесы. Можно и самому в скрипт поставить error_reporting, но оно не всегда работает. При работе с файлами скрывайте ошибки с помощью @
При работе с файлами скрывайте ошибки с помощью @
Просто хотел немного поправить При работе с отлаженым скриптом. А вообще лучше не скрывать, а пользоваться конструкцией if() or die('message'). Если я уверен, что ошибки произойти по вине скрипта не может, а это модификация входящих данных, что уже можно считать попыткой взлома.
exit(header('location:http://microsoft.com'));
}
Название сайтов можно менять:). Как то даже делал ссылку на 172 и 173 :))
PS Только "<a href ....>" не надо, это форум так решил... Чтож ему виднее :)э
Кстати, window.history работает только в ИЕ.
В Firefox почему-то тож работает.
При работе с файлами скрывайте ошибки с помощью @
Лучше ставить собаку везде, где только можно, в т. ч. и при работе с мускулом.