Pull to refresh

PHP ACL. Попытка сделать код безопаснее

Reading time4 min
Views6.5K
Приветствую хабрасообщество.

Представьте? что вы разрабатываете какой-то продукт в котором есть система модулей. Модули могут писать сторонние разработчики. Далее вы загружаете модули в систему и запускаете код.
В такой ситуации часто возникает вопрос — как можно ограничить возможности запускаемого кода?

Все мы помним истории со скрытыми майнерами, которые были добавлены в зависимости опенсорс библотек.

Как защитить свой продукт от модуля который банально сделает запрос в базу и закачает архив на какой-нибудь фтп сервер.

Если вы не 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.

Я думаю, у меня получилось донести основную идею. Надеюсь, в комментариях получить ваши отзывы.
Tags:
Hubs:
+6
Comments6

Articles