Тема: Проверяй данные юзверя
Совет: Всегда проверяйте данные приходящие из форм прежде чем будете их использовать!!!
Ни когда недоверяйте юзеру. Иначе горько поплатитесь за свою доверчивость. Даже если не по злому умыслу, а исключительно по криворукости он может ввести ахинею которая выдыст ошибку (это в лучшем случае), а то и совершенно завалит базу данных. И так приступим...
Во-первых, никогда не доверяйте ограничение количества вводимых символов тегу:
Код
<input type="Text" name="UserName" maxlength="20">
При передачи данных формы в скрипт нужно всегда обрезать лишние символы:
Код
$usrname = substr($HTTP_POST_VARS['UserName'], 0, 20);
таким образом вы гарантируете себе правильность длинны полученных данных.
Второе: всегда проверяйте полученные данные на наличие спецсимволов. Таким образом вы присечете вставку в ваш скрип потенциально опастных добавлений. Именно с помощью них поломаны большинство сайтов. И так пишем...
Код
if (preg_match("/[^(\w)|(\x7F-\xFF)|(\s)]/", $usrname)) $usrname = "";
теперь если злобный хацкер попробует таким образом взломать ваш сайт, мы пресечем его действия на корню. Теперь делаем проверку существует ли переменая $usrname:
Код
if ($usrname!=""){
//выполняем дольнейшие проверки и саму программу
}
else{
echo "Access is refused. Hi, hazker!!!";
}
Для проверки e-mail:
Код
if (preg_match("/[^(\w)|(\@)|(\.)]/",$usermail)) $usermail = "";
Ещё одна неплохая регулярка для проверки мыла:
Код
eregi("^[a-zA-Z0-9_\-]{1,}\.{0,1}[a-zA-Z0-9_\-]{0,}@[a-zA-Z0-9_\-]{1,}\.[a-zA-Z0-9_\-\.]{2,}$",$mail)
При введении данных, любым образом поступивших извне (формы методами GET, POST и пр.) никогда не доверять пользователю, что он ввел правильные символы. Должны в обязательном порядке следовать следующие проверки:
1.При отправке сообщений через форму по почте, обязательно проверйте все почтовые поля на наличие переноса не только в конце, но и внутри:
Например, если указать в поле обратный адрес (или в теме):
Цитата
test@codenet.ru\nBCC: test1@codenet.ru
То может получится такое письмо:
Цитата
From: test@codenet.ru To: админ@codenet.ru BCC: test1@codenet.ru Subject: ля-ля-ля само письмо.
Таким образом, письмо уйдет не только на админ@codenet.ru, но еще и на test1@codenet.ru
2.При доблении в базу, всегда используйте mysql_escape_string().
htmlspecialchars() и StripSlashes() лучше всего использовать только при выводе в браузер.
3. Для строковых переменных:
Код
// Пресекаем нарушение ограничения по длине строки.
// Как правило можно ограничиться ограничением БД 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;
Более быстрый способ проверки:
Код
if(!is_numeric($myinteger)) {
//что-то делаем для вывода ошибки
}
А вкупе с strlen() получаеться непробиваемая комбинация.
Код
if(!is_numeric($myinteger) || strlen($myinteger)>4) {
header("location:error.php?id=1");
}
Проверять переменные можно по упрощенной схеме, например с
помощью функций intval и doubleval
То есть вместо:
Код
// Если речь идет о целом числе, то единственные символы,
// которые должны содержаться в нем - это [0-9]+
$myinteger = trim($myinteger);
$myinteger = preg_match("/^[0-9]+$/i", $myinteger) ? $myinteger : 0;
можно использовать:
Код
$myinteger = intval($myinteger);
Кроме того, иногда проще использовать такие конструкции:
Код
$public=$_POST["public"]=="yes"?"yes":"no";
Про проверку чисел. Довольно часто программисты забывают про великолепную возможность приведения типа. Ну к примеру:
Код
$some = (int) $_GET["id"];
Естественно, если вы ожидаете число, то его и получите, а в противном случае 0 (а не DELETE FROM ...)
Тема: Запрет на выполнение других скриптов кроме index.php
Если сайт реально работает только от index.php, то от запуска остальных модулей можно защитится так. Константа SAFETY определяется в неком ини файле и подключается только в index.php. Если из броузера набрать имена остальных модулей, то они не отработают
Код
if (!defined('SAFETY')) { die('Invalid context'); } if (SAFETY=='safety') { code.... } else { echo "хакер выпей ЙАду"; }
Проверка существования модулей
Прежде чем подключать какой либо файл (он же модуль) к скрипту следует проверять его наличие. В таком случае вы избегаете ошибок, которые могут выдать хацкеру структуру вашего сайта.
Код
if(file_exists("modules/test.php"))
$exists=1;
else
$exists=0;
if($exists!=1) {
echo("Access is refused. Hi, hazker!!!");
exit;
}
К тому же можно использовать использовать конструкцию:
Код
require_once ("www.mysite.ru/modules/test.php");
в этом случае если вы используете еще где-то повторное
подключение этого модуля ошибки не возникнет.
Если файл не должен вызываться напрямую:
1. Его можно положить в отдельный каталог и запретить к нему доступ с помощью .htaccess
2. Можно положить этот файл в каталог не доступный веб-серверу.
3. Можно использовать префикс в названии файла, и файлы с этим префиксом запретить в .htaccess
4. Можно назвать его начиная с .ht - это префикс в Apache запрещен по умолчанию.
Когда Вы используете один из этих способов, всегда проверяйте, работает он или нет.
Тема: Хранение данных в сессиях
Для безопастного хранения данных использовать не куки (которые, кстати могут быть и отключены), а сессии.
Например, при обращении пользователя к страницам, требующим авторизации, использую проверку, скажем пароля на каждой странице.
Код
if(isset($SESSION_LOGIN)&&isset($SESSION_PASSWORD))
{
if (!Auth_User($SESSION_LOGIN,$SESSION_PASSWORD))
exit(header("Location: noway.php"));
}
esle
exit(header("Location: noway.php"));
где
Auth_User (упрощенный вариант):
Код
function Auth_User($log,$pwd)
{
if($log=="admin"&&$pwd="admin")
return(TRUE);
else
return(FALSE);
}
Для того чтобы окончательно увеличить безопасность и исключить возможность подбора переменных и значений злоумышленником в адресной строке (что и так маловероятно), предлагаю сначала удалять переменные (unset), а только затем запускать сессию:
Код
/*даже если появилась SESSION_LOGIN и SESSION_PASSWORD (пришли из адресной строки и т.д.), убьём их.*/
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_LOGIN=$login;
$SESSION_PASSWORD=$password;
session_register("SESSION_LOGIN");
session_register("SESSION_PASSWORD");
Причем, можно все это использовать не меняя в своих сайтах ничего (не париться насчёт стоит global on или нет). Для этого нужно заинклудить в начале работы скрипта еще один скрипт vars.php например, в котором будут описаны функциии проверки и инициализации используемых переменных.
Пример:
Код
function InitialVar($var_name)
{
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.
И использовать только один файл (index.php) для запросов к сайту, а уже этот файл будет решать какие подключать модули и какие функции вызывать.
Зачем использовать функции? допустим у вас в каком нибудь модуле написана работа с файлами, если запустить этот модуль без нужных переменных он скорее всего не отработает и выдаст ошибку в описании которой будут нужные переменные, которые можно передать в этот скрипт и... вскяко может быть
Для того, чтобы быть абсолютно уверенным, что хакер не сможет подключиться к подчиненному файлу напрямую с заданными переменными, которые модуль сочтет за глобальные данные основного файла, необходимо использовать следующую конструкцию:
Код
// Код для index.php
// Объявление константы. Как известно, константу невозможно передать в строке
// запроса, а значит мы в безопасности.
DEFINE('_MY_SITE_CHECKER', true);
// Код для ВСЕХ (!!) остальных php файлов сайта
// Проверка, объявлена ли константа?
// Если не объявлена, значит index.php не был вызван.
// Значит нас любят злобные хакеры. Так их:
defined( '_MY_SITE_CHECKER' )
or die( 'Direct Access to this location is not allowed.' );
Тема: Свои сессии. Часть 1
HTTP протокол (80 и др. порты) очень удачный как популярный протокол. Но имеет некоторые недостатки, в числе которых то, что каждое новое соединение Клиента с Сервером совершенно независимо от предыдущего.
Поэтому и были созданы так называемые "сессии", которые позволяют "окольными путями" вести вместе с Клиентом по сайту дополнительную информацию, такую как, например, логин и пароль (если речь идет о пользователе), данные о реферале, какой-нибудь ключ и многое другое.
PHP имеет свой собственный движок сессий. Он достаточно удачен, но имеет некоторые недостатки, о которых мы сейчас не будем говорить. Давайте посмотрим как написать свой собственный набор функций по работе с сессиями.
Для начала, мы предполагаем что у нас есть база данных, к которой мы подключены. И что у нас есть объект $database с небольшим набором методов (функций объекта) для начала. Я предлагаю работать через объект, поскольку тип БД может варьироваться, а подобный объект написать не представляет особенной сложности.
А писать мы будем сессии для авторизованного пользователя, т.е. наиболее популярный вариант.
Поехали!
Свои сессии. Часть 2: код управляющих сессиями функций
Данные функции являют собой ядро нашей системы управления сессиями. Нам необходима всего одна таблица, которую можно создать SQL-запросом:
Код
CREATE TABLE `sessions` ( `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";
}
}
Не забывайте проверять в своих функциях для работы с БД такие строчки:
Код
if(!mysql_query($query))
{
echo mysql_error();
}
Распространнёная ошибка.
Старайтесь по-меньше вешать задач на ява-скрипт, которые можно решить через пхп. Кстати, window.history работает только в ИЕ.
И еще, если вы захотите изменить какой-нить обьект родительского окна в поп-ап окне, вам это удастся не во всех браузерах. Создайте в предке функцию и вызывайте из потомка.
Для работы с сессией можно применять такие приёмы:
1. По ужасному называть переменые и таким образом запутать хацкера.
Код
define("SESSION_USER","qweqeqwe342r3");
...
if($_SESSION(SESSION_USER))...
2. При удалении или обновлении строки в таблице не забывайте ставить LIMIT 1
Код
DELETE FROM Table WHERE uid=5 LIMIT 1
Все свои классы и настройки сохраняйте ТОЛЬКО с разширением .php
Это уже к админам, лучше всего ставить настройки, при которых непоказываются ни ошибки, ни воринги, ни ноутесы. Можно и самому в скрипт поставить error_reporting, но оно не всегда работает. При работе с файлами скрывайте ошибки с помощью @
Тема: Регистрация пользователя
В этой теме мы расмотрим один из примеров регистрации пользователей вашего сайта.
Для начала создаем в базе данных таблицу для хранения информации о пользователях со следующими полями:
1) id - уникальный номер пользователя
2) login - Ну это я думаю понятно (логин)
3) psw - пароль
4) email - адрес электронной почты пользователя
5) question - вопрос который будет задан пользователю в случае если он забудет пароль
6) answer - ответ на вопрос пользователя
С полями определились теперь создаем SQL-таблицу:
Код
create table users ( 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):
Код
<html>
<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):
Код
<?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;
}
?>
...
Работа с утеренными паролями
По этой теме можно статью писать смело.
Начнём с малого.
Код
$new_pass = "";
$num = 6;
while($num--) {
$new_pass .=(chr(mt_rand(33,126));
}
$pass = md5(md5($new_pass));
//Всё, теперь у нас в $new_pass пароль для
//отправки по почте юзеру, а в $pass зашифрованный
//для базы данных
Информация взята с сайта http://www.codenet.ru Далее переработана и структурированна мной (Созидателем). Большое спасибо следующим людям (Они писали посты, которые отобрал и структурировал):
Dolonet;
S_K_I_V--WEBER;
Extractor;
Yurec;
mike;
shaelf;
Новая папка;
Артём;
S_K_I_V--WEBER;
Extractor;
Yurec;
mike;
shaelf;
Новая папка;
Артём;