Приветствую хабрасообщество.
Представьте? что вы разрабатываете какой-то продукт в котором есть система модулей. Модули могут писать сторонние разработчики. Далее вы загружаете модули в систему и запускаете код.
В такой ситуации часто возникает вопрос — как можно ограничить возможности запускаемого кода?
Все мы помним истории со скрытыми майнерами, которые были добавлены в зависимости опенсорс библотек.
Как защитить свой продукт от модуля который банально сделает запрос в базу и закачает архив на какой-нибудь фтп сервер.
Если вы не Apple, Google и т.д. и у вас нет штата своих модераторов которые будут модерировать загружаемые модули возможно решение под катом облегчит Вам жизнь.
Сразу оговорюсь, что на данный момент этот пост лишь попытка прощупать почву и собрать мнения сообщества, дабы понять стоить ли копать дальше. Проект представляет из себя пока лишь концепт.
В айти существует глобальная проблема безопасности, связанная с тем что ЯП не предоставляют возможностей на уровне языка контролировать уровни привилегий как это делают различные операционные системы. В какой-то мере мы можем использовать эти возможности, например менять права файлов, разрешать запрещать открытие портов, настраивать фаервол. Но это не всегда удобно. Нет возможности разделить код вашего продукта на системный и пользовательский (по аналогии с kernel и userspace).
Идея сделать некую ACL для бедных, пока уважаемые разработчики языков не введут такие возможности в сам язык.
За основу взято прекрасное расширение uopz. Спасибо ребятам за огромный труд. На данный момент поддерживается PHP 7. (7.1 и вроде 7.2)
Оно позволяет нам переопределить встроенные PHP функции и что очень важно методы классов.
Пользуясь этим, мы можем переопределить все опасные функции (доступ к ФС, сокеты, вызовы exec, proc_open и т.д.), заменить их своими, чтобы на базе этого сделать набор правил, по которым мы разрешаем / отклоняем данное действие.
Сразу скажу что такие функции как require, include и т.д. нет возможности переопределить т.к. это не функции вовсе. Но про это чуть ниже.
Это работает следубщим образом. Вызовы опасных функции перенаправляются в функции обертку, которая создает объект содержащий инфорацию о вызове, далее этот объект передается массиву ACL правил. Если хоть одно правило вернет true, действие разрешается (передается нативной функции). В противном случае возникает ошибка.
Код инициализации довольно прост. В точке входа пишем.
Файл конфига для проекта на симфони может иметь такой вид:
В такой конфигурации попытка записать/прочитать/открыть файл из стороннего кода, даже вашего, приведет к ошибке.
На данный момент поддерживается не так много функций. Если в коментариях не выявится явного недостатка такого подхода, набросать остальные функции не составит труда.
По поводу require и иже с ними. К сожалению победить таким образом конструкции языка невозможно. Злой хакер до сих пор может подключить ваш файл конфигов и вывести его прямо в браузер.
Но так как это конструкция языка, мы не можем вызывать её не напрямую, через сокрытие названия в переменных и т.д.
Мы не можем написать что-то вроде:
Значит мы можем пробежаться по исходникам и просто найти данные опасные вызовы и принять меры.
Возможно, такой функционал стоит добавить в этот проект.
Вопрос с БД можно решить похожим способом. Можно плагинам предоставить возможность работать только от имени определенного пользователя БД, запретив через ACL получить ссылку на полноправный конект к базе. К примеру в доктрине мы можем создать несколько EntityManager и через ACL запретить стороннему коду методы, которые позволят ему получить дефолтный EntityManager с раширенными правами.
Исходный код можно посмотреть на github.
Я думаю, у меня получилось донести основную идею. Надеюсь, в комментариях получить ваши отзывы.
Представьте? что вы разрабатываете какой-то продукт в котором есть система модулей. Модули могут писать сторонние разработчики. Далее вы загружаете модули в систему и запускаете код.
В такой ситуации часто возникает вопрос — как можно ограничить возможности запускаемого кода?
Все мы помним истории со скрытыми майнерами, которые были добавлены в зависимости опенсорс библотек.
Как защитить свой продукт от модуля который банально сделает запрос в базу и закачает архив на какой-нибудь фтп сервер.
Если вы не Apple, Google и т.д. и у вас нет штата своих модераторов которые будут модерировать загружаемые модули возможно решение под катом облегчит Вам жизнь.
Сразу оговорюсь, что на данный момент этот пост лишь попытка прощупать почву и собрать мнения сообщества, дабы понять стоить ли копать дальше. Проект представляет из себя пока лишь концепт.
В айти существует глобальная проблема безопасности, связанная с тем что ЯП не предоставляют возможностей на уровне языка контролировать уровни привилегий как это делают различные операционные системы. В какой-то мере мы можем использовать эти возможности, например менять права файлов, разрешать запрещать открытие портов, настраивать фаервол. Но это не всегда удобно. Нет возможности разделить код вашего продукта на системный и пользовательский (по аналогии с kernel и userspace).
Идея сделать некую ACL для бедных, пока уважаемые разработчики языков не введут такие возможности в сам язык.
За основу взято прекрасное расширение uopz. Спасибо ребятам за огромный труд. На данный момент поддерживается PHP 7. (7.1 и вроде 7.2)
Оно позволяет нам переопределить встроенные PHP функции и что очень важно методы классов.
Пользуясь этим, мы можем переопределить все опасные функции (доступ к ФС, сокеты, вызовы exec, proc_open и т.д.), заменить их своими, чтобы на базе этого сделать набор правил, по которым мы разрешаем / отклоняем данное действие.
Сразу скажу что такие функции как require, include и т.д. нет возможности переопределить т.к. это не функции вовсе. Но про это чуть ниже.
Это работает следубщим образом. Вызовы опасных функции перенаправляются в функции обертку, которая создает объект содержащий инфорацию о вызове, далее этот объект передается массиву ACL правил. Если хоть одно правило вернет true, действие разрешается (передается нативной функции). В противном случае возникает ошибка.
Код инициализации довольно прост. В точке входа пишем.
$acl = \PhpAcl\ACLComponent::getInstance(); $acl->init(require_once __DIR__ . '/../app/config/acl.php');
Файл конфига для проекта на симфони может иметь такой вид:
<?php use PhpAcl\IOOperation; define('ROOT_DIR', realpath(__DIR__ . '/../../../code')); return [ 'io' => [ 'enabled' => true, 'rules' => [ // allow all from symfony function(IOOperation $operation){ return $operation->callerStartsWith(ROOT_DIR . '/vendor/symfony/symfony/src'); }, // allow doctrine to read annotations function(IOOperation $operation){ return $operation->isReadOrOpenOperation() && $operation->callerStartsWith(ROOT_DIR . '/code/vendor/doctrine/annotations'); }, // allow writing to cache function(IOOperation $operation){ return $operation->isWriteOrOpenOperation() && preg_match(sprintf('{^%s/var/cache/dev|prod/}', ROOT_DIR), $operation->getSrc()); }, // allow reading from cache function(IOOperation $operation){ return $operation->isReadOrOpenOperation() && preg_match(sprintf('{^%s/var/cache/dev|prod/}', ROOT_DIR), $operation->getSrc()); }, // allow monolog read/write log files function(IOOperation $operation){ return ( $operation->type === IOOperation::TYPE_WRITE || $operation->type === IOOperation::TYPE_READ || $operation->type === IOOperation::TYPE_OPEN ) && $operation->callerStartsWith(ROOT_DIR. '/vendor/monolog/monolog/src') && preg_match(sprintf('{^%s/var/logs/dev|prod.log}', ROOT_DIR), $operation->getSrc()); }, // allow twig to read/write template files function(IOOperation $operation){ return ( $operation->type === IOOperation::TYPE_WRITE || $operation->type === IOOperation::TYPE_READ || $operation->type === IOOperation::TYPE_OPEN ) && $operation->callerStartsWith(ROOT_DIR . '/vendor/twig/twig') && preg_match('/\.twig$/', $operation->getSrc()); } ] ] ];
В такой конфигурации попытка записать/прочитать/открыть файл из стороннего кода, даже вашего, приведет к ошибке.
На данный момент поддерживается не так много функций. Если в коментариях не выявится явного недостатка такого подхода, набросать остальные функции не составит труда.
По поводу require и иже с ними. К сожалению победить таким образом конструкции языка невозможно. Злой хакер до сих пор может подключить ваш файл конфигов и вывести его прямо в браузер.
Но так как это конструкция языка, мы не можем вызывать её не напрямую, через сокрытие названия в переменных и т.д.
Мы не можем написать что-то вроде:
$_req = 'require'; $_req('/app/config/config.yml');
Значит мы можем пробежаться по исходникам и просто найти данные опасные вызовы и принять меры.
Возможно, такой функционал стоит добавить в этот проект.
Вопрос с БД можно решить похожим способом. Можно плагинам предоставить возможность работать только от имени определенного пользователя БД, запретив через ACL получить ссылку на полноправный конект к базе. К примеру в доктрине мы можем создать несколько EntityManager и через ACL запретить стороннему коду методы, которые позволят ему получить дефолтный EntityManager с раширенными правами.
Исходный код можно посмотреть на github.
Я думаю, у меня получилось донести основную идею. Надеюсь, в комментариях получить ваши отзывы.