Найти тему
ZDG

Разрабатываем сайт #9: Финал CRUD и SQL-инъекции

Предыдущие части: Заполнение формы, Действие Update, CRUD, Форма редактирования, PHP и база данных, Идентификаторы, Работаем с таблицами, Создаём базу данных, Установка MySQL

Читайте также: Как работает веб-сайт, Бэкэнд на PHP, Введение в язык PHP

Для завершения CRUD осталось сделать две задачи: просмотр записи и удаление записи. Это будет легко, так как вся подготовка уже проведена.

Я также выложил текущий проект на github, можете смотреть файлы там и скачивать себе.

Просмотр записи

Контроллер note.php должен распознать параметр action=view, загрузить из базы модель с нужным id, и отрендерить представление view.php. То есть всё так же, как при редактировании записи, только будем рисовать не форму, а просто саму запись.

Начнём с доработки контроллера:

Если action=view и id не пустой, вызываем функцию view()... Оказывается, мы это уже давно написали, когда делали контроллер, оставалось только реализовать саму функцию view():

-2

Сущая ерунда. Теперь перейдём к представлениям и создадим новое представление views/view.php для отображения данных из модели:

-3

Всё! Мы можем просматривать записи и, для удобства навигации, пользоваться ссылками для возврата в список, редактирования и удаления:

-4

Ссылки, конечно, выглядят страшно, как и всё остальное, но это технологический макет. Украшать будем отдельно.

Удаление записи

Так как контроллер уже опознаёт action=delete, нам осталось написать только функцию delete():

-5

Нашли запись по id, удалили её с помощью SQL-команды DELETE, и сделали перенаправление браузера назад на список записей, так как у удаления нет собственного представления. Хотя мы могли бы сделать что-то вроде страницы с надписью "Запись была успешно удалена" и отрендерить её.

Обратите внимание: зачем искать запись? Не проще ли, получив id, сразу вызвать команду удаления? Ведь если id существует в базе, то запись будет удалена, а если нет – тогда ничего не произойдёт. В любом случае ничего неправильного.

Да, но иногда нам нужно точно знать, что где-то есть ошибка. Например, переданный нам id некорректен, и значит что-то где-то пошло не так. Это должно нас насторожить. Предварительная проверка по базе поможет отфильтровать такие случаи.

Также мы записываем результат удаления (успех/неуспех) в переменную $result, но не проверяем и не используем её, она пропадает зря. Просто на данный момент я хочу показать абсолютно минималистичный по функционалу код, чтобы ничего не мешало. Когда требования к проекту начнут ужесточаться, придётся добавить множество разных проверок на корректность.

Безопасность

Что ж, вот мы и реализовали CRUD-контроллер. Мы можем создавать, просматривать, редактировать и удалять записи.

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

SQL-инъекция

При обращении к базе мы вставляем, например, значение переменной $id в SQL-cтроку:

select * from note where id='$id'

Когда $id равен 1, 5, 100 и т.д., это штатная ситуация. Мы получим:

select * from note where id='5'

Даже когда $id равен "какой-то текст", тоже ничего. Мы всего лишь получим:

select * from note where id='какой-то текст'

Но если присвоить $id вот это:

'; delete from note; --

То наша SQL-строка в результате примет вид:

select * from note where id=''; delete from note; -- '

И все записи в таблице note будут удалены. Почему?

Потому что апостроф ' закрывает строку, а символ ";" разделяет SQL-команды. Мы вписали в $id специально подобранные символы, чтобы получился конец одной команды и началась другая, которая удаляет записи. В конце стоят символы " -- ", это комментарий, чтобы игнорировался последний апостроф. Получились две валидные команды, которые выполнятся друг за другом:

select * from note where id='';
delete from note;

Мы осуществили инъекцию, внедрив в SQL-команду под видом параметра ещё одну команду.

Как от этого защититься? Нужно просто экранировать апостроф внутри параметра, чтобы он не считался символом, закрывающим строку. Как и во многих других языках, перед ним надо поставить обратный слэш (\):

select * from note where id='\'; delete from note; -- '

Тогда с помощью апострофа, который находится в $id, строку будет нельзя закрыть и начать новую команду. Всё, что есть в $id, будет оставаться обычной строкой до заключительного – нашего собственного – апострофа.

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

-6

Мы вставляем значения в SQL не просто так, а с помощью функции quote(). Но на тот момент мы её просто не дописали, и она пока только заключает значения в апострофы:

-7

И значит, пора её дописать, чтобы она экранировала апострофы внутри переменной $value. Сделать это можно разными способами.

1. Поиск и замена с помощью функции замены строки

str_replace("'", "\\'", $value)

Апостроф заменяется на обратный слэш и апостроф. Здесь мы пишем два обратных слэша, потому что в PHP, как и в других языках, обратный слэш это управляющий символ, так что нам нужен слэш, чтобы экранировать слэш.

2. Использование специальной функции:

mysqli_real_escape_string($cn, $value)

Результат будет одинаковый, но я предлагаю использовать специальную функцию. Она предназначена именно для SQL, и поэтому лучше знает, что и как экранировать. Но в неё нужно передавать параметр $cn – соединение с базой. Значит, этот параметр придётся также передать в функцию quote():

-8

Да, где-то в начале цикла я писал, что кроме расширения mysqli есть расширение pdo, которое немного по-другому устроено, и работать с ним чуть сложнее. Там такие вопросы решены уже на базовом уровне, но мы до этого доберемся в будущем.

Ну вот, проблему безопасности SQL мы более-менее решили. Главное, не забывать оборачивать все параметры, которые используются при составлении SQL-запросов, в функцию quote(). Я сделал соответствующие изменения в проекте, здесь их приводить не буду ввиду тривиальности (всё лежит на гитхабе).

Осталась ещё одна проблема. Примерно такие же инъекции нас ждут в HTML-вёрстке, когда мы будем выводить данные из таблицы или помещать их в форму. И хотя там невозможно чем-то сильно навредить, но сломать внешний вид страницы можно запросто. Обсудим в следующей части.

Читайте дальше: Борьба с HTML