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

SQL-инъекция: знак вопроса против конкатенации

Сравню два подхода к одному из самых распространённых векторов атаки.

Уязвимый код:

const userId = req.query.id;
db.prepare('SELECT * FROM users WHERE id = ' + userId).get();

Если передать ' OR '1'='1 вместо ID — запрос превращается в SELECT * FROM users WHERE id = ' OR '1'='1 и возвращает всех пользователей. Пароль? Данные карт? Добро пожаловать.

Безопасный код:

const userId = req.query.id;
db.prepare('SELECT * FROM users WHERE id = ?').get(userId);

Символ ? — это плейсхолдер. Библиотека сама экранирует всё, что приходит от пользователя. Подставить SQL-код вместо ID физически невозможно.

XSS: innerHTML против textContent

Межсайтовый скриптинг — это когда вредоносный JavaScript попадает на страницу через ваш же сайт.

Уязвимый код:

const comment = req.body.comment;
element.innerHTML = comment;

Пользователь пишет комментарий с тегом <script>. Каждый посетитель страницы выполняет этот скрипт — куки уходят злоумышленнику.

Безопасный код:

const comment = req.body.comment;
element.textContent = comment;

textContent выводит текст как текст. Браузер не интерпретирует <script> как команду.

Где ещё протекает

Места, где дыры встречаются чаще всего:

  • Публичные директории на сервере — бекапы, .env с паролями, логи лежат в вебе
  • Файл конфигурации с секретами в репозитории на GitHub
  • Нет https на сайте — куки перехватываются в открытой сети
  • Один пароль на базу, админку и всё остальное

Итого: меньше теории, больше привычки

Серьёзные взломы — это не кино. Это sqlmap и автоматические сканеры, которые находят дыры за минуты. Но и защита не требует высшего образования: достаточно понимать, где именно данные от пользователя попадают в места, где они могут навредить.

Когда я вижу db.prepare('SELECT * FROM users WHERE id = ?').get(userId) — я знаю, что всё в порядке. Когда вижу db.prepare('SELECT * FROM users WHERE id = ' + userId) — у меня включается красная лампочка. Это не магия. Это просто понимание того, как работает строка в запросе.