Remote Code Execution in InterSystems Caché (RCE)

    Van Gogh Fishing Boats on the Beach

    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")

    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:

    RCE Project Class Hierarchy

    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 event that there is ssh support on the client, the server generates an ssh command and executes it on the client using the standard % Net.SSH.Session class .

    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 ())

    »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

    Also popular now: