[Внимание] SQL-инъекция через miniShop2 (msPayment) в MODX — разбор и поиск причины
Хочу поделиться реальным кейсом: вчера на два сайта обнаружилась массовая SQL-инъекция через компонент miniShop2, которая загружала MySQL и выводила из строя сайт (я бы сказал нагружает сервер под 100%).
Версия minishop2 (2.8.3)
Что происходило
Началась высокая нагрузка на MySQL: десятки зависших SELECT COUNT(DISTINCT msPayment.id) с вложенными INFORMATION_SCHEMA, ORD(MID(...)), CAST(...), XOR() — т.е. типичный blind SQL injection.
Все вредоносные запросы шли от пользователя, к базе.
Сайт работал через miniShop2, и инъекции шли через msPayment и msDeliveryMember в JOIN-запросах.
Пример вредоносного запроса:
Вот полный кусок кода из запросов и таких было десятки
После анализа:
На 100% я не уверен, что причина в minishio2 поэтому это может быть какой то другой плагин или сниппет
Как решение сделал плагин BlockSQLInjection на событие OnHandleRequest
Может ли быть, что кастомные вызовы pdoFetch в других сниппетах дают такую дыру?
Есть ли обновления в miniShop2, закрывающие передачу leftJoin, where, select извне?
Как надёжно защитить pdoFetch от подобных инъекций?
Версия minishop2 (2.8.3)
Что происходило
Началась высокая нагрузка на MySQL: десятки зависших SELECT COUNT(DISTINCT msPayment.id) с вложенными INFORMATION_SCHEMA, ORD(MID(...)), CAST(...), XOR() — т.е. типичный blind SQL injection.
Все вредоносные запросы шли от пользователя, к базе.
Сайт работал через miniShop2, и инъекции шли через msPayment и msDeliveryMember в JOIN-запросах.
Пример вредоносного запроса:
SELECT COUNT(DISTINCT msPayment.id)
FROM modx_ms2_payments AS msPayment
JOIN modx_ms2_delivery_payments Member ON Member.payment_id = msPayment.id AND 7574=IF((SESSION_USER() LIKE USER()), (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C WHERE 0 XOR 1), 7574)
WHERE msPayment.id = 1 AND msPayment.active = 1
Вот полный кусок кода из запросов и таких было десятки
Id: 22887
User: XXXX
Host: localhost
db: XXXX
Command: Query
Time: 3884
State: Sending data
Info: SELECT COUNT(DISTINCT msPayment.id) FROM modx_ms2_payments AS msPayment JOIN modx_ms2_delivery_payments Member ON Member.payment_id = msPayment.id AND Member.delivery_id = 1 AND 5273=IF((ORD(MID((HEX(IFNULL(CAST(DATABASE() AS NCHAR),0x20))),6,1))>67),(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C WHERE 0 XOR 1),5273) WHERE ( msPayment.id = 1 AND msPayment.active = 1 )
Progress: 0.000
Что я нашёлПосле анализа:
- Инъекция шла через сниппет msOrder (предположительно), который подставлял leftJoin, where, select — потенциально из $scriptProperties.
- Сниппет msOrder или msGetOrder использует pdoFetch, и если параметры &where, &leftJoin приходят снаружи (например, через чанк, GET/POST или вызов в шаблоне), они не фильтруются и могут быть внедрены в SQL.
// Add user parameters
foreach (array('where', 'leftJoin', 'select') as $v) {
if (!empty($scriptProperties[$v])) {
$tmp = $scriptProperties[$v];
if (!is_array($tmp)) {
$tmp = json_decode($tmp, true);
}
if (is_array($tmp)) {
$$v = array_merge($$v, $tmp);
}
}
unset($scriptProperties[$v]);
}
Изначально были мысли, что это может быть и ms2DeliveryCost. (удалил его на двух сайтах, на одном атака прекратилась).На 100% я не уверен, что причина в minishio2 поэтому это может быть какой то другой плагин или сниппет
Как решение сделал плагин BlockSQLInjection на событие OnHandleRequest
<?php
// === Настройки ===
$enabled = true; // Включить блокировку (403)
$logFile = MODX_CORE_PATH . 'cache/logs/pdo_abuse.log';
// === Шаблоны вредоносных выражений ===
$patterns = [
'UNION\s+SELECT',
'SELECT.+FROM',
'INFORMATION_SCHEMA',
'SLEEP\(\d+\)',
'BENCHMARK\(',
'IF\s*\(',
'ORD\(',
'HEX\(',
'CAST\(',
'DATABASE\(',
'USER\(',
'LIKE\s+USER\(',
'0\s+XOR\s+1',
'NULL\s+OR\s+NULL',
];
// === Собираем все входные данные ===
$allInputs = array_merge($_GET, $_POST, $_REQUEST, $_COOKIE);
$inputJson = json_encode($allInputs, JSON_UNESCAPED_UNICODE);
// === Проверка на вредоносные шаблоны ===
foreach ($patterns as $pattern) {
if (preg_match('/' . $pattern . '/i', $inputJson)) {
// === Получаем данные запроса ===
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'unknown';
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'CLI';
$userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no-user-agent';
// === Формируем лог-запись ===
$log = sprintf(
"[%s] Possible SQLi from %s | URI: %s\nUser-Agent: %s\nData: %s\n---\n",
date('Y-m-d H:i:s'),
$ip,
$uri,
$userAgent,
$inputJson
);
// === Пишем в лог ===
file_put_contents($logFile, $log, FILE_APPEND);
// === Если включена блокировка, прерываем выполнение ===
if ($enabled) {
@http_response_code(403);
exit('Access denied.');
}
break; // достаточно одного совпадения
}
}
Кто сталкивался с похожей ситуацией?Может ли быть, что кастомные вызовы pdoFetch в других сниппетах дают такую дыру?
Есть ли обновления в miniShop2, закрывающие передачу leftJoin, where, select извне?
Как надёжно защитить pdoFetch от подобных инъекций?
Комментарии: 5
Стандартный вызов msOrder происходит на сервере и не использует GET или POST. msGetOrder использует только id заказа из GET параметров, но и это значение приводится к числу, что исключает возможностью инъекции. Сам pdoFetch и методы xPDO фильтруют запросы, во всяком случае так мне показалось, когда я смотрел код. На мой взгляд проблему надо искать в кастомных запросах если они есть. Ну и обновить минишоп до свежей версии наверное будет нелишним.
В том то и дело, что кастомных запросов нет, не нашел. Не всегда есть возможность обновлять до последней версии. В более свежей версии вроде как нет таких явных дыр, но gtp все равно видим уязвимость. Я вообще не понимаю какой смысл от взлома сайта, на котором даже трафика то нет почти.
Не знаю как у вас, но у меня продолжаются атаки. Плагин спасает. Похоже придется ставить на все сайты, где есть minishop2
Попробуйте мой компонент. IskWaf — Web Application Firewall (MODX3) iskwaf2x (MODX2)
Может поможет.
А то мало скачиваний даже немного обидно))
Может поможет.
А то мало скачиваний даже немного обидно))
Спасибо, я его смотрел но не уверен, что он поможет в данной ситуации. Я добавил кастомный плагин и он решил эту проблему, удивительно что у меня у одного такая проблема) Возможно не все отслеживают сервер)
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.