Данный топик посвящается вопросам защищенности MODx Revolution в целом, а так же коннекторов и контекстов в отдельности (релиз Revolution 2.1.0 ).
Предыстория: встал вопрос создать серьезный ресурс на движке MODx Revolution. Технических проблем мы не видели, но решили больше внимания уделить вопросам защищенности движка.
Честно сказать, я всегда считал механизмы безопасности в MODx Revolution очень гибкими и надежными, но тут я получил довольно много сюрпризов… Постараемся разобрать их как можно больше и подробней.
Кто любит сразу самое интересное читать, начинайте читать со слов «Теперь подытожим, что же надо чтобы работал коннектор........», так как сначала рассмотрели не проблему, а задачу.
UDP: в версии 2.1.1 пофиксили. Но зная на сколько >2.1.0 сырая еще, уверен что 99% Рево в ходу это более ранние релизы.
Во-первых, берем во внимание самый распространенный миф с open-source продуктами: «Легко скачать движек, покопать его и найти дыры». Не будем доказывать обратное, а просто сделаем одну хитрость: изменим файловую структуру. Переименовываем основные папки MODx (assets/, connectors/, manager/), а core/ вообще переносим за пределы корневой директории сайта, чтобы через адресную строку вообще было не подкопаться.
Правда есть с этим несколько моментов:
Таким образом это вас хоть немного защитит от целенаправленных прощупываний по файловой системе (к примеру из соседних аккаунтов на дырявом виртуальном хостинге).
Во-вторых, решили мы сделать попутный сервис для привилегированных пользователей, но не на базе админки (то есть не так, чтобы они входили в админку, там настройками безопасности все от них скрыто было, кроме нового модуля для них), а на базе дополнительного контекста (то есть на новом субдомене и т.п.). У нас же MODx Revolution — многосайтовая платформа))).
Вот тут всё самое интересное и начинается…
Итак по порядку…
Для начала создаем новый контекст, создаем в нем несколько нужных нам документов, прописываем все необходимые настройки, привязываем к сайту новый поддомен, в index-файле прописываем по данному домену инициализацию нужного нам контекста… Проверяем — работает. Теперь самое важное — запретить доступ к данному контексту всем, кроме тех, кому разрешено…
Прежде, чем продолжить, немного о принципах безопасности:
В MODx Revolution на всё действует правило: «Если к чему-то назначены права, значит действует принцип 'все, что не разрешено, то запрещено'. А если ничего ни для кого не назначено, то всем все можно».
Поясняю: Если есть группа ресурсов, на которую назначены кому-то доступы, то все остальные, кому не разрешено, ходят лесом. А если вы просто создали группу ресурсов для группировки, то и доступ к ним имеют все подряд.
Так же и с контестами: если к контексту прописаны доступы для каких-то Ролей политик безопасности, то все остальные идут лесом. Всё логично.
Итак, нам можно было создать группу ресурсов, на которую назначили бы уровни доступа, и в которую бы объединили наши закрытые документы. Но мы от этого простого способа отказались, так как во-первых, мог бы иметь место человеческий фактор (к примеру кто-то создал новый ресурс и забыл его добавить в группу), а во-вторых, мы хотели в принципе закрыть доступ ко всему контексту: ли авторизуйся, или иди дальше.
Вот мы и решили закрыть доступ к контексту. Лезу я, значит, редактировать наш новый контекст, дабы запретить к нему доступ… И тут я в небольшой ступор вошел… А как это так, закрыть к нему доступ? На контекст установлен доступ для Роли супер-юзера, больше ни для кого. Тем не менее все имеют доступ.
Поразмыслив, я понимаю, что логично, чтобы к нему имели доступ, это же публичный контекст… Но зачем тогда вообще существует механизм доступов к контекстам?
Покапавшись получше, все оказалось просто: оказывается на публичные контексты вообще не проверяются доступы, доступы проверяются только на контекст mgr, потому что обработчики запросов вообще разные. Это тоже отдельная статья, потому отправляю на уже написанное: про доступы к контекстам MODx Rvolution
Хорошо. Создаем новый обработчик для нашего контекста, который перехватывает доступы к контексту. Кстати, тут себя MODx показывает во всей красе. Занимает это буквально 30 минут (с учетом освоения новых технологий и 10 строк кода). Если не авторизован, выводим страницу авторизации. Если авторизован, но не имеет доступа к загрузке панели (Специально для этого добавили пользовательскую настроку политик безопасности), то выводит сообщение об отсутствии доступа. Получилось просто отлично: даже супер-пользователи, если не находятся одновременно в нужной группе пользователей и доступа к контексту с определенной Ролью, не имеют доступа к панели. Супер)))
Все вроде клева, но добрался я до написания необходимых коннекторов. Создаю по всем правилам коннектор, прописываю код, создаю процессор. Выполняю запрос… Пустой ответ. Это связано с выполнением файла connectors/index.php
Есть там такой код:
То есть у вас 2 варианта:
Мы пошли по второму пути. Но все равно все еще не бибикает. Покапались еще немного. В результате пришли к выводу о необходимости наличия вот такой строки: $_SERVER['HTTP_MODAUTH'] = $modx->site_id; Собственно именно переменная $modx->site_id (она же глобальная $site_id) и есть причина написания данной статьи. Но об этом чуть позже…
В общем прописываем мы и ее в коннекторе. Все еще не работает. Оказывается надо еще одну переменную прописать $_REQUEST['ctx'] = 'context_id'; И это источник головной боли, ибо если это жестко не прописать, передать ctx можно хоть в GET, хоть в POST-запросе. Это собственно говоря ключ контекста.
Теперь подытожим, что же надо чтобы работал коннектор и в чем же глобальная проблема:
Для работы необходимо:
Итак, фокус: допустим, мы используем настройки политик безопасности при выполнении коннектора. Передаем в него ctx нашего контекста. Все клева. Кому можно выполнять, тому можно, а кому нельзя, тому нельзя. А тут мы такие берем, и прописываем фанарный ctx=sdfsdfsdfsdfsdfsd. Что в данный момент происходит? Инициализируется контекст sdfsdfsdfsdfsdfsd (интересно знать, у вас такой есть?)))). Вот у меня такого контекста нет. И что думаете, все сломано? Нифига!!! MODx не проверяет наличие контекстов. Более того, он вообще нифига не проверяет! Он тупо применяет настройки контекста sdfsdfsdfsdfsdfsd и ПОЛИТИКИ БЕЗОПАСНОСТИ контекста sdfsdfsdfsdfsdfsd! А так как нет политик безопасности для контекста sdfsdfsdfsdfsdfsd, то (вспоминаем правило, сказанное выше), если ничего не запрещено, значит все можно!!! То есть коннектор выполняется.
Более того. Оказывается, для того, чтобы получить доступ ко всему и вся (в том числе и в админке, то есть в контексте mgr), не надо тырить кучу логинов/паролей и прочей лабуды. Главное — это стырить злосчастный $site_id (который к тому же бля глобальный! то есть может быть перехвачен в любом даже самом дохлом клочке кода, пропученном через $modx или если есть доступ к config.inc.php).
Что же вы заимеете, получив $site_id? Полный доступ на выполнение админских запросов к MODx. Попробуйте сами на своих сайтах. Будучи не авторизованными отправьте простой GET запрос connectors/resource/index.php?&HTTP_MODAUTH={значение $site_id}&ctx=fgdfgdfg&action=getToolbar. В данном случае мы подставляем значение переменной $site_id в качестве переменной HTTP_MODAUTH (но этого еще не достаточно, так как все еще инициализируется контекст mgr), а фонарной переменной ctx мы заставляем MODx инициализировать несуществующий контекст, то есть применить отсутствие настроек безопасности. И конечно же не забываем про переменную action (все эти запросы вы легко можете отладить с помощью того же firebug, поигравшись в админке своего сайта, или скачав релиз с оффсайта MODx). Всё! Доступ получен! Конечно придется покопаться чтобы написать несколько интересных запросов, но в результате мы легко можем получить доступ к файловой системе через встроенный файловый манагер самого MODx-а…
Так что ребята, защищайте свои сайты, у кого есть что важное :-)
Кстати, как это сделать?
Очень просто. В файле connectors/index.php перепишите код
на
то есть инициализируйте жестко контекст mgr. Это как пример. Дальше уже сами посмотрите по задачам.
Предыстория: встал вопрос создать серьезный ресурс на движке MODx Revolution. Технических проблем мы не видели, но решили больше внимания уделить вопросам защищенности движка.
Честно сказать, я всегда считал механизмы безопасности в MODx Revolution очень гибкими и надежными, но тут я получил довольно много сюрпризов… Постараемся разобрать их как можно больше и подробней.
Кто любит сразу самое интересное читать, начинайте читать со слов «Теперь подытожим, что же надо чтобы работал коннектор........», так как сначала рассмотрели не проблему, а задачу.
UDP: в версии 2.1.1 пофиксили. Но зная на сколько >2.1.0 сырая еще, уверен что 99% Рево в ходу это более ранние релизы.
Во-первых, берем во внимание самый распространенный миф с open-source продуктами: «Легко скачать движек, покопать его и найти дыры». Не будем доказывать обратное, а просто сделаем одну хитрость: изменим файловую структуру. Переименовываем основные папки MODx (assets/, connectors/, manager/), а core/ вообще переносим за пределы корневой директории сайта, чтобы через адресную строку вообще было не подкопаться.
Правда есть с этим несколько моментов:
- Есть тонкости переноса папки manager/. Не хочу повторяться, читайте тут
- Готовьтесь к тому, что слетят какие-нибудь плагины. Особенно это касается плагина directresize. В нем во-первых, жестко прописано assets/, во-вторых пути к картинкам в файловой системе не абсолютные формируются, а относительные, что тоже в свою очередь приводит к ошибкам.
- Никогда не прописывайте в своем коде жесткие пути, обязательно используйте системные переменные base_path, core_path и т.п.
Таким образом это вас хоть немного защитит от целенаправленных прощупываний по файловой системе (к примеру из соседних аккаунтов на дырявом виртуальном хостинге).
Во-вторых, решили мы сделать попутный сервис для привилегированных пользователей, но не на базе админки (то есть не так, чтобы они входили в админку, там настройками безопасности все от них скрыто было, кроме нового модуля для них), а на базе дополнительного контекста (то есть на новом субдомене и т.п.). У нас же MODx Revolution — многосайтовая платформа))).
Вот тут всё самое интересное и начинается…
Итак по порядку…
Для начала создаем новый контекст, создаем в нем несколько нужных нам документов, прописываем все необходимые настройки, привязываем к сайту новый поддомен, в index-файле прописываем по данному домену инициализацию нужного нам контекста… Проверяем — работает. Теперь самое важное — запретить доступ к данному контексту всем, кроме тех, кому разрешено…
Прежде, чем продолжить, немного о принципах безопасности:
В MODx Revolution на всё действует правило: «Если к чему-то назначены права, значит действует принцип 'все, что не разрешено, то запрещено'. А если ничего ни для кого не назначено, то всем все можно».
Поясняю: Если есть группа ресурсов, на которую назначены кому-то доступы, то все остальные, кому не разрешено, ходят лесом. А если вы просто создали группу ресурсов для группировки, то и доступ к ним имеют все подряд.
Так же и с контестами: если к контексту прописаны доступы для каких-то Ролей политик безопасности, то все остальные идут лесом. Всё логично.
Итак, нам можно было создать группу ресурсов, на которую назначили бы уровни доступа, и в которую бы объединили наши закрытые документы. Но мы от этого простого способа отказались, так как во-первых, мог бы иметь место человеческий фактор (к примеру кто-то создал новый ресурс и забыл его добавить в группу), а во-вторых, мы хотели в принципе закрыть доступ ко всему контексту: ли авторизуйся, или иди дальше.
Вот мы и решили закрыть доступ к контексту. Лезу я, значит, редактировать наш новый контекст, дабы запретить к нему доступ… И тут я в небольшой ступор вошел… А как это так, закрыть к нему доступ? На контекст установлен доступ для Роли супер-юзера, больше ни для кого. Тем не менее все имеют доступ.
Поразмыслив, я понимаю, что логично, чтобы к нему имели доступ, это же публичный контекст… Но зачем тогда вообще существует механизм доступов к контекстам?
Покапавшись получше, все оказалось просто: оказывается на публичные контексты вообще не проверяются доступы, доступы проверяются только на контекст mgr, потому что обработчики запросов вообще разные. Это тоже отдельная статья, потому отправляю на уже написанное: про доступы к контекстам MODx Rvolution
Хорошо. Создаем новый обработчик для нашего контекста, который перехватывает доступы к контексту. Кстати, тут себя MODx показывает во всей красе. Занимает это буквально 30 минут (с учетом освоения новых технологий и 10 строк кода). Если не авторизован, выводим страницу авторизации. Если авторизован, но не имеет доступа к загрузке панели (Специально для этого добавили пользовательскую настроку политик безопасности), то выводит сообщение об отсутствии доступа. Получилось просто отлично: даже супер-пользователи, если не находятся одновременно в нужной группе пользователей и доступа к контексту с определенной Ролью, не имеют доступа к панели. Супер)))
Все вроде клева, но добрался я до написания необходимых коннекторов. Создаю по всем правилам коннектор, прописываю код, создаю процессор. Выполняю запрос… Пустой ответ. Это связано с выполнением файла connectors/index.php
Есть там такой код:
if (defined('MODX_REQP') && MODX_REQP === false) {
} else if (!$modx->context->checkPolicy('load')) {
@session_write_close();
die();
}
То есть у вас 2 варианта:
- Прописать в коннекторе define('MODX_REQP', false); В таком случае отключается вообще проверка на право выполнения скрипта. Тупо всем можно.
- Назначить права на данный контекст на право load. Это более предпочтительно, если вы хотите ограничивать доступ к коннектору. Проблема в данном случае только одна: если в первом случае в случае ошибки выполнения кода будет отдан ответ в формате JSON, то во встором случае ответа не будет вообще.
Мы пошли по второму пути. Но все равно все еще не бибикает. Покапались еще немного. В результате пришли к выводу о необходимости наличия вот такой строки: $_SERVER['HTTP_MODAUTH'] = $modx->site_id; Собственно именно переменная $modx->site_id (она же глобальная $site_id) и есть причина написания данной статьи. Но об этом чуть позже…
В общем прописываем мы и ее в коннекторе. Все еще не работает. Оказывается надо еще одну переменную прописать $_REQUEST['ctx'] = 'context_id'; И это источник головной боли, ибо если это жестко не прописать, передать ctx можно хоть в GET, хоть в POST-запросе. Это собственно говоря ключ контекста.
Теперь подытожим, что же надо чтобы работал коннектор и в чем же глобальная проблема:
Для работы необходимо:
- $_REQUEST['ctx'] — ключ контекста, переданный любым способом;
- $site_id — ключ сайта, прописанный в конфиге MODx-а, он же $_SERVER['HTTP_MODAUTH']
Итак, фокус: допустим, мы используем настройки политик безопасности при выполнении коннектора. Передаем в него ctx нашего контекста. Все клева. Кому можно выполнять, тому можно, а кому нельзя, тому нельзя. А тут мы такие берем, и прописываем фанарный ctx=sdfsdfsdfsdfsdfsd. Что в данный момент происходит? Инициализируется контекст sdfsdfsdfsdfsdfsd (интересно знать, у вас такой есть?)))). Вот у меня такого контекста нет. И что думаете, все сломано? Нифига!!! MODx не проверяет наличие контекстов. Более того, он вообще нифига не проверяет! Он тупо применяет настройки контекста sdfsdfsdfsdfsdfsd и ПОЛИТИКИ БЕЗОПАСНОСТИ контекста sdfsdfsdfsdfsdfsd! А так как нет политик безопасности для контекста sdfsdfsdfsdfsdfsd, то (вспоминаем правило, сказанное выше), если ничего не запрещено, значит все можно!!! То есть коннектор выполняется.
Более того. Оказывается, для того, чтобы получить доступ ко всему и вся (в том числе и в админке, то есть в контексте mgr), не надо тырить кучу логинов/паролей и прочей лабуды. Главное — это стырить злосчастный $site_id (который к тому же бля глобальный! то есть может быть перехвачен в любом даже самом дохлом клочке кода, пропученном через $modx или если есть доступ к config.inc.php).
Что же вы заимеете, получив $site_id? Полный доступ на выполнение админских запросов к MODx. Попробуйте сами на своих сайтах. Будучи не авторизованными отправьте простой GET запрос connectors/resource/index.php?&HTTP_MODAUTH={значение $site_id}&ctx=fgdfgdfg&action=getToolbar. В данном случае мы подставляем значение переменной $site_id в качестве переменной HTTP_MODAUTH (но этого еще не достаточно, так как все еще инициализируется контекст mgr), а фонарной переменной ctx мы заставляем MODx инициализировать несуществующий контекст, то есть применить отсутствие настроек безопасности. И конечно же не забываем про переменную action (все эти запросы вы легко можете отладить с помощью того же firebug, поигравшись в админке своего сайта, или скачав релиз с оффсайта MODx). Всё! Доступ получен! Конечно придется покопаться чтобы написать несколько интересных запросов, но в результате мы легко можем получить доступ к файловой системе через встроенный файловый манагер самого MODx-а…
Так что ребята, защищайте свои сайты, у кого есть что важное :-)
Кстати, как это сделать?
Очень просто. В файле connectors/index.php перепишите код
$ctx = isset($_REQUEST['ctx']) && !empty($_REQUEST['ctx']) ? $_REQUEST['ctx'] : 'mgr';
$modx->initialize($ctx);
на
/* initialize the proper context */
$ctx = 'mgr';
$modx->initialize($ctx);
то есть инициализируйте жестко контекст mgr. Это как пример. Дальше уже сами посмотрите по задачам.