Skip to main content

INJ02-G: Prevent OS Injection


Introduction

External programs are commonly invoked to perform a function that a system requires, embodying a practice akin to reusability and an elementary form of component-based software engineering. Vulnerabilities stemming from operating system (OS) injection occur when an application fails to adequately filter untrusted input, subsequently integrating it during the execution of external programs.

This rule provides instructions on sanitizing untrusted user input for operating system calls.

Execution of external programs in the host operating system is not supported on Guidewire Cloud. The purpose of this document is informational only and applies only to self-managed environments.

Every Java application has a single instance of class Runtime that allows the application to interface with the application's environment. Accessing the current runtime is achievable through the Runtime.getRuntime() method. Although similar to the C Standard Library's system function, the Runtime.exec method diverges in its approach: it disassembles the input string into an array of words using a StringTokenizer, executing the initial word in the array and treating the remaining words as parameters. On Windows platforms, these tokens are consolidated back into a singular argument string prior to execution.

The Runtime.getRuntime() method typically interacts with the underlying operating system's shell, such as CreateProcess() or cmd.exe on Windows, and /bin/sh -c on POSIX. This allows it to execute native processes like fork() or exec(). Within a shell, the pipe (|), ampersand(&), double ampersand (&&), and double pipe(||) characters may be used to chain commands. The && operator executes the second command only if the first command succeeds. The || operator executes the second command only if the first command fails. On Unix-based systems, the backtick (`), or dollar sigh ($) can be used to execute an injected command within the original command.

` injected command `
$( injected command )

The triggering of cmd.exe transformations hinges on the presence of specific metacharacters, including:

  • Left and Right Parentheses ( )
  • Percent (%)
  • Exclamation Mark (!)
  • Caret (^)
  • Quotation Marks (")
  • Less Than (<) and Greater Than (>)
  • Ampersand (&)
  • Pipe (|)

Due to the intricate nature of securing such processes, invoking shells should be avoided unless absolutely necessary.

For methods of exec that accept the command line as a unified string, it's advised to partition them using StringTokenizer. In Windows, these segments are reconstituted into a single argument string prior to execution. OS injection assaults necessitate the explicit invocation of a command interpreter. However, vulnerability to argument injection arises with spaces, double quotes, and other characters, particularly when they initiate with a forward slash (/) or hyphen (-). Consequently, any string data originating beyond the program's trust boundary mandates thorough sanitization prior to execution as a command on the current platform.

Non-Compliant Code Example (Windows)

The non-compliant code example below reads the dir system property and invokes Runtime.exec() to display the specified directory using the Windows dir command. The environment variable is only one source of untrusted data. Any data originating outside the program’s trust boundary (from a user, for example) should be considered untrusted.

// Non-Compliant Code Example

function runCmd() : void {
var dir : String = System.getProperty("dir")
var rt : Runtime = Runtime.getRuntime()
var proc : Process = rt.exec("cmd.exe /C dir " + dir)
var result : int = proc.waitFor()
if (result != 0) {
print("process error: " + result)
var ins : InputStream = (result == 0) ? proc.getInputStream() : proc.getErrorStream()
var c: int = ins.read()
while (c != -1) {
System.out.print(c as char) // cast to char
c = ins.read()
}
}
}

Because the Runtime.exec method receives unsanitized data originating from the environment, this code is susceptible to a command injection attack. For example, if the program is running with root privileges and the attacker provides the following string for dir:

Commandline injection example

Non-Compliant Code Example (POSIX)

The following non-compliant code example provides the same functionality but uses the POSIX ls command. The only difference from the Windows version is the argument passed to Runtime.exec().

// Non-Compliant Code Example

function runCmd() : void {
var dir : String = System.getProperty("dir")
var rt : Runtime = Runtime.getRuntime()
var proc : Process = rt.exec(new String[] {"sh", "-c", "ls " + dir});
var result : int = proc.waitFor()
if (result != 0) {
print("process error: " + result)
var ins : InputStream = (result == 0) ? proc.getInputStream() : proc.getErrorStream()
var c: int = ins.read()
while (c != -1) {
System.out.print(c as char) // cast to char
c = ins.read()
}
}
}

The attacker can supply the same command shown in the previous non-compliant code example with similar effects. The command executed is: sh -c 'ls dummy & echo bad'

Compliant (Sanitized) Solution

This below compliant solution adds several lines of code to sanitize the untrusted user input by allowing only a small group of "safe" characters in the argument that are passed to the Runtime.exec method. If any unsafe characters are found, the function immediately throws IllegalStateException.

// Compliant Solution

function runCmdSanitized() : void {
var dir : String = System.getProperty("dir")
if (not Pattern.matches("[0-9A-Za-z@.]+", dir)) {
throw new IllegalStateException()
}
var rt : Runtime = Runtime.getRuntime()
var proc : Process = rt.exec("cmd.exe /C dir " + dir) // Windows version
var result : int = proc.waitFor()
if (result != 0) {
print("process error: " + result);

var ins : InputStream = (result == 0) ? proc.getInputStream() :
proc.getErrorStream()
var c : int = ins.read()
while (c != -1) {
System.out.print(c as char) // cast to char
c = ins.read()
}
}
}

The sanitization approach used in this compliant solution can reject valid directories. Also, because the command interpreter invoked is system-dependent, it is difficult to prove that this solution prevents command injections on every platform on which a Gosu program might run.

Compliant Solution (Static Strings)

The compliant solution below prevents command injection by passing only trusted strings to the Runtime.exec method. The user has control over which string is used but cannot inject string data directly into the call to Runtime.exec.

// Compliant Solution

// throws NumberFormatException & IllegalStateException
function viewDirContents() : void {
var dir : String = null
// Only allow integer choices
var number : int = Integer.parseInt(System.getProperty("dir"))
switch (number) {
case 1:
dir = "directory_1"
break; // Option 1
case 2:
dir = "directory_2"
break; // Option 2
default: // Invalid
throw new IllegalStateException()
}
var rt : Runtime = Runtime.getRuntime()
var proc : Process = rt.exec("cmd.exe /C dir " + dir) // Windows version
var result : int = proc.waitFor()
if (result != 0) {
print("process error: " + result)

var ins : InputStream = (result == 0) ? proc.getInputStream() : proc.getErrorStream()
var c : int = ins.read()
while (c != -1) {
System.out.print(c as char) // cast to char
c = ins.read()
}
}
}

This compliant solution hard codes the directories that may be listed. This solution is appropriate for a relatively small, static, and enumerable set of options.

Compliant Solution (Gosu Implementation)

When executing a system command can be accomplished by some other means, it is almost always advisable to do so. This compliant solution below uses the File.list method to provide a directory listing, eliminating the possibility of command or argument injection attacks.

// Compliant Solution

function viewDirContentsCS() : void {
var dir: File = new File(System.getProperty("dir"));
if (!dir.isDirectory()) {
System.out.println("Not a directory");
} else {
foreach (file in dir.list()) {
System.out.println(file);
}
}
}

In this case, the solution is simpler than invoking a system command and works with all valid directory names.

Risk Assessment

Passing untrusted, unsanitized data to a command interpreter can result in OS command and argument injection attacks.

RuleSeverityLikelihoodRemediation CostPriorityLevel
INJ02-GHighLikelyMediumL12L1

Additional resources