Инъекции: когда ввод становится кодом
Инъекция — самый старый и по-прежнему самый частый серьёзный баг. Он случается, когда данные от пользователя начинают трактоваться как инструкции вместо данных. Для большинства приложений важны две разновидности.
SQL injection — это когда пользовательский ввод попадает в запрос к базе. Классический уязвимый паттерн:
// VULNERABLE: ввод пользователя вклеивается прямо в запрос
app.get("/user", (req, res) => {
const name = req.query.name;
db.query(`SELECT * FROM users WHERE name = '${name}'`);
});
// Если кто-то передаст name = '; DROP TABLE users; --
// ваш запрос превратится в команду на удаление таблицы.
Исправление — это параметризованный запрос (его ещё называют prepared statement). Вы отправляете запрос и данные раздельно, чтобы база никогда не путала одно с другим:
// SAFE: значение передаётся как параметр, а не как код
app.get("/user", (req, res) => {
const name = req.query.name;
db.query("SELECT * FROM users WHERE name = ?", [name]);
});
Правило: никогда не собирайте запрос конкатенацией строк. Если в диффе вы видите обратные кавычки или +, склеивающие SQL вокруг переменной, остановитесь и попросите параметризованную версию.
XSS (cross-site scripting) — та же идея, но в браузере. Если вы берёте пользовательский ввод и вставляете его на страницу как сырой HTML, атакующий может внедрить тег <script>, который выполнится в браузерах ваших других пользователей — например, угнав их сессии. Исправление — экранирование: выводить пользовательский контент как текст, а не как HTML. Современные фреймворки вроде React экранируют по умолчанию, и это отлично — ровно до тех пор, пока AI не потянется к лазейке вроде dangerouslySetInnerHTML или innerHTML, чтобы «сделать, чтобы работало». Они обходят защиту. Любой такой вызов в диффе воспринимайте как повод задать вопрос.
Объединяющий принцип за каждым багом инъекции: держите данные и код раздельно. Всякий раз, когда пользовательский ввод переходит в запрос, команду, шаблон или HTML, что-то обязано его экранировать или параметризовать.