І найменша допомога була від тієї функції, яку ви розробили. PHP: "Кивачки". Складання запитів mysql, сліши, екранування лапок Пошук у коді

лапок рядку (11)

Я намагаюся дізнатися найкращий спосіб писати запити. Я також розумію важливість того, щоб бути послідовним. Досі я випадково використовував одинарні лапки, подвійні лапки та зворотні тики без будь-якої реальної думки.

$query = "INSERT INTO table (id, col1, col2) VALUES (NULL, val1, val2)";

Крім того, у наведеному прикладі розглянемо, що "table," "col[n]," and "val[n]" можуть бути змінними.

Який стандарт для цього? Чим ти займаєшся?

Я читав відповіді на такі запитання близько 20 хвилин, але, схоже, немає остаточної відповіді на це запитання.

Answers

Тепер припустимо, що ви використовуєте пряму змінну post у запиті MySQL, тоді використовуйте її так:

$query = "INSERT INTO `table` (`id`, `name`, `email`) VALUES (" ".$_POST["id"]." ", " ".$_POST["name"]." ", " ".$_POST["email"]." ")";

Це найкраща практика використання змінних PHP у MySQL.

В основному в Mysql ці типи ідентифікаторів використовуються в запитах ` , " , " і () .

    " або " використовувати для увімкнення рядка як значення "26-01-2014 00:00:00" або "26-01-2014 00:00:00" . Цей ідентифікатор використовується тільки для функції "26-01-2014 00:00:00" рядка, наприклад, now() or sum ,max .

    використовувати для включення таблиці таблиці або таблиці, наприклад, select column_name з table_name де id = "2"

    () використовуються тільки для того, щоб просто укласти частини запиту, наприклад, select column_name з table_name де (id = "2" та gender = "male") або name = "rakesh.

Крім всіх (добре пояснених) відповідей, не було згаданих нижче, і я часто буваю у цьому Q&A.

В двох словах; MySQL думає, що ви хочете робити математикуна власній таблиці / стовпці та інтерпретуєте дефіси, такі як «електронна пошта», як e mail .

Відмова від відповідальності.Тому я думав, що додам це як відповідь типу «FYI» для тих, хто зовсім не знайомий із роботою з базами даних, і які можуть не розуміти вже описані технічні терміни.

(Є хороші відповіді вище стосовно характеру вашого питання SQL, але це також може бути актуальним, якщо ви новачок в PHP.)

Можливо, важливо відзначити, що PHP обробляє поодинокі та подвійні лапки по-різному.

Рядки з одним лапком - це «літерали» і є досить багато рядків WYSIWYG. Рядки з подвійними лапками інтерпретуються PHP для можливої ​​заміни змінних (зворотні посилання PHP не є точно рядками, вони виконують команду в оболонці і повертають результат).

$ foo = "bar"; echo "there is a $foo"; // There is a $foo echo "there is a $foo"; // There is a bar echo `ls-l`; // ... a directory list

Якщо таблиці cols та значення є змінними, то існує два способи:

З подвійними лапками "" повний запит:

$query = "INSERT INTO $table_name (id, $col1, $col2) VALUES (NULL, "$val1", "$val2")";

$query = "INSERT INTO ".$table_name." (id, ".$col1.", ".$col2.") VALUES (NULL, "".$val1."", "".$val2."" )";

З одинарними лапками "":

$query = "INSERT INTO ".$table_name." (id, ".$col1.", ".$col2.") VALUES (NULL, ".$val1.", ".$val2.")";

Використовуйте обернені тики `` коли ім'я стовпця / значення схоже на зарезервоване ключове слово MySQL.

Примітка.Якщо ви вказуєте ім'я стовпця з ім'ям таблиці, використовуйте обернені тики таким чином:

`table_name`. `column_name`<- Примечание: исключить. из задних клещей.

Backticks повинні використовуватися для ідентифікаторів таблиць і стовпців, але потрібні лише тоді, коли ідентифікатор є зарезервованим ключовим словом MySQL або коли ідентифікатор містить символи пропуску або символи за межами обмеженого набору (див. нижче). Часто рекомендується уникати використання зарезервованих ключових слів як ідентифікаторів стовпців або таблиць, якщо це можливо, щоб уникнути проблеми з лапками.

Одинарні лапки слід використовувати для рядкових значень, наприклад у списку VALUES() . Подвійні лапки підтримуються MySQL також для рядкових значень, але одинарні лапки ширше приймаються іншими РСУБД, тому непогано використовувати одинарні лапки замість подвійних.

MySQL також очікує, що літеральні значення DATE та DATETIME будуть мати одинарні лапки у вигляді рядків, наприклад "2001-01-01 00:00:00". Для отримання додаткової інформації див. Документацію з літератури дати та часу, зокрема альтернативи використанню дефісу - як роздільник сегментів у рядках дати.

Тому, використовуючи ваш приклад, я двічі привів би рядок PHP і використовував одинарні лапки для значень "val1", "val2" . NULL - це ключове слово MySQL та спеціальне (non)-значення, і тому воно не використовується.

Жоден з цих ідентифікаторів таблиць або стовпців не є зарезервованими словами або використовує символи, що вимагають цитування, але я все одно цитував їх зі зворотними висновками (докладніше про це пізніше...).

Функції, споріднені з RDBMS (наприклад, NOW() в MySQL), не повинні цитуватися, хоча їх аргументи підпорядковуються тим же правилам або правилам цитування, які вже згадувалися.

Backtick (`) table & column ───────┬─────┬──┬──┬──┬────┬──┬────┬──┬──── ┬──┬───────┐ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ $query = " INSERT INTO `table` (`id`, `col1`, `col2`, `date`, `updated`) VALUES (NULL, "val1", "val2", "2001-01-01", NOW())"; Unquoted keyword ─────┴┴┴┘ │ │ │ │ │ │ │││││ Single-quoted (") strings ────────────┴──── ┴────┘ │ │ │││││ Single-quoted (") DATE ───────────────────────────┴── ────────┘ │││││ Unquoted function ────────────────────────────────── ───────┴┴┴┴┘

