Previous Up Next

Chapter 5  Config4* Security

5.1  The Need for Security

There are three ways that Config4* can execute an external command. First, an external command can be specified when parsing a configuration source.

cfg.parse("exec#command");

Second, within a configuration file, you can @include the output of executing an external command.

@include "exec#command";

Finally, within a configuration file you can use exec() to execute an external command.

name = exec("command");

Each of these three cases presents the same security risk: if Config4* permits arbitrary external commands to be executed, then somebody could arrange for a malicious command to be executed. For example, imagine the damage that could be caused by somebody adding the following to a configuration file.

@if (osType() == "windows") {
    @include "exec#del /F /S /Q C:\";
} @elseIf (osType() == "unix") {
    @include "exec#rm -rf /";
}

We need a way to prevent such malicious commands from being executed, while allowing non-malicious commands, such as curl and hostname, to be executed.

5.2  The Config4* Security Mechanism

The security mechanism in Config4* is driven by three configuration variables: allow_patterns, deny_patterns and trusted_directories. An attempt to execute a command will succeed only if all of the following conditions are true.

When pattern matching is being performed, "*" is treated as a wildcard that can match zero or more characters. For example, the pattern "curl *" matches "curl -sS http://host/file.cfg". The pattern "curl*" (no space before "*") matches the same string but it matches "curlfoobar" too.

If Config4* allows a command to be executed then it rewrites the command slightly to put in the full path to the executable. For example, if curl resides in /usr/local/bin (and let us assume that directory is listed in trusted_directories) then /usr/local/bin/ is prefixed onto the command "curl -sS http://host/file.cfg" when it is being executed. This is to ensure that the executed command is one in a trusted directory rather than one in another directory that appears in PATH.

5.3  The Default Security Policy

Figure 5.1 shows the default security policy of Config4*.


Figure 5.1: Default security configuration
@if (osType() == "unix") {
    allow_patterns = ["curl *", "hostname", "uname",
                      "uname *", "ifconfig"];
    deny_patterns = ["*‘*", "*|*", "*>*"];
    trusted_directories = ["/bin", "/usr/bin", "/sbin"
                           "/usr/local/bin", "/usr/sbin"];
} @elseIf (osType() == "windows") {
    allow_patterns = ["curl *", "hostname", "uname",
                      "uname *", "ipconfig"];
    deny_patterns = ["*‘*", "*|*", "*>*"];
    trusted_directories = [
        getenv("SYSTEMROOT") + "\system32"
    ];
} @else {
    allow_patterns = [];
    deny_patterns = ["*"];
    trusted_directories = [];
};

The @if-then-@else statement in the default security policy makes it possible to tailor the security for different operating systems. You can see from Figure 5.1 that the security policy varies slightly between UNIX and Windows; for other (unknown) operating systems, the security policy denies all attempts to execute commands. Over time, it is likely that Config4* will be ported to other operating systems, and the default security policy will be updated to reflect this.

On UNIX, allow_patterns permits the use of curl, hostname (without any command-line arguments), uname (with and without command-line arguments), and ifconfig (without any command-line arguments). The deny_patterns denies the use of back quotes, pipes and redirection of output. The use of back quotes and pipes is prohibited because they provide ways to combine potentially malicious commands with benign commands, as the examples below show.

@include "exec#curl ‘rm -rf /‘";
@include "exec#curl | rm -rf /";

Redirection of output is prohibited for two reasons. First, Config4* needs to have access to the standard output and standard error of executed commands so there is no valid reason for a user to want to redirect standard output or standard error. Second, if redirection of output was allowed then a malicious person might use this to damage important files in the operating system. For example, a malicious person might trick somebody with root privileges to parse a configuration file containing the following.

@include "exec#curl > /etc/passwd";

The Config4* security policy on Windows is very similar to that on UNIX. Partly this is due to Windows and UNIX having some similarities, and partly due to collections of UNIX-like utilities, such as Cygwin (www.cygwin.com), being available for Windows machines. The main difference between the default security policies on Windows and UNIX is trusted_directories, as can be seen in Figure 5.1.

5.4  Overriding the Default Security Policy

When a Configuration object is created, it uses the default security policy shown in Figure 5.1. You can override this default security policy by calling setSecurityConfiguration(). The first (and possibly only) parameter to this operation is a configuration object that defines allow_patterns, deny_patterns and trusted_directories. By default, Config4* assumes that these variables are defined in the global scope. However, if the variables are defined in a nested scope then you should pass a second parameter that indicates the name of this scope. For example, consider a security policy defined in a scope called security.

security {
    allow_patterns = [ ... ];
    deny_patterns = [ ... ];
    trusted_directories = [ ... ];
}

The code below shows (in Java syntax) how you can set that security policy on a Configuration object called cfg prior to parsing an application configuration file.

Configuration cfg    = Configuration.create();
Configuration secCfg = Configuration.create();
secCfg.parse(...);
cfg.setSecurityConfiguration(secCfg, "security");
cfg.parse(...); // parse application configuration

That code is a bit verbose, but you can shorten it somewhat by using one of the overloaded versions of setSecurityConfiguration(). For example, if the security policy is defined in a file called security.cfg then you can use the following code.

Configuration cfg = Configuration.create();
cfg.setSecurityConfiguration("security.cfg", "security");
cfg.parse(...); // parse application configuration

If the security policy is, say, on a web server then you can use "exec#..." in place of the file name (as long as the default security policy in place permits that command to execute). If the security policy is in an embedded configuration string (perhaps generated with the aid of config2cpp or config2j) then you can use code like that shown below.

Configuration cfg = Configuration.create();
cfg.setSecurityConfiguration(
        Configuration.INPUT_STRING,
        EmbeddedSecurityConfig.getString(),
        "security");
cfg.parse(...); // parse application configuration

5.5  Summary

There are three ways in which Config4* can execute a shell command.

cfg.parse("exec#command");  // C++ or Java code.
@include "exec#command";     // Inside a configuration file.
name = exec("command");     // Inside a configuration file.

Each case presents the same security issue: we need to permit useful commands to be executed, while preventing the execution of harmful commands. Config4* does this by attaching a security policy to each Configuration object. A default security policy is embedded inside the Config4* library, and an application programmer can override this for a Configuration object by invoking the setSecurityConfiguration() operation.


Previous Up Next