SQL-инъекция и как ее предотвратить в PHP-приложениях

Thank you for reading this post, don't forget to subscribe!

Допу­стим, вы созда­е­те типич­ный веб-сайт PHP для мест­но­го мага­зи­на элек­трон­ной ком­мер­ции, поэто­му вы реши­ли доба­вить кон­такт­ную фор­му, например:

[codesyntax lang="php"]

[/codesyntax]

И давай­те пред­по­ло­жим, что файл send_message.php хра­нит все в базе дан­ных, что­бы вла­дель­цы мага­зи­на мог­ли читать поль­зо­ва­тель­ские сообщения.

Напри­мер такой код:

[codesyntax lang="php"]

[/codesyntax]

Итак, вы сна­ча­ла пыта­е­тесь уви­деть, есть ли у это­го поль­зо­ва­те­ля непро­чи­тан­ное сооб­ще­ние. Запрос SELECT * from messages where name = $name, кажет­ся доста­точ­но про­стым, верно?

НЕПРАВИЛЬНО!

Теперь мы откры­ли две­ри для мгно­вен­но­го уни­что­же­ния нашей базы данных.

Для это­го зло­умыш­лен­ни­ку необ­хо­ди­мо выпол­нить сле­ду­ю­щие условия:

  • При­ло­же­ние рабо­та­ет на базе дан­ных SQL (сего­дня почти каж­дое при­ло­же­ние рабо­та­ет на SQL)
  • Теку­щее под­клю­че­ние к базе дан­ных име­ет раз­ре­ше­ния «редак­ти­ро­вать» и «уда­лять» в базе данных.
  • Назва­ния важ­ных таб­лиц мож­но угадать

Тре­тий момент озна­ча­ет, что теперь, когда зло­умыш­лен­ник зна­ет, что вы управ­ля­е­те мага­зи­ном элек­трон­ной ком­мер­ции, вы, ско­рее все­го, сохра­ни­те дан­ные зака­за в таб­ли­це orders.

Воору­жив­шись всем этим, все, что нуж­но сде­лать зло­умыш­лен­ни­ку, это ука­зать имя:

Joe; truncate orders;

Давай­те посмот­рим, каким будет запрос, когда он будет выпол­нен скрип­том PHP:

SELECT * FROM messages WHERE name = Joe; truncate orders;

Хоро­шо, в пер­вой части запро­са есть син­так­си­че­ская ошиб­ка (без кавы­чек вокруг «Joe»), но точ­ка с запя­той вынуж­да­ет дви­жок MySQL начать интер­пре­та­цию ново­го: truncate orders.

Про­сто так, одним махом, вся исто­рия зака­зов исчезла!

Теперь, когда вы зна­е­те, как рабо­та­ет SQL-инъ­ек­ция, при­шло вре­мя посмот­реть, как это оста­но­вить. Для успеш­но­го внед­ре­ния SQL необ­хо­ди­мо выпол­нить два условия:

  1. Скрипт PHP дол­жен иметь пра­ва на изме­не­ние / уда­ле­ние базы дан­ных. Я думаю, что это вер­но для всех при­ло­же­ний, и вы не смо­же­те сде­лать ваши при­ло­же­ния доступ­ны­ми толь­ко для чте­ния. И поду­май­те, что, даже если мы уда­лим все при­ви­ле­гии на изме­не­ние, SQL-инъ­ек­ция все еще может поз­во­лить кому-то запус­кать запро­сы SELECT и про­смат­ри­вать всю базу дан­ных, вклю­чая кон­фи­ден­ци­аль­ные дан­ные. Дру­ги­ми сло­ва­ми, сни­же­ние уров­ня досту­па к базе дан­ных не рабо­та­ет, ведь ваше при­ло­же­ние все рав­но нуж­да­ет­ся в этом.
  2. Поль­зо­ва­тель­ский ввод обра­ба­ты­ва­ет­ся. Един­ствен­ный спо­соб внед­ре­ния SQL-кода — это когда вы при­ни­ма­е­те дан­ные от поль­зо­ва­те­лей. Еще раз, не прак­тич­но оста­нав­ли­вать все вход­ные дан­ные для ваше­го при­ло­же­ния толь­ко пото­му, что вы бес­по­ко­и­тесь о внед­ре­нии SQL.

Предотвращение внедрения SQL инъекуции в PHP

Теперь, учи­ты­вая, что соеди­не­ния с базой дан­ных, запро­сы и поль­зо­ва­тель­ские вво­ды явля­ют­ся частью жиз­ни БД, то как мы можем предот­вра­тить внед­ре­ние SQL?

К сча­стью, это доволь­но про­сто, и есть два спо­со­ба сде­лать это:

1) очи­стить поль­зо­ва­тель­ский ввод и 2) исполь­зо­вать под­го­тов­лен­ные операторы.

Пользовательский ввод

Если вы исполь­зу­е­те более ста­рую вер­сию PHP (5.5 или ниже, и это часто слу­ча­ет­ся на вир­ту­аль­ном хостин­ге), было бы целе­со­об­раз­но выпол­нить весь поль­зо­ва­тель­ский ввод с помо­щью функ­ции mysql_real_escape_string ().

