
Remote Code Execution in InterSystems Caché (RCE)

Introduction
In the event that you manage more than one Caché server, the task of executing arbitrary code from one Caché server to another may arise. In addition, it may be necessary to execute arbitrary code on a remote Caché server, for example, for the needs of a system administrator ... To solve these problems, the RCE utility was developed .
What are the options for solving such problems, and what does RCE (Remote Code Execution) offer?
What is already there?
»Local OS commands
Let's start with a simple one - executing local operating system commands from Caché. The functions $ zf are used for this :
- $ ZF (-1) - calls the program or command of the operating system. The call is made in a new process, while the parent process waits for the completion of the called process. After execution, $ ZF (-1) returns the status of the process: 0 in case of successful execution, 1 in case of error and -1 in case of failure to create a process.
It looks like this: set status = $ ZF (-1, "mkdir" "test folder" "") - $ ZF (-2) - similar, with the difference that the main process does not wait for the results of the created process. As a result, 0 is returned if the creation of the process was successful and -1 if the creation of the process failed.
You can also use the methods of the % Net.Remote.Utility class , which provide convenient wrappers over standard functions, their advantage is the output of the called processes in a more convenient form:
- RunCommandViaCPIPE - Runs a command through a Command Pipe . Returns the created device and a string with the output of the process. Direct execution of commands on the server using Command Pipe is described on Habré in this article .
- RunCommandViaZF - Runs a command through $ ZF (-1). Writes the output of the process to a file, and also returns it as a string.
An alternative is to use a terminal command! (or $, they are identical), which opens the shell of the operating system inside the Caché terminal. There are two modes of operation:
- Single line - with! the team itself is transferred. It is immediately executed by the shell interpreter, and its output is sent to the current Caché device. The previous example looks like this:
SAMPLES>! mkdir ""test folder""
- Multi-line - it is executed first!, Which leads to opening a shell into which the user already enters operating system commands. Exit using the quit or exit commands (depending on the shell):
SAMPLES>! C:\InterSystems\Cache\mgr\samples\> mkdir "test folder" C:\InterSystems\Cache\mgr\samples\> quit SAMPLES>
»Remote execution of COS code
Perhaps using the % Net.RemoteConnection class , where the following functionality is available:
- Opening and changing stored objects;
- Execution of class and object methods;
- Fulfillment of requests.
Sample code demonstrating these features
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 // Addition 150 + 10
Set Status = rc.AddArgument (10.0) break: 'Status // Addition 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 // Addition 150 + 10
Set Status = rc.AddArgument (10.0) break: 'Status // Addition 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")
In this code occurs:
- Connection to the Caché server;
- Opening an instance of the Sample.Person class with Id 1;
- Getting property value;
- Change the value of a property;
- Setting arguments for a method;
- Call an instance method;
- Fulfillment of request Sample.Person: ByName .
For% Net.RemoteConnection to work on the server side sending requests, it is necessary to configure C ++ binding .
We should separately mention the ECP technology , which was written on Habré, and which allows you to call remote JOB processes from the application server on the database server.
As a result, combining the two approaches proposed above, in principle, it is possible to achieve the goal set at the beginning of this article, however, I wanted to achieve a simple process of creating a new executable script by the user, which seems difficult when using existing approaches.
Rce
Thus, the following goals were set for the project:
- Scripting on remote servers from Caché;
- No need to configure a remote server (hereinafter referred to as the client);
- Minimal configuration of the local server (hereinafter referred to as the server);
- User-friendly switching between operating system and COS commands;
- Support for Windows and Linux as a client.
The class hierarchy of the project is as follows:

Hierarchy Machine - OS - Instance is used to store information necessary for access to remote servers.
For storing commands, the RCE.Script class is used, which contains a sequential list of objects of the RCE.Command class, which can be either OS commands or COS code.
Command example:
Set Сommand1 = ## class (RCE.Command).% New ("cd 1", 0)
Set Сommand2 = ## class (RCE.Command).% New ("zn" "% SYS" "", 1)
The first argument is the text of the command, the second is the execution level: 0 - OS, 1 - Cache.
An example of creating a new script:
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 ()
Here, at the OS level, the 1st and 4th commands will be executed, and the 2nd and 3rd will be executed in Caché, and the process of switching the execution level is absolutely transparent for the user.
»Execution mechanisms
The following execution paths are now supported:
Server | Client |
---|---|
Linux | Linux, Windows (requires installing an SSH server on the client) |
Windows | Linux, Windows (requires installing SSH server on the client or psexec on the server) |
In the case, if both the server and the client are running Windows, a bat-file is generated, which is then sent to the client and executed using the psexec utility.
»Adding a server
Download classes from the repository to any scope. If you have a Windows server and want to manage other Windows servers, set the global value ^ settings ("exec") equal to the path to the psexec utility. This completes the setup!
»Adding a client
It consists in saving all the data necessary for authentication.
Example code creating a new hierarchy Machine - OS - Instance
Set Machine = ## class (RCE.Machine).% New ()
Set Machine.IP = "IP or Host"
Set OS = ## class (RCE.OS).% New ("OS") // Linux or Windows
Set OS.Username = "OS Username"
Set OS.Password = "User Password"
Set Instance = ## class (RCE.Instance).% New ()
Set Instance.Name = "Caché
Instance Name" Set Instance.User = "Caché username" // Not necessary if the minimum security settings are
set Instance.Pass = "Caché user password" // Not necessary if the minimum security settings are
set Instance.Dir = "Instance cterm path" // Must,only if cterm is not in PATH (for windows clients)
Set Instance.OS = OS
Set OS.Machine = Machine
Write $ System.Status.GetErrorText (Machine.% Save ())
Set Machine.IP = "IP or Host"
Set OS = ## class (RCE.OS).% New ("OS") // Linux or Windows
Set OS.Username = "OS Username"
Set OS.Password = "User Password"
Set Instance = ## class (RCE.Instance).% New ()
Set Instance.Name = "Caché
Instance Name" Set Instance.User = "Caché username" // Not necessary if the minimum security settings are
set Instance.Pass = "Caché user password" // Not necessary if the minimum security settings are
set Instance.Dir = "Instance cterm path" // Must,only if cterm is not in PATH (for windows clients)
Set Instance.OS = OS
Set OS.Machine = Machine
Write $ System.Status.GetErrorText (Machine.% Save ())
»Script execution
Continuing the previous examples, script execution is very simple - using the ExecuteScript method of the RCE.Instance class, to which the script object and execution area are transferred (by default -% SYS):
Set Status = Instance.ExecuteScript (Script, "USER")
conclusions
RCE provides a convenient mechanism for remote code execution from InterSystems Caché. Since the scripts are stored, you need to write the script only once, then it can be executed at any time and on any number of clients.
References
GitHub RCE Repository RCE
Project Class Archive