Avoid command injection with Apache Common CLI
This article is about using secure coding patterns and correcting vulnerable code. Applications need third-party libraries to provide common facilities for repetitive tasks like logging, parsing, … When a developer uses an opensource library, he must understand that his code inherits also possible security issues. For this reason, opensource libraries must be audited for risks to avoid problems like log4shell vulnerability.
Even if the library is secure…
As part of our daytime job, we audited the Apache Commons CLI source code, to see if exploitable vulnerabilities are present.
Apache Commons CLI is an opensource library providing an API to parse command-line arguments.
Those arguments are used by command-line utilities written in Java. Providing secure input to programs is a hot topic nowadays and this library does its job very well.
The security posture of this library is good and, at the time this post is written, there are no noteworthy vulnerabilities.
… it can be used in a non secure way
Even if the command-line parsing is done properly, it is possible for a developer to use bad programming patterns in his code, introducing a severe security issue like arbitrary command injection.
DefaultParser class does not filter characters that can be used to append another command, like: ' ; | &
. This is a design choice and developers must be aware of it. If they need to use parsed arguments in sensitive operations, like spawning a new process, they must take care of the sanitization part.
OptionValidator class contains a routine named isValidChar
to sanitize input. This method uses Character.isJavaIdentifierPart
as sanitizing routine and it checks if the option value can be a valid Java identifier. However, it is possible to bypass this validation by injecting arbitrary commands if the parameter is used as a command to be executed later on.
Consider the following code:
cmd = parser.parse(options, args);
if (cmd.hasOption("t")) {
String value = cmd.getOptionValue("t");
System.out.println("Value is = " + value);
try {
Process process = Runtime.getRuntime().exec("sh -c " + value);
printResults(process);
} catch (IOException ioe) {
System.err.println(ioe.getMessage());
}
}
Executing the code, passing a command-line argument containing a ‘;’, we can verify that the command injection is possible:
$ java -classpath commons-cli-1.4.jar:. Application -t "ls;id"
Value is = ls;id
Application.class
Application.java
commons-cli
commons-cli-1.4.jar
dotfiles
uid=1000(paolo) gid=1000(paolo) groups=1000(paolo),470(wheel)
The application trusts user input so the “;” character is passed to exec() call so the resulting string is: sh -c ls; id
. As a result, the id
command is executed after sh -c ls
.
It’s important to understand that the problem is how the client application is written. Changing the exec line in the following, the command injection is impossible:
Process process = Runtime.getRuntime().exec(value);
With this coding style, the same
attack attempt results in attempting to execute “ls;id” if command using exec(). This led to a “command not found” exception.
As a best practice to developers, we can suggest not to append a parsed value to “sh -c” but use it directly in exec() or a similar call.
Conclusions
In the next article, we will design an improved version of the parser that adds a sanitization API designed to consume input for sensitive usage.
This will allow even applications with unsafe patterns to be protected against command injection vulnerabilities.
Related Articles
Apr 03rd, 2023
SUSE Linux Enterprise and SBOM support
Oct 27th, 2022