External programs can be invoked from Java code using the exec()
method of the java.lang.Runtime
class. The exec()
method returns an object of a subclass of the abstract class java.lang.Process
. The exitValue()
method can be used to observe the return value of the process. There are several problems of the exec()
method that are discussed in this recommendation.
Noncompliant Code Example
This noncompliant code example invokes notemaker
, a hypothetical cross-platform notepad application. However, the program does not wait for the notemaker
process to terminate, and it throws an IllegalThreadStateException
if the notemaker
process has not completed when the exitValue()
method is called.
public class Exec { public static void main(String args[]) { try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); int exitVal = proc.exitValue(); } catch (Throwable t) { t.printStackTrace();} } }
The waitFor()
method blocks the calling thread until the invoked process terminates. The code can be changed as shown in the modified noncompliant example below. This also has a shortcoming; the program may be blocked for a long time because of the limited buffer size used for the standard output streams on many platforms.
public class Exec { public static void main(String args[]) { try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); int exitVal = proc.waitFor(); } catch (Throwable t) { t.printStackTrace();} } }
Compliant Solution (1)
An inefficient solution would be to exhaust the output (or stderr
) stream before beginning to wait for the process. A better option is to empty both the stderr
and output streams. The code below shows this but is not the best solution since it does not process any arguments passed to the external program (notemaker
) and in turn exits with an OS-specific non-zero exit code.
import java.io.InputStream; import java.io.InputStreamReader; import java.io.BufferedReader; public class Exec { public static void main(String args[]) { try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); InputStream is = proc.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line; while ( (line = br.readLine()) != null) System.out.println(line); // prints the error lines int exitVal = proc.waitFor(); } catch (Throwable t) { t.printStackTrace(); } } }
Compliant Solution (2)
The second compliant solution spawns a command interpreter and executes the user supplied command. It uses a separate OutputStream
to write the output that is read in from the external process.
import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; import java.io.FileOutputStream; class Exec extends Thread { InputStream is; String type; OutputStream os; Exec(InputStream is, String type) { this(is, type, null); } Exec(InputStream is, String type, OutputStream redirect) { this.is = is; this.type = type; this.os = redirect; } public void run() { try { PrintWriter pw = null; if (os != null) { pw = new PrintWriter(os); } InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line=null; while ( (line = br.readLine()) != null) { if (pw != null) { pw.println(line); pw.flush(); } System.out.println(type + ">" + line); } if (pw != null) pw.flush(); } catch (IOException ioe) { ioe.printStackTrace(); } } } public class ExecMe { public static void main(String[] args) { // ... perform command argument check ... try { FileOutputStream fos = new FileOutputStream("c:\\output.txt"); Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); // any error message? Exec errorGobbler = new Exec(proc.getErrorStream(), "ERROR"); // any output? Exec outputGobbler = new Exec(proc.getInputStream(), "OUTPUT", fos); errorGobbler.start(); outputGobbler.start(); // any error? int exitVal = proc.waitFor(); errorGobbler.join(); // handle condition where the outputGobbler.join(); // process ends before the threads finish fos.flush(); fos.close(); } catch (Throwable t) { t.printStackTrace(); } } }
Risk Assessment
Misusing the exec()
method can seriously compromise the security of a Java application.
Rule |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
FIO01-J |
medium |
unlikely |
medium |
P4 |
L3 |
Automated Detection
TODO
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
[[API 06]] method exec()
[[Daconta 00]]
[[Daconta 03]] Pitfall 1
FIO00-J. Canonicalize path names originating from untrusted sources 08. Input Output (FIO) MSC33-J. Prevent against SQL Injection