SQL-инъекции: Как правильно реализовать метод fetchIdsForSqlIn для безопасной вставки массива идентификаторов в SQL-условие WHERE IN (…)?
SQL-инъекции через конструкцию WHERE IN (…) — одна из наиболее распространённых уязвимостей в веб-приложениях. Когда разработчик напрямую подставляет пользовательский массив идентификаторов в SQL-запрос через конкатенацию строк, злоумышленник может передать вредоносные данные и получить несанкционированный доступ к базе данных.
## Почему прямая конкатенация опасна
Рассмотрим небезопасный пример на PHP:
php
$ids = implode(‘,’, $_GET[‘ids’]);
$sql = «SELECT * FROM users WHERE id IN ($ids)»;
Если пользователь передаст `1,2,3) OR 1=1 —`, запрос вернёт все записи таблицы. Это классическая SQL-инъекция.
## Правильная реализация fetchIdsForSqlIn
Метод `fetchIdsForSqlIn` должен решать две задачи:
1. **Валидация** — убедиться, что все элементы массива являются целыми числами (или ожидаемым типом данных).
2. **Параметризация** — использовать подготовленные выражения (prepared statements) или корректное экранирование.
### Реализация на PHP (PDO)
php
function fetchIdsForSqlIn(PDO $pdo, array $ids): array {
// Валидация: оставляем только целые числа
$safeIds = array_filter($ids, fn($id) => is_int($id) || ctype_digit((string)$id));
$safeIds = array_map(‘intval’, $safeIds);
if (empty($safeIds)) {
return [];
}
// Создаём плейсхолдеры: ?,?,?
$placeholders = implode(‘,’, array_fill(0, count($safeIds), ‘?’));
$stmt = $pdo->prepare(«SELECT * FROM users WHERE id IN ($placeholders)»);
$stmt->execute(array_values($safeIds));
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
### Реализация на Python (psycopg2)
python
def fetch_ids_for_sql_in(cursor, ids: list) -> list:
safe_ids = [int(i) for i in ids if str(i).isdigit()]
if not safe_ids:
return []
placeholders = ‘,’.join([‘%s’] * len(safe_ids))
cursor.execute(f»SELECT * FROM users WHERE id IN ({placeholders})», safe_ids)
return cursor.fetchall()
### Реализация на Java (JDBC)
java
public List<Map> fetchIdsForSqlIn(Connection conn, List ids) throws SQLException {
String placeholders = ids.stream().map(i -> «?»).collect(Collectors.joining(«,»));
String sql = «SELECT * FROM users WHERE id IN (» + placeholders + «)»;
PreparedStatement stmt = conn.prepareStatement(sql);
for (int i = 0; i < ids.size(); i++) {
stmt.setInt(i + 1, ids.get(i));
}
ResultSet rs = stmt.executeQuery();
// … обработка результата
}
## Ключевые принципы безопасности
— **Никогда не используйте конкатенацию строк** для подстановки пользовательских данных.
— **Всегда валидируйте тип данных**: для ID допустимы только целые числа.
— **Используйте prepared statements** — они разделяют код SQL и данные на уровне протокола.
— **Проверяйте пустой массив** — пустое `IN ()` вызовет синтаксическую ошибку в SQL.
— **Ограничивайте размер массива** — слишком большой список может вызвать проблемы с производительностью или стать вектором DoS-атаки.
Следование этим правилам полностью исключает возможность SQL-инъекции через конструкцию WHERE IN.
