Введение
В том случае, если вы управляете более чем одним сервером Cache может возникнуть задача выполнения произвольного кода из одного сервера Cache на другом. Кроме того, может потребоваться выполнение произвольного кода на удалённом сервере Cache, например, для нужд сисадмина… Для решения этих задач была разработана утилита RCE.
Какие вообще есть варианты решения подобных задач, и что предлагает RCE (Remote Code Execution) – под катом.
Что уже есть?
» Локальные команды ОС
Начнём с простого – выполнения локальных команд операционной системы из Cache. Для этого используются функции $zf:
- $ZF(-1) – вызывает программу или команду операционной системы. Вызов осуществляется в новом процессе, родительский же процесс ждёт окончания выполнения вызванного процесса. После выполнения $ZF(-1) возвращает статус выполнения процесса: 0 в случае успешного выполнения, 1 в случае ошибки и -1 в случае если не получилось создать процесс.
Выглядит это так: set status = $ZF(-1,"mkdir ""test folder""")
- $ZF(-2) – аналогична, с той разницей, что основной процесс не ждёт результатов выполнения созданного процесса. В результате возвращается 0, если создание процесса прошло успешно и -1 если создать процесс не удалось.
Также можно использовать методы класса %Net.Remote.Utility, которые предоставляют собой удобные обёртки над стандартными функциями, их преимуществом является получение вывода вызванных процессов в более удобной форме:
- RunCommandViaCPIPE – выполняет команду через Command Pipe. Возвращает созданное устройство и строку с выводом процесса. Напрямую выполнение команд на сервере с помощью Command Pipe описано на Хабре в этой статье.
- RunCommandViaZF – выполняет команду через $ZF(-1). Записывает вывод процесса в файл, а также возвращает его в виде строки.
Альтернативным вариантом является использование терминальной команды ! (или $, они идентичны), которая открывает оболочку операционной системы внутри терминала Cache. Есть два режима работы:
- Однострочный – вместе с ! передаётся сама команда. Она сразу выполняется интерпретатором оболочки, а её вывод отправляется на текущее устройство Cache. Предыдущий пример выглядит так:
SAMPLES>! mkdir ""test folder""
- Многострочный – сначала выполняется !, что приводит к открытию оболочки, в которую пользователь уже вводит команды операционной системы. Выход осуществляется с помощью команд quit или exit (в зависимости от оболочки):
SAMPLES>! C:\InterSystems\Cache\mgr\samples\> mkdir "test folder" C:\InterSystems\Cache\mgr\samples\> quit SAMPLES>
» Удалённое выполнение COS кода
Возможно с помощью класса %Net.RemoteConnection, где доступна следующая функциональность:
- Открытие и изменение хранимых объектов;
- Выполнение методов класса и объекта;
- Выполнение запросов.
Пример кода, демонстрирующего эти возможности
Set rc=##class(%Net.RemoteConnection).%New()
Set Status=rc.Connect("127.0.0.1","SAMPLES",1972,"_system","SYS") break:'Status
Set Status=rc.OpenObjectId("Sample.Person",1,.per) break:'Status
Set Status=rc.GetProperty(per,"Name",.value) break:'Status
Write value
Set Status=rc.ResetArguments() break:'Status
Set Status=rc.SetProperty(per,"Name","Jones,Tom "_$r(100),4) break:'Status
Set Status=rc.ResetArguments() break:'Status
Set Status=rc.GetProperty(per,"Name",.value) break:'Status
Write value
Set Status=rc.ResetArguments() break:'Status
Set Status=rc.AddArgument(150,0) break:'Status // Сложение 150+10
Set Status=rc.AddArgument(10,0) break:'Status // Сложение 150+10
Set Status=rc.InvokeInstanceMethod(per, "Addition", .AdditionValue, 1) break:'Status
Write AdditionValue
Set Status=rc.ResetArguments() break:'Status
Set Status=rc.InstantiateQuery(.rs,"Sample.Person","ByName")
Set Status=rc.Connect("127.0.0.1","SAMPLES",1972,"_system","SYS") break:'Status
Set Status=rc.OpenObjectId("Sample.Person",1,.per) break:'Status
Set Status=rc.GetProperty(per,"Name",.value) break:'Status
Write value
Set Status=rc.ResetArguments() break:'Status
Set Status=rc.SetProperty(per,"Name","Jones,Tom "_$r(100),4) break:'Status
Set Status=rc.ResetArguments() break:'Status
Set Status=rc.GetProperty(per,"Name",.value) break:'Status
Write value
Set Status=rc.ResetArguments() break:'Status
Set Status=rc.AddArgument(150,0) break:'Status // Сложение 150+10
Set Status=rc.AddArgument(10,0) break:'Status // Сложение 150+10
Set Status=rc.InvokeInstanceMethod(per, "Addition", .AdditionValue, 1) break:'Status
Write AdditionValue
Set Status=rc.ResetArguments() break:'Status
Set Status=rc.InstantiateQuery(.rs,"Sample.Person","ByName")
В данном коде происходит:
- Подключение к серверу Cache;
- Открытие экземпляра класса Sample.Person с Id 1;
- Получение значения свойства;
- Изменение значения свойства;
- Установка аргументов для метода;
- Вызов метода экземпляра;
- Выполнение запроса Sample.Person:ByName.
Для работы %Net.RemoteConnection на стороне сервера, отправляющего запросы необходима настройка C++ биндинга.
Отдельно следует упомянуть о технологии ECP, о которой писали на Хабре, и которая позволяет вызывать удаленные JOB процессы со стороны сервера приложений на сервере БД.
В результате, комбинируя два предложенных выше подхода, в принципе, возможно достижение поставленной в начале этой статьи цели, однако, мне хотелось достичь простого процесса создания нового выполняемого скрипта пользователем, что представляется затруднительным при использовании существующих подходов.
RCE
Таким образом, перед проектом были поставлены следующие цели:
- Выполнение скриптов на удалённых серверах из Cache;
- Отсутствие необходимости настройки удалённого сервера (далее – клиента);
- Минимальная настройка локального сервера (далее – сервера);
- Прозрачное для пользователя переключение между командами операционной системы и COS;
- Поддержка Windows и Linux в качестве клиента.
Иерархия классов проекта выглядит следующим образом:
Иерархия Машина – ОС – Инстанс служит для хранения информации, необходимой для доступа к удалённым серверам.
Для хранения команд служит класс RCE.Script, который содержит последовательный список – объектов класса RCE.Command, которые могут быть как командами ОС так и COS кодом.
Пример команд:
Set Сommand1 = ##class(RCE.Command).%New("cd 1",0)
Set Сommand2 = ##class(RCE.Command).%New("zn ""%SYS""",1)
Первый аргумент – текст команды, второй – уровень выполнения: 0 – ОС, 1 – Cache.
Пример создания нового скрипта:
Set Script = ##class(RCE.Script).%New()
Do Script.Insert(##class(RCE.Command).%New("touch 123",0))
Do Script.Insert(##class(RCE.Command).%New("set ^test=1",1))
Do Script.Insert(##class(RCE.Command).%New("set ^test(1)=2",1))
Do Script.Insert(##class(RCE.Command).%New("touch 1234",0))
Do Script.%Save()
Здесь на уровне ОС будут выполнены 1-я и 4-я команда, а 2-я и 3-я будут выполнены в Cache, причём процесс переключения уровня выполнения абсолютно прозрачен для пользователя.
» Механизмы выполнения
Сейчас поддерживаются следующие пути исполнения:
Сервер |
Клиент |
---|---|
Linux |
Linux, Windows (требуется установка SSH сервера на клиенте) |
Windows |
Linux, Windows (требуется установка SSH сервера на клиенте или psexec на сервере) |
В случае же, если и сервер и клиент – под управлением ОС Windows, происходит генерация bat-файла, который потом отправляется на клиент и выполняется с помощью утилиты psexec.
» Добавление сервера
Загрузите классы из репозитория в любую область. В случае, если у вас Windows сервер и вы хотите управлять другими Windows серверами, установите значение глобала ^settings(«exec») равное пути к утилите psexec. На этом настройка завершена!
» Добавление клиента
Состоит в сохранении всех необходимых для аутентификации данных.
Пример кода, создающего новую иерархию Машина – ОС – Инстанс
Set Machine = ##class(RCE.Machine).%New()
Set Machine.IP = "IP или Хост"
Set OS = ##class(RCE.OS).%New("OС") // Linux или Windows
Set OS.Username = "Имя пользователя ОС"
Set OS.Password = "Пароль пользователя"
Set Instance = ##class(RCE.Instance).%New()
Set Instance.Name = "Имя инстанса Cache"
Set Instance.User = "Имя пользователя Cache" // Не надо, если установлены минимальные настройки безопасности
Set Instance.Pass = "Пароль пользователя Cache" // Не надо, если установлены минимальные настройки безопасности
Set Instance.Dir = "Путь к cterm инстанса" // Надо, только если cterm не в PATH (для windows клиентов)
Set Instance.OS = OS
Set OS.Machine = Machine
Write $System.Status.GetErrorText(Machine.%Save())
Set Machine.IP = "IP или Хост"
Set OS = ##class(RCE.OS).%New("OС") // Linux или Windows
Set OS.Username = "Имя пользователя ОС"
Set OS.Password = "Пароль пользователя"
Set Instance = ##class(RCE.Instance).%New()
Set Instance.Name = "Имя инстанса Cache"
Set Instance.User = "Имя пользователя Cache" // Не надо, если установлены минимальные настройки безопасности
Set Instance.Pass = "Пароль пользователя Cache" // Не надо, если установлены минимальные настройки безопасности
Set Instance.Dir = "Путь к cterm инстанса" // Надо, только если cterm не в PATH (для windows клиентов)
Set Instance.OS = OS
Set OS.Machine = Machine
Write $System.Status.GetErrorText(Machine.%Save())
» Выполнение скрипта
Продолжая предыдущие примеры, выполнение скрипта происходит очень просто – с помощью метода ExecuteScript класса RCE.Instance, которому передаются объект скрипта и область выполнения (по умолчанию – %SYS):
Set Status = Instance.ExecuteScript(Script,"USER")
Выводы
RCE предоставляет удобный механизм удалённого выполнения кода из InterSystems Cache. Так как скрипты хранимые, вам необходимо написать скрипт только один раз, потом он может выполнятся когда угодно и на любом числе клиентов.
Ссылки
GitHub репозиторий RCE
Архив классов проекта RCE
Комментарии (8)
Sioln
26.10.2015 17:37+7Привычно видеть RCE (Remote Code Execution) как тип уязвимости ПО.
А RPC (Remote Procedure Call) как штатное средство удалённого исполнения команд.Chikey
27.10.2015 13:52Во во. Remote code execution is the ability an attacker has to access someone else's computing device and make changes, no matter where the device is geographically located
AlexeyMaslov
27.10.2015 10:31Интересно, но немного непривычно: обычно клиент даёт команду, а сервер её исполняет.
Вопрос: если через RCE удалённо выполняется $$-функция, как вернуть на локальный сервер её возвращаемое значение?eduard93
27.10.2015 10:42Интересно, но немного непривычно: обычно клиент даёт команду, а сервер её исполняет.
Ну тут у одного сервера много клиентов. Наоборот как-то странно.
Вопрос: если через RCE удалённо выполняется $$-функция, как вернуть на локальный сервер её возвращаемое значение
С psexec никак (внутри проекта RCE). C SSH собираем вывод переданной команды. Соответственно его можно использовать.AlexeyMaslov
27.10.2015 11:14Ну тут у одного сервера много клиентов. Наоборот как-то странно.
Почему же? Один админ с одного клиента рулит несколькими серверами. Вполне себе жизненно.
morisson
А что насчет безопасности такого решения? По сути — это ведь «дырка» на сервере. Как достигается только легитимное ее использование?
eduard93
SSH вполне безопасен, особенно при использовании ключей, что поддерживается.
psexec не так безопасен, но эту проблему можно решить, установив windows SSH сервер и отказавшись от использования psexec.