[Внимание] 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-запросах.

Пример вредоносного запроса:

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.
В msgetorder закоментил

// 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 от подобных инъекций?
Сергей
22 июня 2025, 12:09
modx.pro
1
1 058
0

Комментарии: 5

Артур Шевченко
22 июня 2025, 15:34
0
Стандартный вызов msOrder происходит на сервере и не использует GET или POST. msGetOrder использует только id заказа из GET параметров, но и это значение приводится к числу, что исключает возможностью инъекции. Сам pdoFetch и методы xPDO фильтруют запросы, во всяком случае так мне показалось, когда я смотрел код. На мой взгляд проблему надо искать в кастомных запросах если они есть. Ну и обновить минишоп до свежей версии наверное будет нелишним.
    Сергей
    22 июня 2025, 15:50
    0
    В том то и дело, что кастомных запросов нет, не нашел. Не всегда есть возможность обновлять до последней версии. В более свежей версии вроде как нет таких явных дыр, но gtp все равно видим уязвимость. Я вообще не понимаю какой смысл от взлома сайта, на котором даже трафика то нет почти.
    Сергей
    23 июня 2025, 09:08
    0
    Не знаю как у вас, но у меня продолжаются атаки. Плагин спасает. Похоже придется ставить на все сайты, где есть minishop2
      Ivan K.
      23 июня 2025, 15:13
      0
      Попробуйте мой компонент. IskWaf — Web Application Firewall (MODX3) iskwaf2x (MODX2)
      Может поможет.
      А то мало скачиваний даже немного обидно))
        Сергей
        24 июня 2025, 18:51
        0
        Спасибо, я его смотрел но не уверен, что он поможет в данной ситуации. Я добавил кастомный плагин и он решил эту проблему, удивительно что у меня у одного такая проблема) Возможно не все отслеживают сервер)
      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
      5