C++ Безопасность: Защита от SQL-инъекций и XSS в 2024

Онлайн-тренажер Python для начинающих

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

Начать курс

Когда 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. Замените < на &lt;, > на &gt;, & на &amp;, " на &quot;.

Вот простая, но эффективная функция для экранирования HTML в C++:

#                

Блоги

Рекомендаций по книгам