Безопасность MODX, часть 1 - обход фильтрации MODX тегов

Это первая часть доклада с конференции MODX Meetup Moscow. Информацию решил разбить на 2 статьи посвященных разным векторам атак. Полная запись доклада доступна в ВК, а на ютубе запись моего экрана

Для тех, кто в танке, скажу еще раз — парсер, который разбирает MODX теги рекурсивен. И благодаря именно этому возможна нежелательная обработка каких-то случайных данных полученных от пользователя. Разработчики движка это прекрасно понимают, потому позаботились о том, чтобы из получаемых данных вырезалось все, что хоть чем-то похоже на MODX теги.

Таким образом, если вы передадите сайту строчку похожую на
Hi [[++dsn]]!
То движок ее отфильтрует и вы увидите всего лишь
Hi !
В 2012 году мной был обнаружен способ обойти эту фильтрацию
Hi [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]++dsn]]!
И запрос с кучей скобок преобразовывался и становился вполне легитимным для MODX
Hi [[++dsn]]!
В конечном счете это приводило к тому, что на выходе мы получали обработку тега и наблюдали параметры подключения к базе данных
Hi mysql:host=127.0.0.1;dbname=modx;charset=utf8mb4!
На тот момент, из глобальных плейсхолдеров можно было узнать не только параметры подключения к базе через плейсхолдер [[++dsn]], но еще и логин-пароль пользователя этой самой базы. Сейчас плейсхолдеры с логином и паролем убрали. Но это не единственные плейсхолдеры которые стоит спрятать.
  • [[++emailsender]] Хранит в себе почтовый адрес отправителя. По умолчанию, после установки движка, этот адрес совпадает с почтовым ящиком администратора.
  • [[++mail_smtp_pass]] — Содержит пароль от почты в случае если вы настроили отправку писем с использованием SMTP. Зачастую с этим паролем можно не только отправлять, но и читать почту.
В общем зная значения этих плейсхолдеров можно деанонимизировать администратора, попробовать получить доступ к почте и сбросить пароль. Что в конечном счете приведет к несанкционированному доступу.

Я согласен, что уязвимость 2012 года для обхода фильтрации тегов на данный момент уже не актуальна. Но тем не менее, вектор атаки остался прежним. Поскольку политика MODX — не воспринимать багрепорты с потенциальными угрозами безопасности, я считаю, что все разработчики должны знать как самостоятельно сделать свои сниппеты безопасными. Поэтому теперь давайте посмотрим на метод modX::sanitize и разберем различные типовые ситуации, которые помогут обойти фильтрацию.

Я создал тестовый сниппет с названием flag на демо-сайте. И вот несколько способов как его можно вызвать

Фильтрация HTML — способ 1


Перед выводом пользовательских данных вы предусмотрительно воспользовались функцией strip_tags() или даже Jevix.

Таким образом, уязвимый сниппет в сильно упрощенном варианте будет выглядеть примерно следующим образом
return strip_tags($_REQUEST['data']);
Соответственно строка
[<b></b>[flag]<b></b>]
преобразуется во
[[flag]]

Многомерные массивы — способ 2


Следующая ситуация немного необычная, но тем не менее, о ней стоит знать, т.к. все зависит от настроек вашего сайта. По умолчанию, метод modX::sanitize поддерживает массивы максимум с 99 уровнями вложенности. Соответственно если кто-то завернет вызов сниппета в массив с более чем 99 уровнями вложенности, то фильтрацию удастся обойти.

На данный момент от этого вектора атаки спасают лишь настройки PHP по умолчанию.
Максимальная вложенность массивов ограничена параметром max_input_nesting_level, который равен 64. Если в настройках вашего сервера установлено значение больше 100, то сайт потенциально уязвим.

Уязвимый сниппет на демо-сайте называется StageTwo. Для простоты, данные просто пропускаются через JSON:
return json_encode($_REQUEST['data'], JSON_FORCE_OBJECT);
Соответственно запрос
data[][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]=[[flag]]
Успешно обойдет фильтрацию, т.к. массив data имеет 100 уровень вложенности.

