Command injections are possible when user input is interpreted by a system shell (e.g., /bin/sh, Windows Command Prompt, or PowerShell). In the worst case, an attacker can execute arbitrary commands to execute other programs, elevate privilege, and access other system resources.
In Java programs, this vulnerability often appears in the form of a
Runtime.exec("cmd /C" ...) or
Runtime.exec("/bin/sh -c" ...) where unsanitized user
input is used as arguments to some external program or non- executable
In this exercise, we provide a naive program that looks for a domain
This simple example demonstrates how a developer might choose to use an
external program to perform certain tasks. When an external program
does not have a compatible API, it is tempting to use a shell
interpreter to invoke the program, but this may expose the risk of
Exploit the command injection vulnerability to run an extra command.
In a real exploit, the command may be something disastrous like
rm -rf /, but for now stick with something more innocuous
For this exercise, we will see two ways to mitigate the vulnerability. The first will "Eliminate the Shell", which is an important part of the attack vector. By executing a program directly instead of trying to run a shell command, we mitigate the possibility of abusing shell metacharacters to execute two commands.
Even better than invoking another program is using an internal API. In this example, the JDK has internal implementations of domain name resolution. Using an API instead of executing a program provides much more control over how the library/program interprets inputs. For example, a well-written API will have separate interfaces/methods for separate functionality, so you will not be able to accidentally allow illegal behavior. In nearly every case, an internal API is both more secure and more efficient than trying to execute an external program.
This exercise will be completed entirely on the command line shell of the provided virtual machine. To open the shell, right-click on the "EXERCISES" directory and select "Open in Terminal". Enter the following command to change into the Command Injection exercise directory:
We provide a Makefile that will compile the program.
Every time you change the
you must recompile the program before running it again.
Enter the following command to compile the program:
The next step is to run the program and test some inputs. To execute the program, enter the following command:
You should see the output from the program prompting for a hostname.
Type a hostname like
wisc.edu and press enter.
The program will lookup the hostname using the
command and display the output.
Here is an example of a successful input/output for the program:
hostname to lookup: wisc.edu Server: 127.0.1.1 Address: 127.0.1.1#53 Non-authoritative answer: Name: wisc.edu Address: 13..92.9.70
Try this with a few different hostnames, including hostnames that do
not exist or are not correctly formatted.
To exit the program, simply type
exit in place of a
On some inputs, it is possible for the
nslookup command to
execute in "interactive mode" when no arguments are provided to it.
In this case you can press
ctrl+c to terminate the
Now that you understand the basic behavior of the program, it's time to
look at the implementation.
This program is implemented in
Use your favorite text editor to
open this file.
Enter the following command to open the file in Nano:
Spend some time looking at the code and tracing the flow of execution. You're looking for an attack surface and corresponding attack vector. In other words, how can an attacker's input reach the shell command?
Exit the text editor. Run the program again in the same way as Run the Program. Enter a hostname that, when inserted into the shell interpreter arguments, will result in a second command being run.
Once you manage to see the output of your second command, it's time to fix the vulnerability!
Let's go back to our text editor and open
(see Inspect the Program Code).
This time, we're going to make some changes.
The vulnerability in this example comes from the shell interpreter's
ability to execute multiple programs. Instead of executing a shell
/bin/sh), execute the intended program
Once you have a potential fix implemented, save and exit the file
(e.g., in Vim, press
esc then type
Now recompile and run the program as we did in Compile the Program and Run the
Try your exploit again.
Make sure to test both good and bad inputs,
so that we know the exploit is no longer possible and
the program still works as intended.
Repeat the process of changing the program, compiling, and testing until you are convinced that the vulnerability is mitigated and the original program intent remains functional.
Now we'll improve our solution by using an internal API.
This will remove the possibility of bad input to the
nslookup command causing unexpected behavior.
java.net.InetAddress in the
documentation for domain name lookup APIs.
Use the documentation to create a new method that replaces
rDomainName() and generates the appropriate output using
Repeat the process of changing the program, compiling, and testing until you are convinced that the vulnerability is mitigated and the original program intent remains functional. This time, you should find that the program will not be easily broken!