Защита от SQL Injection – начин на работа, уязвимости, защита

Защита от SQL Injection (подробно, начин на работа, уязвимости, защита)

Пак ще се повторя като от предишния урок.
SQL Инжекциите са досадно нещо. Доста начинаещи PHP програмисти просто нямат нужните познания или дори на някой напреднал програмист, може да му се случи да пропусне да си прегледа кода и да остане уязвимост.
За какво се използва? – използва се за неоторизирано изпълнение на SQL заявка към базата данни.
Тази техника е за експлоатация на уязвимите кодове.

Най-лесно ще го схванете с нагледни примери. Имаме програмист , който е нает да направи система за онлайн пазаруване. Програмиста създава база данни (MySQL) и следната таблица в нея:

CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(200) NOT NULL,
`password` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `users` (`id`, `username`, `password`) VALUES (1, 'admin', 'securitypass'), (2, 'Ticketa', '123456'), (3, 'Client', '12345');

Така. Трябва да отбележа, че има проблем с горния код, а той е: НИКОГА, ама НИКОГА не оставяйте паролите в ясен вид. По-принцип се криптират най-често с md5(), за по-голяма сигурност, можете да създадете и SALT към него.

Сега да си представим, че имаме една проста Логин система(която е уязвима..)

<?php
if(isSet($_GET['submit'])) {
$username = stripslashes($_GET['username']);
$password = stripslashes($_GET['password']);
$query = mysql_query("SELECT * FROM `users` WHERE `username` = '$username' AND `password` = '$password';") or die(mysql_error());
if(mysql_num_rows($query) > 0) {
$row = mysql_fetch_assoc($query);
echo "Здрасти {$row['username']} : {$row['password']}!<br />";";
}
} else {
echo "<form method='GET' action='login.php'>
Потребител: <input type='text' name='username' /><br />
Парола: <input type='text' name='password' /><br />
<input type='submit' name='submit' value='Login' />
</form>";
}
?>

Проблеми на формата:

  • Използваме $_GET , вместо $_POST – по не сигурно е. Сега след като сме видели това просто в полето за URL адрес в своя браузър записва:
    http://saita.com/login.php?username=test&password=test’%20OR%201=’1&submit=Login
    След, подобна така атакуващия (хахора) , ще види съобщение:
Здрасти admin : securitypass!

Сещате се на къде бия нали? Т’ва се получи заради кода, който добавих: ‘ or 1=’1

Прави SQL заявката по следния начин:

SELECT * FROM `users` WHERE `username` = 'test' AND `password` = 'test' OR 1='1'

Виждаме, че след WHERE клаузата ще бъде изпълнен за всички редове. Тук логиката на кода ограничава изхода до първия ред от таблицата, но сами се сещате, че с добавяне на малко повече код може да се извлече цялата таблица с потребителите.
За да не се превръща урока в ръководство за хахори спирам до тук.

Най-лесния начин да се предпазите от SQL инжекции е да филтрирате всички входящи данни. Например , чрез функциите:
htmlspecialchars(); addslashes(); и trim();
Какво правят функциите?

htmlspecialchars() – преобразува специални знаци в html единици
addslashes() – екранира специалните знаци на даден стринг
trim() – премахва знаци в началото и края на даден стринг
htmlentities() – преобразува всички подходящи знаци в HTML единици и е .. сходна с htmlspecialchars();

Сега ще създадем една функция и ще обхождаме всяка $_GET, $_POST заявка както и бисквитките. (кода е най-добре, да седи най-отгоре на страниците).

function security($method){
if(intval($method)){
return $method;
} else {
$method = addslashes($method);
$method = htmlspecialchars($method, ENT_NOQUOTES);
return $method;
}
}
$_GET = array_map("security", $_GET);
$_POST = array_map("security", $_POST);
$_COOKIE = array_map("security", $_COOKIE);

Сега. Взимаме горния код за вход и го променяме малко:

<?php
function security($method){
if(intval($method)){
return $method;
} else {
$method = addslashes($method);
$method = htmlspecialchars($method, ENT_NOQUOTES);
return $method;
}
}
$_GET = array_map("security", $_GET);
$_POST = array_map("security", $_POST);
$_COOKIE = array_map("security", $_COOKIE);
if(isSet($_POST['submit'])) {
$username = $_POST['username'];
$password = md5($_POST['password']);
$query = mysql_query("SELECT * FROM `users` WHERE `username` = '{$username}' AND `password` = '{$password}';") or die(mysql_error());
$row = mysql_fetch_assoc($query);
if((mysql_num_rows($query) == 1) && ($password == $row['password'])) {
echo "Здрасти {$row['username']} : {$row['password']}!<br />";";
}
} else {
echo "<form method='POST' action='login.php'>
Потребител: <input type='text' name='username' /><br />
Парола: <input type='text' name='password' /><br />
<input type='submit' name='submit' value='Login' />
</form>";
}
?>