Вывод данных из сторонних источниковспособ 3


Наконец мое любимое — вывод данных из сторонних источников. Это может быть список тем или комментариев с форума, который установлен у вас где-то на поддомене. Или даже лента сообщений из какой-нибудь социальной сети по определенному хеш-тегу.
Как можно догадаться, эксплуатация очень простая. Заходим на форум, создаем сообщение, в котором будут использоваться MODX теги. Затем открываем сайт и радуемся результату. 6 лет назад даже официальный сайт был подвержен этому вектору атаки


Вывод


Как вы видите, способы обхода фильтрации достаточно типовые.
Поэтому единственное разумное решение — всегда подменять квадратные скобки на сущности, а не полагаться на встроенный обработчик.
[ - [
] - ]

P.S. В 2013 году был еще один способ обхода фильтрации, но о нем мало кто помнит и знает. Для истории и полноты статьи оставлю тут видео, которое тогда было передано в MODX для демонстрации уязвимости.
Евгений Борисов
30 сентября 2018, 21:35
10
586
+30

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

Евгений Борисов
01 октября 2018, 01:02
+1
2 способ будет не актуален после принятия PR #14098
Илья Уткин
01 октября 2018, 08:53
+4
Кстати, в Jevix есть параметр escapeTags — он по умолчанию выключен. Но если его включить, то все квадратные и фигурные скобки будут заменены на соответствующие HTML-коды.
Алексей Шумаев
01 октября 2018, 09:00
0
Благодарю!
Николай Савин
01 октября 2018, 13:06
0
Спасибо Евгений. Очень познавательно!
SEQUEL.ONE
01 октября 2018, 13:08
0
А если используешь Fenom и его синтаксис?
    Евгений Борисов
    01 октября 2018, 13:19
    0
    Для аудита мне еще ни разу не попадался сайт с Fenom. Поэтому ничего не могу сказать
Николай
01 октября 2018, 14:14
0
Проверил у себя, реально дыры…