В основ­ном то, что он дела­ет, это уда­ле­ние всех спе­ци­аль­ных сим­во­лов в стро­ке, так что они теря­ют свое зна­че­ние при исполь­зо­ва­нии базой данных.

Напри­мер, если у вас есть стро­ка, похо­жая на стро­ку «I’m a strin», зло­умыш­лен­ник может исполь­зо­вать сим­вол оди­нар­ной кавыч­ки (‘) для мани­пу­ли­ро­ва­ния созда­ва­е­мым запро­сом к базе дан­ных и вызы­вать SQL-инъекцию.

Запуск его через mysql_real_escape_string () при­во­дит к появ­ле­нию стро­ки, кото­рая добав­ля­ет обрат­ную косую чер­ту к оди­ноч­ной кавыч­ке, избе­гая ее.

В резуль­та­те вся стро­ка теперь пере­да­ет­ся в базу дан­ных как без­вред­ная стро­ка вме­сто воз­мож­но­сти участ­во­вать в мани­пу­ли­ро­ва­нии запросами.

У это­го под­хо­да есть один недо­ста­ток: это дей­стви­тель­но очень ста­рая тех­ни­ка, кото­рая согла­су­ет­ся со ста­ры­ми фор­ма­ми досту­па к базе дан­ных в PHP.

Начи­ная с PHP 7, этой функ­ции боль­ше не суще­ству­ет, что под­во­дит нас к сле­ду­ю­ще­му решению.

Используйте готовые операторы

Под­го­тов­лен­ные опе­ра­то­ры — это спо­соб сде­лать запро­сы к базе дан­ных более без­опас­ны­ми и надежными.

Идея состо­ит в том, что вме­сто отправ­ки необ­ра­бо­тан­но­го запро­са в базу дан­ных мы сна­ча­ла сооб­ща­ем базе дан­ных струк­ту­ру запро­са, кото­рый мы будем отправлять.

Это то, что мы под­ра­зу­ме­ва­ем под «под­го­тов­кой».

Как толь­ко опе­ра­тор под­го­тов­лен, мы пере­да­ем инфор­ма­цию как пара­мет­ри­зо­ван­ные вход­ные дан­ные, что­бы база дан­ных мог­ла «запол­нить про­бе­лы», под­клю­чив вход­ные дан­ные к струк­ту­ре запро­сов, кото­рую мы отправ­ля­ли ранее.

Это отни­ма­ет любую осо­бую мощ­ность, кото­рую могут иметь вход­ные дан­ные, в резуль­та­те чего они будут рас­смат­ри­вать­ся как про­стые пере­мен­ные (или полез­ные нагруз­ки, если хоти­те) во всем процессе.

Вот как выгля­дят под­го­тов­лен­ные операторы:

[codesyntax lang="php"]

[/codesyntax]

Я знаю, что этот про­цесс кажет­ся излишне слож­ным, если вы нови­чок в под­го­тов­лен­ных опе­ра­то­рах, но кон­цеп­ция сто­ит затра­чен­ных усилий.

Для тех, кто уже зна­ком с рас­ши­ре­ни­ем PDO PHP и исполь­зу­ет его для созда­ния гото­вых опе­ра­то­ров, у меня есть неболь­шой совет.

Предупреждение: будьте осторожны при настройке PDO

Исполь­зуя PDO для досту­па к базе дан­ных, мы можем впасть в лож­ное чув­ство без­опас­но­сти. «Ах, хоро­шо, я исполь­зую PDO. Теперь мне не нуж­но думать ни о чем дру­гом », — тако­во наше мышление.

Это прав­да, что PDO (или под­го­тов­лен­ные MySQLi опе­ра­то­ры) доста­точ­но для предот­вра­ще­ния все­воз­мож­ных SQL-атак, но вы долж­ны быть осто­рож­ны при его настройке.

Обыч­но про­сто ско­пи­руй­те и вставь­те код из учеб­ни­ков или из ваших преды­ду­щих про­ек­тов и дви­гай­тесь даль­ше, но этот пара­метр может отме­нить все:

Реше­ние про­стое: убе­ди­тесь, что для этой эму­ля­ции уста­нов­ле­но зна­че­ние false.

Теперь PHP-скрипт вынуж­ден исполь­зо­вать под­го­тов­лен­ные опе­ра­то­ры на уровне базы дан­ных, предот­вра­щая все­воз­мож­ные SQL-инъекции.

Предотвращение SQLi с использованием WAF

Зна­е­те ли вы, что вы так­же може­те защи­тить веб-при­ло­же­ния от внед­ре­ния SQL с помо­щью WAF (бранд­мау­эр веб-приложений)?

И не толь­ко SQL-инъ­ек­ция, но и мно­гие дру­гие уяз­ви­мо­сти уров­ня 7, такие как меж­сай­то­вый скрип­тинг, непра­виль­ная аутен­ти­фи­ка­ция, меж­сай­то­вая под­дел­ка, рас­кры­тие дан­ных и т. д.