Приветствую хабрасообщество.
Представьте? что вы разрабатываете какой-то продукт в котором есть система модулей. Модули могут писать сторонние разработчики. Далее вы загружаете модули в систему и запускаете код.
В такой ситуации часто возникает вопрос — как можно ограничить возможности запускаемого кода?
Все мы помним истории со скрытыми майнерами, которые были добавлены в зависимости опенсорс библотек.
Как защитить свой продукт от модуля который банально сделает запрос в базу и закачает архив на какой-нибудь фтп сервер.
Если вы не 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.
Я думаю, у меня получилось донести основную идею. Надеюсь, в комментариях получить ваши отзывы.