А есть способ сделать фильтрацию всего, что вообще приходит в параметрах post и get? Т.е. прежде чем данные дойдут до какого-нибудь сниппета, они обработаются раньше? Чтобы исключить недостатки большинства компонентов.
    Николай
    01 октября 2018, 14:25
    0
    Особо не силён, поэтому только догадки… Сниппеты обращаются к коннекторам, и практически все данные проходят через них. Возможно ли расширить какой-нибудь стандартный класс MODX, чтобы данные лучше фильтровать?
      Сергей Шлоков
      01 октября 2018, 20:31
      1
      +1
      MODX фильтрует POST, GET, REQUEST, COOKIE в каждом запросе. Но этого, конечно, недостаточно. Фильтруйте сами. Например, так.

      А ещё фильтруются эти значения
      $targets= array ('PHP_SELF', 'HTTP_USER_AGENT', 'HTTP_REFERER', 'QUERY_STRING');
      foreach ($targets as $target) {
          $_SERVER[$target] = isset ($_SERVER[$target]) ? htmlspecialchars($_SERVER[$target], ENT_QUOTES) : null;
      }
        Николай
        01 октября 2018, 23:32
        0
        Спасибо за подсказку, способ нормальный, но если сам какой-нибудь сниппет пишу. А если компонент сторонний, то не иначе как в его внутренности лезть придётся. А сколько там данных передаётся и принимается. А если ставишь их по 20 штук… и там фильтрация слабенькая, или что-то упустил автор. Я просто довольно легко один сайт (свой) взломал по этой заметке, хотя далеко не спец в программировании, а в безопасности вообще нуб. Отсутствие фильтрации квадратных скобочек реально напрягает… В идеале было бы как-то в одном месте все данные фильтровать по каким-то своим правилам. Типа такой общий шлюз, сначала данные через него проходят, а потом уже в сниппеты и компоненты. Но не знаю возможно ли это.
          Сергей Шлоков
          02 октября 2018, 07:04
          1
          +1
          Отсутствие фильтрации квадратных скобочек реально напрягает…
          Ну вот откуда Вы это взяли? Евгений же всё внятно объяснил.

          В идеале было бы как-то в одном месте все данные фильтровать по каким-то своим правилам. Типа такой общий шлюз, сначала данные через него проходят, а потом уже в сниппеты и компоненты. Но не знаю возможно ли это.
          Без проблем. Плагин на событие OnMODXInit.
            Николай
            02 октября 2018, 13:42
            0
            Ну вот откуда Вы это взяли? Евгений же всё внятно объяснил.
            Его же слова:
            Как вы видите, способы обхода фильтрации достаточно типовые.
            Поэтому единственное разумное решение — всегда подменять квадратные скобки на сущности, а не полагаться на встроенный обработчик.
            Приведу пример. Есть компонент платный — Callbak. Я без проблем вписываю вызовы сниппетов или глобальных плейсхолдеров в поля формы обратного звонка, и на почту админу приходит ключ его сессии, или пароль от БД. Это хорошо, что штатно письмо только админу приходит. И это не единственный пример, который я знаю. И их может быть много больше. Да тот же FormIt в БД хранит не обработанные данные форм. Если они где-то выводятся на сайте, то картина будет похожая.
              Николай
              02 октября 2018, 13:44
              0
              Но, конечно, фильтровать вообще всё без разбора тоже не самый лучший вариант. Неплохо было бы какие-то стандарты написания и тестирования компонентов и сниппетов ввести, особенно платных, и какой-нибудь значок соответствующий — «фильтрует данные» (как в программах — проверено антивирусом), или что-то типа того. И в настройках системных, чтобы опции были как строго фильтровать данные, какие и т.д. Отдельный раздел по безопасности в документации к компоненту. Чтобы было ясно, что этот вопрос волновал разработчика. Как-то так… Есть же для верстальщиков валидатор, вот подобный чек-лист у разработчиков должен быть.
                Алексей Шумаев
                02 октября 2018, 13:53
                +1
                По-моему, это не выход. Фильтрация должна быть глобальной на уровне системы, иначе угроза будет постоянной.
                  Николай
                  02 октября 2018, 14:20
                  0
                  По-моему, это не выход. Фильтрация должна быть глобальной на уровне системы, иначе угроза будет постоянной.
                  Просто, например, может возникнуть необходимость хранить исходные данные от посетителей. Особенно если они нигде не выводятся на сайте. Опять же какие-то гибкие настройки должны быть, что фильтровать, а что нет, и как. Допустим, сниппет хочет вывести какой-то плейсхолдер, вот по умолчанию он должен фильтроваться перед выводом на сайте на уровне ядра. А если я как разработчик хочу отключить это поведение, то должен указать это системе (по аналогии с виндой — запуск от имени администратора). Короче, всё что во фронтенд выводится, всё фильтровать по идее нужно, по умолчанию. И тогда в БД можно хранить что угодно. А если ещё и на уровне приёма данных также сделать, то двойная защита будет. Тогда и новички в MODX, коих большинство, будут в безопасности. Скажем, хотят вывести
                  [Мой сайт], а и им MODX подменяет на &#91;Мой сайт&#93;
                  Начинают негодовать, почему так происходит. И потом узнают про безопаность. Т.е. сначала идёт безопасность, а потом уже остальное.

                  Ну это чисто мои мысли вслух так сказать, не знаю как система устроена и насколько это всё реализуемо)
              Алексей Шумаев
              02 октября 2018, 13:48
              +1
              Народ, правильно ли я понимаю: иного выхода у пользователей, как ждать новой версии modx с фиксом — просто нет. Ну или массово ставим фильтрацию в плагине на всех проектах.
              Учитывая, что не все, у кого сайт на modx читают форум и не все смогут быстренько сделать плагин — ждём новую волну?
                Сергей Шлоков
                02 октября 2018, 18:34
                +3
                Вряд ли будет фикс. Задача фильтрации входящих данных ложится на разработчика сайта, а не системы.
                Я в библиотеку modHelpers добавил функции для кодирования скобок. Выпушу в ближайшее время.
              Сергей Шлоков
              02 октября 2018, 18:33
              +2
              Этой уязвимости подвержен не только этот компонент. К сожалению эта проблема ложится на плечи разработчика сайта. Решение простое — фильтр запросов. Мне также пришлось на свой сайт срочно делать фильтр.

              А про FormIt я писал ещё полгода назад.
                Николай
                02 октября 2018, 19:20
                0
                FormIt пробовал вчера таким же образом тестить, вырезает скобки. То ли что-то изменилось за полгода, то ли я особо сильно не экспериментировал.
                  Сергей Шлоков
                  02 октября 2018, 21:36
                  0
                  * Полтора года назад. Описался.
                  Насколько я знаю проблема так и осталась.И феном также нужно фильтровать.