Сега, за да не ни пусне в чужд акаунт горния код, просто трябва да направим ограничение за уникалност на стойностите в колона – username.
Променили сме и метода на формата от GET на POST, дипче.. не е няк’ва ГОЛЯМА защита. Колкото да не се вижда какво въвеждаме в браузъра. И другото, което сме променили е начина за проверка към базата данни.
Именно вече използваме md5() за да „кодираме“/“хешираме“ паролата.

Пак казвам. Това е прост пример, може да се направят и други защити, но.. може да направи атаката само малко по сложна. В такъв случай, въведете и една проверка, за „грешни пароли“. Ако някой реши, може да напише един brute-force за сайта ви и да счупи случайно някой от паролите. А, ако имате скрипт, който проверява за грешни пароли тогава е друго. Какво искам да кажа?

При 3 грешни опита за вход с грешна парола в базата данни, заключваме акаунта за 15 минути. Ловя бас, защото повечето сте виждали подобно нещо.

Причини за бъгове в системата(уязвимости към SQLi). SQL Инжекцията, може да възникне в тези случай:

Методи за защита от SQL инжекция

  • * Филтриране на данните (по-горе съм дал пример)
  • Изключване на докладите за грешки.
  • Създаване на потребител, с по-малко привилегии.
  • Максимална стойност

1 – Изключване на докладване за грешки

<?php
error_reporting(0);
?>


В резултат, хахора няма да може да разпознае дали има грешка и дали е уязвим сайта. Е.. Не всички, но пак е част от защитата.
По-този начин само ще усложним възможността на хахора да ни счупи сайта, може да успее да се добере до базата само с по-сложна атака.

2 – Създавайте MYSQL потребителите с най-слабите привилегии
Да предположим, че имаме сайт за хм.. Онлайн магазин? Ок. Какво всъщност ни трябва? Потребителя вижда информацията (SELECT) и си избира, разни продукти.
Накрая прави проръчката (INSERT) , също така да речем, че имаме и колко прегледа има един продукт(UPDATE).

Имаме и админ панел – къде без него :)) Сега, правим си два акаунта(mysql). Единия има привилегии само за: SELECT, INSERT и UPDATE – това са привилегиите в магазина.
Правим си ОЩЕ един акаунт, който ще ни е за админ панела. Например SELECT, INSERT, UPDATE, DELETE. – Той може да трие, това което не го кефи, да сменя информация и т.н.

Всичко, друго е просто. 2та потребителя, ще използват една дб, но ще имаме два вида връзка към дб-то. Една за потребителя и една за админа. mysql_connect(‘localhost’, ‘adm’, ‘admpass’); и mysql_connect(‘localhost’, ‘user’, ‘userpass’);

Повечето програмисти не го одобряват това, нооо не са и патили :))

3 – Максимална стойност
Имаме форма за поръчка на даден продукт. В нея трябва да напишем нашето име и ще го ограничим до.. хм. 15 знака. HTML кода или по-точно полето за писане, можем да го ограничим с maxlength=“15″.

Това няма , много, много да ни предпази, защото ако хахора си свали HTML формата или просто редактира формата, чрез някой браузър плъгин може да промени това полe. Която ще обработи информацията, през една и съща връзка. След това трябва да се дублира с това ограничение на сървъра:
Ще използваме функцията substr(); за да ограничим символите в полето.

<?php
$name = substr($_POST['name'], 0, 10);
?>

Щях, да забравя. Атрибутът ACTION на тагът FORM. Никога не задавайте пълния път, а относителния. По този начин формата винаги ще се събмитва към файл от вашия сървър.

<form method="POST" action="dir/test.php"> - правилно
<form method="POST" action="http://sata.com/dir/test.php"> - неправилно

Ако искате формата да се събмитва към текущия файл (т.е. този който я съдържа), използвайте това:

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">

но ще трябва и него да го защитите , защото е уязвимо към XSS атаки, по следния начин;

<form action="<?php htmlentities($_SERVER['PHP_SELF'], ENT_QUOTES); ?>" method="POST">

Лек ден, ако имате предложения драскайте ЛС. 🙂

П.С. Сега се, сещам че водихме една дискусия във форума и драснах за mod_security модула, може да видите мнението тук: http://web-tourist.net/forum/viewtopic.php?p=635752#635752

Добър модул е. Доволен съм от него, въпреки че може да се намери някоя друга вратичка.

П.П. АКО използвате $_GET заявка, която ще съдържа само цифри използвайте (int)$_GET[‘id’]; например.

Вашият коментар