Змінна інтерполяція

Шаблони цитування для змінних не змінюються, хоча якщо ви маєте намір інтерполювати змінні безпосередньо в рядку, це має бути подвійним лапком у PHP. Просто переконайтеся, що ви правильно уникнули змінних для використання в SQL. (Натомість рекомендується використовувати API, що підтримує підготовлені інструкції, як захист від SQL-ін'єкції).

// Одна thing with some variable replacements // Here, a variable table name $table is backtick-quoted, and variables // У VALUES list є один-quoted $query = "INSERT INTO `$table`(`id`, `col1`, `col2`, `date`) VALUES (NULL, "$val1", "$val2", "$date")";

Підготовлені заяви

Під час роботи з підготовленими заявами проконсультуйтеся з документацією, щоб визначити, чи слід вказувати заповнювачі заяви. Найпопулярніші API-інтерфейси, доступні в PHP, PDO та MySQLi, припускають неавторизованізаповнювачі, як і більшість підготовлених API-інтерфейсів інструкцій іншими мовами:

// PDO example with named parameters, unquoted $query = "INSERT INTO `table` (`id`, `col1`, `col2`, `date`) VALUES (:id, :col1, :col2, :date)" ; // MySQLi example with? parameters, unquoted $query = "INSERT INTO `table` (`id`, `col1`, `col2`, `date`) VALUES (?, ?, ?, ?)";

Символи, що повертають зворотне посилання в ідентифікаторах:

Наприклад:

Те саме можна зробити для імен таблиць і імен полів. Це дуже гарна звичка,якщо ви прив'язуєте свій ідентифікатор бази даних зворотними вікнами.

Перевірте це відповідь, щоб дізнатися більше про зворотні висновки.

Тепер про Double quotes & Single Quotes (Майкл вже згадав про це).

Але щоб визначити значення, вам потрібно використовувати одиночні або подвійні лапки. Давайте подивимося інший приклад.

INSERT INTO `tablename` (`id, `title`) VALUES (NULL, title1);

Тут я свідомо забув обернути title1 лапками. Тепер сервер прийме title1 як ім'я стовпця (тобто ідентифікатор). Отже, щоб вказати, що це значення, вам потрібно використовувати подвійні чи одинарні лапки.

INSERT INTO `tablename` (`id, `title`) VALUES (NULL, "title1");

Тепер, у поєднанні з PHP, подвійні лапки та одинарні лапки значно спрощують час написання запиту. Давайте подивимося на змінену версію запиту у вашому питанні.

$query = "INSERT INTO `table` (`id`, `col1`, `col2`) VALUES (NULL, "$val1", "$val2")";

Тепер, використовуючи подвійні лапки в PHP, ви зробите змінні $val1 і $val2 щоб використовувати їх значення, створивши цим коректний запит. подібно

$ val1 = "my value 1"; $ val2 = "my value 2"; $query = "INSERT INTO `table` (`id`, `col1`, `col2`) VALUES (NULL, "$val1", "$val2")";

INSERT INTO `table` (`id`, `col1`, `col2`) VALUES (NULL, "my value 1", "my value 2")

Тут було багато корисних відповідей, що загалом досягають найвищої точки в два пункти.

  1. BACKTICKS (`) використовуються навколо імен ідентифікаторів.
  2. ОДНЕ ЦИТАТИ (") використовуються навколо значень.

І як @MichaelBerkowski сказав

Backticks повинні використовуватися для ідентифікаторів таблиць і стовпців, але потрібні лише тоді, коли ідентифікатор є зарезервованим ключовим словом MySQL або коли ідентифікатор містить символи пропуску або символи за межами обмеженого набору (див. нижче). Часто рекомендується уникати використання зарезервованих ключових слів як ідентифікаторів стовпців або таблиць, якщо це можливо, щоб уникнути проблеми з лапками.

Існує випадок, коли ідентифікатор не може бути зарезервованим ключовим словомабо утримувати пробільні символиабо символи, що знаходяться за межами обмеженого набору,але обов'язково вимагають зворотних посилань довкола них.

123E10 - це допустиме ім'я ідентифікатора, але дійсний літерал INTEGER .

[Не вдаючись у подробиці, як ви отримаєте таке ім'я ідентифікатора], Припустимо, я хочу створити тимчасову таблицю з ім'ям 123456e6.

Ні ПОМИЛКИ на backticks.

DB > create temporary table `123456e6` (`id` char (8)); Query OK, 0 rows affected (0.03 sec)

ПОМИЛКА, якщо ви не використовуєте зворотні дзвінки.

DB > create temporary table 123451e6 ( `id` char (8)); ERROR 1064 (42000): Ви маєте помилку в вашій SQL syntax; check the manual that corresponds to your MariaDB server version for right syntax to used near "123451e6 (`id` char(8))" at line 1

Тим не менш, 123451a6 - це чудове ім'я ідентифікатора (без зворотних тиків).

DB > create temporary table 123451a6 ( `id` char (8)); Query OK, 0 rows affected (0.03 sec)

Це повністю тому, що 1234156e6 також є експонентним числом.

Одинарні лапки слід використовувати для рядкових значень, наприклад у списку VALUES ().

Backticks зазвичай використовуються для вказівки ідентифікатора, а також можуть бути безпечними через випадкове використання зарезервованих ключових слів.

У поєднанні PHP та MySQL подвійні лапки та одинарні лапки значно спрощують час написання запитів.

У MySQL є два типи котирувань:

  1. для включення рядкових літералів
  2. ` для включення ідентифікаторів, таких як імена таблиць та стовпців

І тоді є " це особливий випадок. Він може використовуватися для однієїз вищезгаданих цілей за раз залежно від sql_mode сервера sql_mode :

  1. За замовчуванням"символ" можна використовувати для вкладення рядкових літералів "
  2. У режимі ANSI_QUOTES "символ може використовуватися для включення ідентифікаторів, ANSI_QUOTES

Наступний запит призведе до різних результатів (або помилок) залежно від режиму SQL:

SELECT "column" FROM table WHERE foo = "bar"

ANSI_QUOTES вимкнено

Запит вибиратиме рядковий літерал "column" де стовпець foo дорівнює рядку "bar"

Включено ANSI_QUOTES

Запит вибиратиме стовпець column де стовпець foo дорівнює стовпцю

Коли використовувати

  • Я пропоную вам уникати використання " щоб ваш код не залежав від режимів SQL
  • Завжди вказуйте ідентифікатори, оскільки це хороша практика (досить багато питань на SO обговорюють це)

Існує чітка різниця між використанням "" та "".

Коли " " використовується у всьому, немає "трансформації або перекладу". Він друкується так, як є.

З " " , незалежно від того, що він оточує, "перекладається або перетворюється" на його цінність.

Під трансляцією/перетворенням я маю на увазі наступне: все, що міститься в одинарних лапках, не буде переведено на їх значення. Вони будуть прийняті, оскільки вони знаходяться всередині лапок. Приклад: a=23 , тоді echo "$a" генеруватиме $a на стандартному виході. У той час, як echo "$a" зробить 23 на стандартному виході.

(PHP 4> = 4.3.0, PHP 5)

mysql_real_escape_string Escapes specific characters in string for use in an SQL statement

Description

mysql_real_escape_string (string $unescaped_string [, resource $link_identifier = NULL]) : string

Escapes особливі характеристики в unescaped_string , використовуючи поточний характер набору з'єднання з тим, що він є надійним в місці його в mysql_query(). Якщо binary data is to inserted, ця функція повинна бути використана.

mysql_real_escape_string() calls MySQL's library function mysql_real_escape_string, які підтримують backslashes to the following characters: \x00, \n, \r, \ , " , " and \x1a.

Ця функція мусить будь-коли (з кожним виразом) бути використана для отримання даних надійно, щоб скористатися MySQL.

Caution

Security: the default character set

Character set must be set either at the server level, or with the API function mysql_set_charset() for it to affect mysql_real_escape_string() . Натисніть на розділи розділу на рисунок розділів для більшої інформації.

Parameters

unescaped_string

string that is to be escaped.

Link_identifier

The MySQL connection. Якщо link identifier is not specified, the last link opened by mysql_connect() is assumed. Якщо не такий link є відомості, він буде робити, щоб створити один як якщо mysql_connect() had been called with no arguments. Якщо не з'єднано, то з'єднано або встановлено, E_WARNINGрівень error is generated.

Return Values

Returns the escaped string, or FALSE on error.

Errors/Exceptions

Executing ця функція без MySQL connection present will also emit E_WARNINGрівень PHP errors. Тільки виконує цю функцію з правильним connection present MySQL.

Examples

Example #1 Simple mysql_real_escape_string() example

// Connect
$link = mysql_connect ("mysql_host", "mysql_user", "mysql_password")
OR die(mysql_error());

// Query
$query = sprintf ( "SELECT * FROM users WHERE user="%s" AND password="%s"",
mysql_real_escape_string ($user ),
mysql_real_escape_string ($password));
?>

Example #2 mysql_real_escape_string() requires a connection example

Цей приклад демонструє те, що happens if MySQL connection is not present when calling this function.

Докладніше про те, що ви бачите деякий подібний до:

Warning: mysql_real_escape_string(): Немає такого файлу або directory в /this/test/script.php on line 5 Warning: mysql_real_escape_string(): Link to server could no established in /this/test/script.php on line 5 bool(false) string(41) "SELECT * FROM actors WHERE last_name = """

Example #3 An example SQL Injection Attack

// We didn"t check $_POST["password"], it could be anything the user wanted! For example:
$_POST ["username"] = "aidan";
$_POST [ "password" ] = "" OR ""="" ;

// Query database to check if there are any matching users
$query = ( $_POST [ "username" ]) " AND password=" ( $_POST [ "password" ]) "" ;
mysql_query ($query);

// Це означає, що ми можемо бути MySQL would be:
echo $query;
?>

The query sent to MySQL:

Це буде дозволено будь-який один log in with a valid password.

Notes

A MySQL connection is required before using mysql_real_escape_string() іншіwise an error of level E_WARNING is generated, and FALSE is returned. Якщо link_identifier isn"t defined, the last MySQL connection is used.

Note: mysql_real_escape_string() does not escape % and _ . Вони є wildcards в MySQL і якщо вони з'єднані з LIKE, GRANT, or REVOKE.

8 років тому

Just a little function which mimics the original mysql_real_escape_string but which doesn"t need an active mysql connection.

function mysql_escape_mimic ($inp) (
if(is_array ($inp ))
return array_map (__METHOD__, $inp);

If(!empty($inp ) && is_string ($inp )) (
return str_replace (array("\\" , "\0" , "\n" , "\r" , """ , """ , "\x1a" ), array("\\\" , "\ \0" , "\n" , "\r" , "\"" , "\\"" , "\Z" ), $inp );
}

Return $inp;
}
?>

13 років тому

Зверніть увагу, що mysql_real_escape_string не "підключені backslashes до \x00, \n, \r, і \x1a mentionned in the documentation, але в даний час replaces the character with MySQL acceptable representation for queries (e.g. \n is replaced with the "\" n" litteral."

6 років тому

Відсутність роздумів про те, що ні в якому разі не пов'язано з тим, що ви повинні basically never use external input to generate interpreted code. Це goes for SQL statements, або будь-який вам потрібно call any sort of "eval" function on.

Так, усвідомлює використання цієї территорічно відбитої функції, використовуючи parametric preparated statements instead.

Безумовно, використовуючи користувача, що надсилається data до compose SQL statements, слід розглядати як професійне невідповідність і ви повинні бути пристосовані до вашого працівника або клієнта, не використовуючи parametric preparated statements.

What does that mean?

It means instead of building a SQL statement like this:

"INSERT INTO X (A) VALUES(".$_POST["a"].")"

Ви повинні використовувати mysqli's prepare() function () для execute a statement that looks like this:

"INSERT INTO X (A) VALUES(?)"

NB: Це не означає, що ви не повинні генерувати dynamickі SQL statements. been prepared.

Так, для прикладу, якщо ви збираєтеся в маленькій рамці і хотіли б, щоб вибрати основу, що базується на потрібній URI, це "в вашому найкращому інтересі не буде $_SERVER["REQUEST_URI"] value (or any part of it) і directly concatenate that with your query. Якщо mapping produces no value, ви знаєте, що деякий час є з'єднаним з користувачем.

Помітно, що це буде бути пов'язано з числом SQL-injection problémy в Ruby On Rails framework, навіть якщо це використовується параметрами відповідних штатів. This is how GitHub was hacked at one point. So, no language is immune to this problem. "Це", з якою це загальна практика і не деякий специфічний для PHP і який ви повинні реально adopt it.

Також, ви повинні залишатися в кількох віршах з validation of data, що надаються користувачами, навіть якщо за допомогою parametric preparated statements. Це означає, що user-provided data буде встановлена ​​як частина певного HTML, і ви будете отримувати доступ до того, що user-запрограмована data isn"t going to cause security problems in the browser.

9 років тому

There's an interesting quirk в тені #2 про SQL injection: AND таки priority over OR, so the injected query в даний час executes as WHERE (user="aidan" AND password="") OR ""="", so instead з відновлення вашого database запису, відповідного до arbitrary username (в цьому випадку "aidan"), це буде, як правило, відновити ALL database records. control over which account it is.

З курсу потенційного аттакера можна тільки змінити свої параметри до списку конкретних користувачів:

// E.g. attacker's values
$_POST ["username"] = "";
$_POST [ "password" ] = "" OR user = "administrator" AND "" = "";

// Malformed query
$query = "SELECT * FROM users WHERE user="$_POST [username] "AND password=" $_POST [password] "";

echo $query;

// The query sent to MySQL would read:
// SELECT * FROM users WHERE user="" AND password="" OR user="administrator" AND ""="";
// Хто може отримати будь-який час, щоб отримати доступ до облікового запису "administrator"

?>

1 рік тому

@feedr
I elaborated his note as following:
$string = "asda\0sd\x1aas\\\\dasd\"asdasd\na\"\sdasdad";
$array1 = array("\\\\\\", "\0", "\n", "\r", """, """, "\x1a");
$array2 = array("\\\\\\\\\\\\\", "\0", "\n", "\r", "\\" ","\", "\Z");
echo($string);
echo(PHP_EOL);
for($i=0; $i if ($i==0)
$p = "/(?else
$p = "/(?echo($i);
echo ($ p);
echo($array2[$i]);
$string = preg_replace($p, $array2[$i], $string);
echo("\t");
echo($string);
echo(PHP_EOL);
}
echo(PHP_EOL);
echo($string);

2 years ago

To Quote Sam at Numb Safari

[ "Не сказано про те, що ви збираєтеся здійснити без повідомлень кожного, що ви повинні basicalle never use external input to generate interpreted code. Ці goes for SQL statements, або будь-який ви повинні бути якомога разом sort "eval" функцію on.

Так, усвідомлює використання цієї территорічно відбитої функції, використовуючи parametric preparated statements instead.

Безумовно, використовуючи користувача, що надсилається data до compose SQL statements, слід розглядати як професійну невідповідність, і ви повинні бути пристосовані до вашого працівника або клієнта, не використовуючи parametric preparated statements." ]

Sam is right........

Але я не маю на увазі, що це чутливий до переривання всієї санітарної і простий хід шпильки на parametric preparated statements.

Пріоритетний розробник працює в особливій ситуації буде завжди знати більше про valid input (specific to that context).

Якщо ви знайдете користувача, щоб програти в цінності, ви маєте продовжувати дати їм і ви знаєте, що всі ці значення Start AB****** and string should be length 7 or 11 but never any other length then you have the basis of good pre-sanitiser - різні відхилені ланки з string might indicate legacy data.

Я не хотів би, щоб тільки pass rubby, що малий користувач може бути пройдений через форму для регулярних підпорядкованих статей, я можу, щоб у мене власних медичних ситуацій вперше і в будь-якому випадку цей ерр на стороні caution simple choose to abort the Database op completely.

Те, що ми DB, не може бути знижена з незначними статтями, щоб зробити надійний - це сприймає, що не може бути зроблено, з якою використовується.

Security in layers - sanitisation і validation should still be considered in every situation BEFORE using prepared statements.

In addition as far as I can read into official doc
==============================================

"Escaping and SQL injection

Базові вариації є тим, що для сервера окремо від трейлера і це не може interfere with it. Веб-сервер використовує ці значення прямо на пункті дії, після того, як template statement is parsed. Bound parameters do not need to be escaped as they are never substituted into query string directly"

Що suggests to me that danger is avoided in internals by alternative handling not by nullification.

Це означає, що великий проект з неповною конверсією до відповідних статей, легітимний код в різних частинах організації або серверів говорячи про одну іншу можна по всьому проходженню від поганих повідомлень від небагато місцевості або розміщення до одного, що не є особливим.

Як тривалий час, як sanitisation є стабільно виконаний безвідповідних можливих ризиків, то індивідуально я буду stick with certain layers of sanitisation and call the prepared statements.


Для початку – трохи про те, чому взагалі потрібні ці сліші.
Якщо ми запитуємо будь-які дані, то, щоб відрізнити ці дані від команд SQL, їх треба брати в лапки.
Наприклад, якщо написати
SELECT * FROM table WHERE name = Bill
то база вирішить, що Bill це ім'я іншого поля, не знайде його, і видасть помилку. Тому дані (в даному випадку ім'я Bill) треба укладати в лапки - тоді база визнає його рядком, значення якого треба присвоїти полю name:
SELECT * FROM table WHERE name = "Bill"
Однак, і в самих даних можуть зустрічатися лапки теж. Наприклад,
SELECT * FROM table WHERE name = "Д"Артаньян"
Тут база даних вирішить, що "Д" – це дані, а Артаньян – команда, яку вона не знає, і теж видасть помилку. Тому і треба прослідковувати всі дані, щоб пояснити базі, що лапки (і деякі інші спецсимволи), що зустрічаються в них, відносяться до даних.
В результаті ми отримаємо правильний запит, який помилок не викликає:
SELECT * FROM table WHERE name = "Д"Артаньян"

Таким чином, ми з'ясували, що при підстановці строкових даних у запит слід дотримуватися двох правил:
- всі строкові дані, що вставляються, повинні бути укладені в лапки (одинарні або подвійні, але зручніше і частіше використовуються одинарні).
- у них мають бути екрановані слішами спецсимволи.

Слід спеціально відзначити: додані сліші не йдуть до бази. Вони потрібні лише у запиті. При попаданні в базу сліші відкидаються. Відповідно, поширеною помилкою є застосування stripslashes при отриманні даних з бази.

Все вищесказане відноситься до даних рядкового типу та дат. Числа можна вставляти не простежуючи і не оточуючи лапками. Якщо ви так робите, то ОБОВ'ЯЗКОВО!насильно наводьте дані до потрібного типу перед вставкою в запит, наприклад:
$ id = intval ($ id);
Однак для простоти (і надійності) можна і з числами працювати, як з рядками (проскольку mysql все одно перетворює їх до потрібного типу). Відповідно, ми будемо будь-які дані, які вставляються в запит, простежувати і укладати в лапки.

Також є ще одне правило - необов'язкове, але його слід дотримуватися, щоб уникнути появи помилок:
Імена полів і таблиць слід укладати у зворотні одинарні лапки - "`" (клавіша з цим символом знаходиться на стандартній клавіатурі зліва від клавіші "1") Адже ім'я поля може збігатися з ключовими словами mysql, але якщо ми використовуємо зворотну лапку, то MySQL зрозуміє все правильно:
SELECT * FROM `table` WHERE `date` = "2006-04-04"
Слід розрізняти ці лапки і плутати одні з іншими. Слід також пам'ятати, що зворотні лапки слішами не екрануються.

Отже, ми навчилися правильно підставляти дані запит.
АЛЕ! Динамічний складання запитів не вичерпується підстановкою даних. Часто нам доводиться підставляти запит команди SQL і імена полів. І тут ми вже переходимо до теми безпеки:

SQL Injection - це спосіб хакерської атаки, коли передані скрипту дані модифікуються таким чином, що запит, який формується в цьому скрипті, починає виконувати зовсім не те, для чого він призначався.
Правила захисту від таких атак можна розділити на два пункти:
1. Робота з даними.
2. Робота з керуючими елементами запиту.

Перший пункт ми докладно розглядали вище. Він, можна сказати, і не є, власне, захистом. Дотримання правил додавання в запит продиктовано, в першу чергу, вимогами СИНТАКСИСУ SQL. А як побічний ефект маємо і захист від злому.

Другий пункт набагато складніше, оскільки не існує такого ж єдиного універсального правила, як для даних – зворотна лапка ніяк не захистить ім'я поля від модифікації хакером. Неможливо захистити ім'я таблиці, оператори SQL, параметри команди LIMIT та інші оператори.
Тому основне правило при підстановці керуючих елементів на запит таке:
Якщо потрібно динамічно підставляти в запит оператори SQL або імена полів, баз даних, таблиць, то ні в якому разі не вставляти їх у запит безпосередньо.
Всі варіанти таких додавань повинні бути заздалегідь прописані у вашому скрипті та вибиратися на підставі того, що ввів користувач.
Наприклад, якщо треба передати ім'я поля в оператор order by, то в жодному разі не можна підставляти його безпосередньо. Потрібно спочатку перевірити його. Наприклад, створити масив допустимих значень, і підставляти в запит тільки якщо переданий параметр у цьому масиві є:
$orders =array("name" , "price" , "qty" );
$key = array_search ($_GET ["sort"], $orders));
$orderby = $orders [$key];
$query = "SELECT * FROM `table` ORDER BY$orderby";

Ми шукаємо в масиві заздалегідь описаних варіантів введене користувачем слово, і, якщо знаходимо, вибираємо відповідний елемент масиву. Якщо збігу не буде знайдено, буде обрано перший елемент масиву.
Таким чином, у запит підставляється не те, що запровадив користувач, а те, що було прописано у нас у скрипті.
Так само треба чинити і в усіх інших випадках
Наприклад, якщо динамічно формується оператор WHERE:
if (!empty($_GET [ "price" ])) $where .= "price="" . mysql_real_escape_string ($_GET [ "price" ]). """ ;
$query = "SELECT * FROM `table` WHERE $where";

Мені складно уявити собі випадок, коли ім'я таблиці може підставлятися в запит динамічно, але якщо таке трапиться, то ім'я теж треба вставляти лише з набору, що заздалегідь прописаний у скрипті.
Параметри оператора LIMIT слід примусово приводити до цілого типу за допомогою арифметичних операцій або функції intval().
Не слід думати, що наведеними тут прикладами вичерпуються всі варіанти динамічного складання запитів. Потрібно просто зрозуміти принцип і застосовувати його у всіх подібних випадках.

За своєю діяльністю, мені доводиться виконувати аудити безпеки вихідного коду веб-додатків.
Багато веб-застосунків і багато коду…

Не секрет, що уразливості впровадження операторів СУБД (SQL injections) є найпоширенішими з усіх серверних уразливостей веб-додатків. Є платформи та фреймворки, де такі речі практично повністю виключені, наприклад ORM"ом та іншим. Але статистика завзято говорить нам про абсолютну перевагу на просторах Інтернету веб-додатків з простими конкатенованими SQL запитами. Крім того, є випадки, де ORM взагалі застосовуємо бути не може, наприклад, коли від даних користувача повинні залежати не тільки параметри виразів, але й сама логіка запиту на рівні операторів.

Тож почнемо.

Некорисне екранування символів
Знайдено в 83% РНР веб-додатків, вразливих до SQL-ін'єкцій
Застосування функції екранування символів, таких як
mysql_escape_string
mysql_real_escape_string
addslashes
без обрамлення лапками. Найчастіше проявляється у числових параметрах (різні *_id).
приклад
$sql = "SELECT user FROM userslist WHERE userid=".mysql_real_escape_string($_GET["uid"]);

На вигляд це безпечний код, але тільки на вигляд. Сюди закрався найчастіший у моїй практиці шаблон SQL-ін'єкцій у РНР. Для проведення атаки на цю вразливість від зловмисника потрібно просто не використовувати символи \x00\r\n\x1a у векторі атаки.
Наприклад:
/index.php?uid=-777 UNION SELECT password FROM userlist

Пошук у коді
Ускладнений семантикою мови. Для простого пошуку можна використовувати egrep:
egrep -Rin "(select|update|insert|delete|replace).*(from|set|into).*(mysql_escape_string|mysql_real_escape_string|addslashes)" . | grep -v "[\""]["\"]"

Логіка пошукового виразу така - знайти всі рядки, в яких ліворуч від функцій фільтрації немає послідовності символів лапок ("", "", "", ""). Метод, зрозуміло, не 100%, але вимагати від регулярного висловлювання виконати семантичний аналіз неможливо.
Для зручності виведення інформації можна підсвітити функцію кольором в консолі:
egrep -Rin "(select|update|insert|delete|replace).*(from|set|into).*(mysql_escape_string|mysql_real_escape_string|addslashes)" . | grep -v "[\""]["\"]" | egrep --color "(mysql_escape_string|mysql_real_escape_string|addslashes)"

Для захисту від цієї шаблонної вразливості, краще використовувати приведення типів.
Це завжди швидше працює і надійніше ніж усілякі фільтрації та екранування.
Наприклад, патч може бути таким:
$sql = "SELECT user FROM userslist WHERE userid=".intval($_GET["uid"]);

На цьому короткий нарис закінчено. Закликаю всіх веб-розробників спробувати грінути свої вихідники щодо таких конструкцій. А ще краще розширити наведений пошуковий скрипт для людей.

Тому в основному я копав глибоко в області MySQL та PHP … зокрема, заходи безпеки, які я повинен вживати під час роботи з базою даних та введенням форм. Досі я знайшов, що рекомендується наступне:

  1. Підготовлені заяви
  2. Використання _real_escape_string()
  3. Не використовуючи магічні лапки, оскільки він збиває бази даних та закінчує тим, що дає вам такі речі, як «Ти назвав це не…».

Все це чудово, і я стежу за ним. Проте, мені було цікаво, чи слід уникати символів, таких як знак долара [$], знак відсотка [%] та, можливо, інші. Чи міг запит інтерпретувати знак долара як змінну PHP, можливо? Що про синтаксис LIKE, який я чув, використовує символ% чи навіть знак підстановки? Підготовлені заяви повинні технічно подбати про все це, але я просто хотів бути в безпеці та переконатися, що в мене все вислизнуло належним чином. У випадку, коли я забуваю використовувати підготовлені заяви або просто нехтувати ними, я сподівався, що ця друга лінія оборони може сказати мені, що я можу позбутися запаморочення.

Ось що я зараз використовую для екранування:

Function escape($connection, $data)( $new_data = trim($data); $new_data = i_real_escape_string($connection, $new_data); $new_data = addcslashes($new_data, "%_$"); $new_data = htmlspecial ($new_data, ENT_NOQUOTES); return $new_data; )

То це правильно? Я роблю щось дуже неправильно? Зауважте, що при поверненні даних бази даних мені доведеться видалити зворотні коси перед символами $,% і _.

Я роблю щось дуже неправильно?

Спочатку про ваші дослідження.

Підготовлені заяви єдиначудова річ, яку ви знайшли.

Хоча використання mysqli_real_escape_string (за умови, що ви використовуєте підготовлені інструкції) було б марним і шкідливим(Створюючи результат, який ви відзначили самі: «Ви назвали isn\t...»).

І Magic Quotes вже давно видалені з мови – таким чином, насправді нічого не варте.

Таким чином, навіть більшість ваших початкових приміщень явно хибні.

Тепер на ваше запитання.

Чи міг запит інтерпретувати знак долара як змінну PHP, можливо?

Що про синтаксис LIKE, який я чув, використовує символ% чи навіть знак підстановки?

Так, ви чули це правильно. Точна мета оператора LIKE – здійснити пошук за шаблоном. Відключення цих символів у LIKE не мало б жодного сенсу.

Щоразу, коли ви збираєтеся використовувати LIKE-оператор, ви повинні вирішити, який конкретний символ використовувати і який заборонити. Не можна використовувати одноразове рішення. Не кажучи вже про те, що в інших взаємодіях mysql знак% не має ніякого особливого значення.

Підготовлені заяви повинні технічно подбати про все це

Підготовлені твердження немає нічого спільного ні з $, ні з знаками%. Підготовлені твердження відносяться до SQL-ін'єкцій, але жоден символ не може викликати його (не могли б ви назвати «ін'єкцію» оператором LIKE, який належним чином використовується, чи не так?).

Зрештою, до найжахливішої частини.

У випадку, якщо ви забудете використовувати підготовлені заяви або просто нехтуєте їх виконанням,

ніщо не врятує вас.

І найменша допомога була від тієї функції, яку ви розробили.

Підвести підсумок.

  1. Позбутися цієї функції.
  2. Використовуйте заповнювачі *для подання кожної окремої змінної у запиті.
  3. Escape % та _ символи у вхідних даних, тільки якщо вони будуть використовуватися в LIKE-операторі, і ви не хочете, щоб їх інтерпретували.
  4. Використовуйте htmlspecialchars() для виведення, а не для введення mysql.

* прочитайте підготовлені заяви, якщо цей термін вам не знайомий.

Вам не потрібно уникати знак долара. MySQL не розглядає цей характер спеціально, і PHP тільки розпізнає його у вихідному коді, а не в рядкових значеннях (якщо ви не викликаєте eval у рядку, але це цілий інший черв'як з черв'яків).

Вам потрібно буде тільки уникнути % і _ якщо ви використовували введення в якості аргументу LIKE і ви не хотіли, щоб користувач міг використовувати підстановочні знаки. Це може виникнути, якщо ви обробляєте форму пошуку. Вам не потрібно використовувати його під час зберігання в базі даних.

При доступі до бази даних вам не потрібно використовувати htmlspecialchars. Це слід використовувати лише при відображенні даних користувачеві на сторінці HTML, щоб запобігти впровадженню XSS.

Залежно від того, які дані та навіщо вони використовуються.

Якщо ви виявите, що готові заяви за промовчанням PHP занадто великі і складні, я пропоную поглянути на деякі класи, доступні на github, щоб дати вам уявлення про спрощені запити.

Приклад вставки запитів із цим класом

$data = Array ("login" => "admin", "active" => true, "firstName" => "John", "lastName" => "Doe", "password" => $db->func( "SHA1(?)",Array ("secretpassword+salt")), // password = SHA1("secretpassword+salt") "createdAt" => $db->now(), // createdAt = NOW() " expires" => $db->now("+1Y") // expires = NOW() + interval 1 year // Supported intervals [s]econd, [m]inute, [h]hour, [d]day, [M]onth, [Y]ear); $id = $db->insert ("users", $data); if ($id) echo "user was created. Id=" . $id; else echo "insert failed:". $db->getLastError();