$24
1. Introduction
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 call to 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 command.
1.1 Exercise Description
In this exercise, we provide a naive program that looks for a domain name address. 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 command injection. 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 like cat
/etc/passwd.
1.2 Vulnerability Mitigation
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.
2. Exercise Instructions
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:
$ cd 3.8.2_command_injections
2.1 Compile the Program
We provide a Makefile that will compile the program. Every time you change the Main.java file, you must recompile the program before running it again. Enter the following command to compile the program:
$ make
2.2 Run the Program
The next step is to run the program and test some inputs. To execute the program, enter the following command:
$ java Main
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 nslookup 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 hostname. 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 program.
2.3 Inspect the Program Code
Now that you understand the basic behavior of the program, it's time to look at the implementation. This program is implemented in Main.java. Use your favorite text editor to open this file. Enter the following command to open the file in Nano:
$ nano Main.java
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?
2.4 Exploit the Vulnerability
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. As mentioned in the introduction, the second command may be something disastrous like rm
-rf /, but for now try to execute something more innocuous like cat
/etc/passwd. Try this now, and only read on when you see the output of your second command.
Once you manage to see the output of your second command, it's time to fix the vulnerability!
2.5 Mitigate the Vulnerability
Let's go back to our text editor and open Main.java (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 command (e.g., /bin/sh), execute the intended program directly. In the case of nslookup, the executable can be invoked by name or full path instead of being an input to the shell executable. You'll have to be careful about how to parse the input as a parameter to nslookup.
Once you have a potential fix implemented, save and exit the file (e.g., in Vim, press esc then type :wq). Now recompile and run the program as we did in Compile the Program and Run the Program. 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. It's likely that your mitigated program will still have some chance of unexpected behavior, like the "interactive mode" of nslookup.
Improve the Mitigation using an Internal API
Now we'll improve our solution by using an internal API. This will remove the possibility of bad input to the nslookupcommand causing unexpected behavior. Look at java.net.InetAddress in the JDK documentation for domain name lookup APIs. Use the documentation to create a new method that replaces rDomainName() and generates the appropriate output using java.net.InetAddress.
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!
3. Delivery Instructions
You'll need to deliver a short report containing:
Screenshots or printouts showing the inputs used for the attack, and the outputs you got from the system.
Your commented code for the 2 mitigation approaches.
Screenshots or printouts showing the inputs and outputs after fixing the vulnerability, for the 2 mitigation approaches. You need to show that the vulnerability is mitigated, and that the program works as expected when the correct input is provided.
A short explanation on your attack and your mitigations.