Внедрение SQL (SQLi) — это метод, с помощью которого злоумышленник может отправлять операторы SQL в приложение и выполнять их в базе данных приложения. Это позволяет злоумышленнику манипулировать этой базой данных неожиданными и непреднамеренными способами. Рассмотрим классический комикс XKCD «Подвиги мамы»:

Что случилось с этими студенческими записями?

Когда в классы зачисляются новые ученики, регистратор школы добавляет их в базу данных, которую они используют для ведения записей об учениках. Информация о самих студентах заносится в таблицу студентов. Если бы они использовали Rails, этот процесс выглядел бы примерно так:

ActiveRecord::Base.connection.execute("INSERT INTO students (name) VALUES ('#{name}');")

Если бы мои родители записывали меня в эту школу, это в конечном итоге расширилось бы до следующего оператора SQL:

INSERT INTO students (name) VALUES ('David Celis');

Тогда я на занятия! Но когда мама Бобби Таблса записывает его, вот что попадает в базу данных учеников:

INSERT INTO students (name) VALUES ('Robert'); DROP TABLE students;--');

Упс. Имя Бобби, Robert'); DROP TABLE students;--, закрывает объявление VALUES, а затем начинает собственное выражение: DROP TABLE students;. Этот оператор просто выполняется, и вот таблица students! На всякий случай последняя пара дефисов начинает комментарий, чтобы гарантировать игнорирование всего остального в операторе, чтобы никакие синтаксические ошибки не мешали запуску вредоносного SQL.

Как мы можем избежать этого в Rails?

В Rails наиболее распространенным способом взаимодействия с базой данных является использование ActiveRecord. ActiveRecord — это средство сопоставления объектных отношений (ORM) по умолчанию, которое Rails включает в свою структуру. ORM — очень удобный способ взаимодействия с базами данных; они позволяют разработчикам запрашивать, вставлять и манипулировать записями базы данных без необходимости вручную создавать операторы SQL. Например, помните тот предыдущий код для вставки нового студента в базу данных? При правильном использовании ActiveRecord этот код будет выглядеть примерно так:

Student.create(name: "David Celis") Student.create(name: "Robert'); DROP TABLE students;--")

Определенно удобнее, но у ORM есть дополнительное преимущество: как правило, их безопаснее использовать, чем не использовать. Во многих случаях ORM должным образом избегают ввода или создают параметризованные запросы, чтобы избежать атак SQLi. В приведенном выше примере ActiveRecord создаст подготовленный оператор и выполнит его с предоставленными значениями. Rails будет регистрировать следующее:

INSERT INTO "students" ("name") VALUES ($1) RETURNING "id" [["name", "Robert'); DROP TABLE students;--"]]

Давайте разберем это. В итоге мы получили готовый оператор:

INSERT INTO "students" ("name") VALUES ($1) RETURNING "id"

и значения привязки, которые будут переданы в него:

[["name", "Robert'); DROP TABLE students;--"]]

Фактический SQL, который выполняется в PostgreSQL, будет выглядеть примерно так:

PREPARE student_insert (varchar) AS INSERT INTO "students" ("name") VALUES ($1) RETURNING "id"; EXECUTE student_insert("'Robert'); DROP TABLE students;--");

Используя подготовленный оператор, ActiveRecord предотвращает выполнение нежелательных операторов SQL. Вместо этого эта строка рассматривается только как значение и не будет выполняться. Он просто сохраняется в новой студенческой записи, и никакого вреда не происходит. К сожалению, в ActiveRecord есть дыры, которые по-прежнему являются векторами для атак SQLi, если пользовательский ввод передается бессистемно. Во второй части этой серии из трех частей мы рассмотрим небезопасные элементы ActiveRecord и способы предотвращения взлома вашей базы данных. В части 3 мы рассмотрим, как Immunio активно защищает ваше приложение от попыток SQLi без необходимости изменения кода.

Первоначально опубликовано на www.immun.io 26 октября 2015 г.