Когда C++ встречает веб: почему безопасность — ваша главная головная боль
Вы когда-нибудь задумывались, почему хакеры так любят C++ приложения? Нет, не потому что код сложный. А потому что в погоне за производительностью разработчики часто забывают о безопасности C++. И расплачиваются за это данными пользователей, репутацией и деньгами.
Представьте: вы написали крутой сервер на C++, который обрабатывает запросы быстрее молнии. Но один неэкранированный ввод — и злоумышленник сливает всю базу данных. Или, что еще хуже, вставляет скрипт, который крадет сессии всех посетителей сайта. Знакомо? Если нет — отлично, значит, вы читаете эту статью вовремя.
В этом материале мы разберем две самые опасные атаки на веб-приложения — SQL-инъекции и межсайтовый скриптинг (XSS) — и покажем, как от них защититься именно в C++. Без магии, с реальными примерами кода. Поехали.
SQL-инъекции: как хакеры превращают ваш запрос в оружие
SQL-инъекция — это когда злоумышленник вставляет в поле ввода (например, логин или поисковый запрос) кусок SQL-кода. Если ваше приложение слепо подставляет этот ввод в запрос, то вместо поиска пользователя по имени, база выполнит что-то вроде DROP TABLE users. Или, что еще страшнее, выдаст все пароли.
Типичная ошибка новичка (и как её избежать)
Посмотрите на этот код. Он выглядит безобидно, но это бомба замедленного действия:
// ОПАСНО! Никогда так не делайте
std::string username = getUserInput(); // пользователь ввел: ' OR '1'='1
std::string query = "SELECT * FROM users WHERE username = '" + username + "'";
// Результат: SELECT * FROM users WHERE username = '' OR '1'='1'
// Вернет всех пользователей!
Видите проблему? Если пользователь введет ' OR '1'='1, запрос вернет всю таблицу. А если '; DROP TABLE users; --, то goodbye, база данных.
Правильный подход: параметризованные запросы
Единственный надежный способ защиты от SQL-инъекций в C++ — использовать параметризованные запросы. База данных сама отличает код от данных. Вот как это выглядит с библиотекой libpqxx (для PostgreSQL) или MySQL Connector/C++:
#include <iostream>
#include <pqxx/pqxx> // libpqxx для PostgreSQL
void safeQuery(const std::string& username) {
try {
pqxx::connection conn("dbname=mydb user=myuser password=mypass");
pqxx::work txn(conn);
// Параметризованный запрос: $1 — это параметр
std::string query = "SELECT * FROM users WHERE username = $1";
pqxx::result result = txn.exec_params(query, username);
for (auto row : result) {
std::cout << "User: " << row["username"].c_str() << std::endl;
}
txn.commit();
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
int main() {
std::string input;
std::cout << "Enter username: ";
std::getline(std::cin, input);
safeQuery(input);
return 0;
}
Обратите внимание: мы используем txn.exec_params вместо конкатенации строк. База данных сама экранирует спецсимволы. Даже если пользователь введет ' OR '1'='1, это будет воспринято как строка, а не как часть SQL-команды.
Что делать, если вы используете сырой SQLite?
Даже с SQLite можно безопасно работать. Используйте sqlite3_prepare_v2 и sqlite3_bind_text:
#include <sqlite3.h>
#include <iostream>
void safeSQLiteQuery(sqlite3* db, const std::string& username) {
sqlite3_stmt* stmt;
const char* sql = "SELECT * FROM users WHERE username = ?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
while (sqlite3_step(stmt) == SQLITE_ROW) {
std::cout << "User: " << sqlite3_column_text(stmt, 0) << std::endl;
}
sqlite3_finalize(stmt);
}
}
Золотое правило C++ безопасности: Никогда, слышите, никогда не вставляйте пользовательский ввод напрямую в SQL-запрос. Только параметризованные запросы.
XSS-атаки: когда пользовательский контент становится бомбой
XSS (Cross-Site Scripting) работает иначе. Злоумышленник вставляет HTML-теги или JavaScript в данные, которые потом отображаются на странице. Например, в комментарии или профиле пользователя. Если ваше C++ приложение генерирует HTML-страницы и не экранирует вывод, то вместо текста <script>alert('XSS')</script> браузер выполнит этот скрипт.
Пример уязвимости в C++ веб-приложении
Представьте, что вы пишете простой HTTP-сервер на C++ (например, с использованием Boost.Beast). Вы получаете имя пользователя из формы и выводите его на страницу:
// ОПАСНО! Уязвимость к XSS
std::string username = getParameter(request, "username"); // пользователь ввел: <script>alert('XSS')</script>
std::string response = "<html><body><h1>Hello, " + username + "!</h1></body></html>";
// Результат: Hello, <script>alert('XSS')</script> — скрипт выполнится!
Вуаля — XSS-уязвимость. Злоумышленник может украсть куки, перенаправить пользователя на фишинговый сайт или полностью перехватить сессию.
Экранирование HTML: ваш главный щит
Чтобы предотвратить XSS атаки в C++, нужно экранировать потенциально опасные символы HTML. Замените < на <, > на >, & на &, " на ".
Вот простая, но эффективная функция для экранирования HTML в C++:
#