|
| 1 | +Рискове за сигурността |
| 2 | +********************** |
| 3 | + |
| 4 | +<div class=perex> |
| 5 | + |
| 6 | +Базите данни често съдържат чувствителни данни и позволяват извършването на опасни операции. За сигурна работа с Nette Database основните аспекти са: |
| 7 | + |
| 8 | +- Разбиране на разликата между сигурен и несигурен API |
| 9 | +- Използване на параметризирани заявки |
| 10 | +- Правилно валидиране на входните данни |
| 11 | + |
| 12 | +</div> |
| 13 | + |
| 14 | + |
| 15 | +Какво представлява SQL инжектирането? .[#toc-what-is-sql-injection] |
| 16 | +=================================================================== |
| 17 | + |
| 18 | +SQL инжектирането е най-сериозният риск за сигурността при работа с бази данни. То възниква, когато нефилтриран потребителски вход стане част от SQL заявка. Нападателят може да вмъкне свои собствени SQL команди и по този начин: |
| 19 | +- да извлече неоторизирани данни |
| 20 | +- да променя или изтрива данни в базата данни |
| 21 | +- да заобиколи удостоверяването |
| 22 | + |
| 23 | +```php |
| 24 | +// ❌ ОПАСЕН КОД - уязвим към SQL инжекция |
| 25 | +$database->query("SELECT * FROM users WHERE name = '$_GET[name]'"); |
| 26 | + |
| 27 | +// Нападателят може да въведе стойност като: ' ИЛИ '1'='1 |
| 28 | +// Получената заявка би била: SELECT * FROM users WHERE name = '' OR '1'='1' |
| 29 | +// което връща всички потребители |
| 30 | +``` |
| 31 | + |
| 32 | +Същото важи и за Database Explorer: |
| 33 | + |
| 34 | +```php |
| 35 | +// ❌ ОПАСЕН КОД - уязвим към SQL инжекция |
| 36 | +$table->where('name = ' . $_GET['name']); |
| 37 | +$table->where("name = '$_GET[name]'"); |
| 38 | +``` |
| 39 | + |
| 40 | + |
| 41 | +Сигурни параметризирани заявки .[#toc-secure-parameterized-queries] |
| 42 | +=================================================================== |
| 43 | + |
| 44 | +Сигурният начин за вмъкване на стойности в SQL заявките е чрез параметризирани заявки. Nette Database предлага няколко начина за използването им. |
| 45 | + |
| 46 | +Най-простият начин е да се използват **заместващи знаци за въпроси**: |
| 47 | + |
| 48 | +```php |
| 49 | +// ✅ Сигурна параметризирана заявка |
| 50 | +$database->query('SELECT * FROM users WHERE name = ?', $name); |
| 51 | + |
| 52 | +// ✅ Защитено условие в Explorer |
| 53 | +$table->where('name = ?', $name); |
| 54 | +``` |
| 55 | + |
| 56 | +Това важи за всички други методи в [Database Explorer |explorer], които позволяват вмъкване на изрази със заместители с въпросителни знаци и параметри. |
| 57 | + |
| 58 | +За командите INSERT, UPDATE или клаузите WHERE можем спокойно да предаваме стойности в масив: |
| 59 | + |
| 60 | +```php |
| 61 | +// ✅ Secure INSERT |
| 62 | +$database->query('INSERT INTO users', [ |
| 63 | + 'name' => $name, |
| 64 | + 'email' => $email, |
| 65 | +]); |
| 66 | + |
| 67 | +// ✅ Secure INSERT в Explorer |
| 68 | +$table->insert([ |
| 69 | + 'name' => $name, |
| 70 | + 'email' => $email, |
| 71 | +]); |
| 72 | +``` |
| 73 | + |
| 74 | +.[warning] |
| 75 | +Трябва обаче да осигурим [правилния тип данни на параметрите |#Validating input data]. |
| 76 | + |
| 77 | + |
| 78 | +Ключовете на масива не са сигурен API .[#toc-array-keys-are-not-secure-api] |
| 79 | +--------------------------------------------------------------------------- |
| 80 | + |
| 81 | +Докато стойностите на масивите са защитени, това не важи за ключовете! |
| 82 | + |
| 83 | +```php |
| 84 | +// ❌ ОПАСЕН КОД - ключовете на масивите не са обработени |
| 85 | +$database->query('INSERT INTO users', $_POST); |
| 86 | +``` |
| 87 | + |
| 88 | +За командите INSERT и UPDATE това е сериозен пропуск в сигурността - атакуващият може да вмъкне или промени всяка колона в базата данни. Той може например да зададе `is_admin = 1` или да вмъкне произволни данни в чувствителни колони (известно като уязвимост при масово задаване). |
| 89 | + |
| 90 | +При условията WHERE това е още по-опасно, тъй като те могат да съдържат оператори: |
| 91 | + |
| 92 | +```php |
| 93 | +// ❌ ОПАСЕН КОД - ключовете на масивите не са обработени |
| 94 | +$_POST['salary >'] = 100000; |
| 95 | +$database->query('SELECT * FROM users WHERE', $_POST); |
| 96 | +// Изпълнява заявка WHERE (`salary` > 100000) |
| 97 | +``` |
| 98 | + |
| 99 | +Атакуващият може да използва този подход, за да разкрива систематично заплатите на служителите. Той може да започне със заявка за заплати над 100 000, след това под 50 000 и чрез постепенно стесняване на обхвата да разкрие приблизителните заплати на всички служители. Този тип атака се нарича SQL enumeration. |
| 100 | + |
| 101 | +Методът `where()` поддържа SQL изрази, включително оператори и функции в ключовете. Това дава възможност на нападателя да извършва сложни SQL инжекции: |
| 102 | + |
| 103 | +```php |
| 104 | +// ❌ ОПАСЕН КОД - атакуващият може да вмъкне свой собствен SQL |
| 105 | +$_POST['0) UNION SELECT name, salary FROM users WHERE (?'] = 1; |
| 106 | +$table->where($_POST); |
| 107 | +// изпълнява заявка WHERE (0) UNION SELECT name, salary FROM users WHERE (1) |
| 108 | +``` |
| 109 | + |
| 110 | +Тази атака прекратява оригиналното условие с `0)`, добавя своя собствена `SELECT` с помощта на `UNION`, за да получи чувствителни данни от таблицата `users`, и приключва със синтактично правилна заявка с помощта на `WHERE (1)`. |
| 111 | + |
| 112 | + |
| 113 | +Бял списък на колони .[#toc-column-whitelist] |
| 114 | +--------------------------------------------- |
| 115 | + |
| 116 | +Ако искате да разрешите на потребителите да избират колони, винаги използвайте бял списък: |
| 117 | + |
| 118 | +```php |
| 119 | +// ✅ Сигурна обработка - само разрешени колони |
| 120 | +$allowedColumns = ['name', 'email', 'active']; |
| 121 | +$values = array_intersect_key($_POST, array_flip($allowedColumns)); |
| 122 | + |
| 123 | +$database->query('INSERT INTO users', $values); |
| 124 | +``` |
| 125 | + |
| 126 | + |
| 127 | +Потвърждаване на входните данни .[#toc-validating-input-data] |
| 128 | +============================================================= |
| 129 | + |
| 130 | +**Най-важното е да се гарантира правилният тип данни на параметрите** - това е необходимо условие за сигурно използване на базата данни Nette. Базата данни приема, че всички входни данни имат правилния тип данни, съответстващ на дадената колона. |
| 131 | + |
| 132 | +Например, ако `$name` в предишните примери беше неочаквано масив вместо низ, Nette Database щеше да се опита да вмъкне всички негови елементи в SQL заявката, което щеше да доведе до грешка. Затова **никога не използвайте** невалидирани данни от `$_GET`, `$_POST` или `$_COOKIE` директно в заявки към базата данни. |
| 133 | + |
| 134 | +На второ ниво проверяваме техническата валидност на данните - например дали низовете са в кодировка UTF-8 и дали дължината им съответства на дефиницията на колоната, или дали числовите стойности са в допустимия диапазон за дадения тип данни на колоната. За това ниво на валидиране можем частично да разчитаме на самата база данни - много бази данни отхвърлят невалидните данни. Поведението на различните бази данни обаче може да е различно, като някои могат мълчаливо да съкращават дълги низове или да изрязват числа извън обхвата. |
| 135 | + |
| 136 | +Третото ниво представлява логически проверки, специфични за вашето приложение. Например проверка дали стойностите от полетата за избор съответстват на предложените опции, дали числата са в очаквания диапазон (например възраст 0-150 години) или дали взаимозависимостите между стойностите имат смисъл. |
| 137 | + |
| 138 | +Препоръчителни начини за прилагане на валидиране: |
| 139 | +- Използвайте [формуляри на Nette, |forms:] които автоматично осигуряват цялостно валидиране на всички входни данни |
| 140 | +- Използвайте [Presenters |application:] и посочете типове данни за параметрите в методите `action*()` и `render*()` |
| 141 | +- Или реализирайте свой собствен слой за валидиране, като използвате стандартни инструменти на PHP, като например `filter_var()` |
| 142 | + |
| 143 | + |
| 144 | +Динамични идентификатори .[#toc-dynamic-identifiers] |
| 145 | +==================================================== |
| 146 | + |
| 147 | +За динамични имена на таблици и колони използвайте заместителя `?name`. Това гарантира правилното ескапиране на идентификаторите в съответствие с дадения синтаксис на базата данни (например използване на задни тирета в MySQL): |
| 148 | + |
| 149 | +```php |
| 150 | +// ✅ Безопасно използване на надеждни идентификатори |
| 151 | +$table = 'users'; |
| 152 | +$column = 'name'; |
| 153 | +$database->query('SELECT ?name FROM ?name', $column, $table); |
| 154 | +// Резултат в MySQL: SELECT `name` FROM `users` |
| 155 | + |
| 156 | +// ❌ ОПАСНО - никога не използвайте потребителски вход |
| 157 | +$database->query('SELECT ?name FROM users', $_GET['column']); |
| 158 | +``` |
| 159 | + |
| 160 | +Важно: използвайте символа `?name` само за доверени стойности, дефинирани в кода на приложението. За потребителски стойности вместо това използвайте подхода на белия списък. |
0 commit comments