Василий Наумкин
02 октября 2018, 20:29
+6
Обновил mSearch2 — улучшил фильтрацию поискового запроса.
Степан Прищепенко
02 октября 2018, 22:24
0
а есть еще culture_key… это тот параметр который воообще ни как не проверяется, но редко кем используется.
Алексей Шумаев
04 октября 2018, 02:40
+1
Обновил mvtForms. Помимо прочего — добавлена фильтрация тегов.
Сергей Шлоков
04 октября 2018, 07:19
+2
Ещё большое спасибо за демонстрацию, что остались разработчики использующие Windows.Садоводы и фруктофилы пусть утрутся ))
Алексей Шумаев
04 октября 2018, 15:06
+1
Реально ли тестить компоненты на известные уязвимости при приёме в modstore?
Может и не все, но выборочно и помечать их.
Это будет что-то стоить и для магазина и авторов, но дело нужное всем.
    Евгений Борисов
    04 октября 2018, 16:37
    +2
    За чей счёт банкет?)) да и какой смысл проверять только при приемке. Очередное обновление и новая дырка…
      Алексей Шумаев
      04 октября 2018, 16:45
      0
      Да, при каждом обновлении. За чей счёт, это решать надо )
      Николай
      04 октября 2018, 18:12
      0
      Нашедшему дырку компонент в подарок))
        Алексей Шумаев
        04 октября 2018, 18:55
        0
        Ну, как вариант )
        Если серьёзно, я думаю, движение в сторону аудита неизбежно, если система хочет успешного развития.
          Сергей Шлоков
          05 октября 2018, 09:07
          +1
          Ага. Только в сторону аудита разработчика сайта. Забейте в гугл inurl:core/components/pdotools и убедитесь, насколько велик и ужасен безмозглый разработчик. И их армия.
        Сергей Шлоков
        05 октября 2018, 09:00
        +1
        Точно. А если открылась новая уязвимость, то аудитора пороть и подарок взад забирать!
          Азамат
          05 октября 2018, 09:38
          0
          глагол «забирать» нужно убрать и ваще норм будет :D
Сергей Шлоков
05 октября 2018, 11:09
+1
Для наивных, что какой-то один человек всё проверит и исправит — Простой CSS-код заставляет iPhone перезагружаться. Это компания Форреста Гампа с огромным штатом тестировщиков и аналитиков.
    Алексей Шумаев
    05 октября 2018, 11:36
    0
    Я писал про тестирование на известные уязвимости, которые могут быть использованы в массовом порядке.
    Разумеется, всё проверить и исправить в компоненте невозможно и цели такой быть не может.
    А вот принять меры для снижения вероятности массового взлома через компонент путём его аудита специалистом по безопасности — это другое дело.