$24
1. Introduction
Exceptions are a control flow mechanism used to handle and propagate errors. Some form of these mechanisms exist in nearly every modern programming language. When a programmer writes code that may throw an exception, improper handling of that exception can cause the program to crash (effecting a denial of service), reveal information that is valuable to an attacker, or result in partially changed state (where the changes made immediately before the error are not reverted). See the chapter on Exceptions for more details on exception-related vulnerabilities.
1.1 Exercise Description
In this exercise, we provide a very simple program that checks for a given username and password in a portable SQLite database. If a correct username and password is given, then the program will display a welcome message. If an incorrect password or nonexistent username is supplied, the program will display a failure message. Your objective is to trigger exceptions in several ways to cause the program to crash, cause the program to leak information about our database access, and cause the program to trigger a "correct" login without credentials.
Note: This program violates several best practices regarding password storage, credential management, authentication, etc., but you should focus on the exceptions for this exercise. You will see this sample again in 3.8.1 SQL Injections.
1.2 Vulnerability Mitigation
Proper exception handling includes many best practices. For this exercise, we will add conditions to handle exceptions that would crash the program, remove details from our error messages, and change the logic of our login method to appropriately deny input that causes an exception. The chapter on Exceptions covers more of these mitigation practices.
2. Exercise Instructions
This exercise will be completed entirely on the command line terminal of the provided virtual machine. To open the terminal, right-click on the "EXERCISES" directory and select "Open in Terminal". Enter the following command to change into the exercise directory:
$ cd 3.4_exceptions
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
If you have edited the Main.java file and it contains compiler errors, the make command will fail and show you where the errors were found.
2.2 Run the Program
The next step is to run the program and test some inputs. To execute the program after compiling, enter the following command:
$ java Main
You should see output from the program prompting for a username. Type a username and press enter. The program will then prompt you for a password. Type a password and press enter. To ease exploitation, the password field will not be hidden. The program will check the SQLite database for the username/password combination and tell you if the login was successful. The following is an example of a correct username/password input:
username: some_guy password: his_password Login Successful! Welcome some_guy
Try this a few times with different usernames and passwords to see how the program behaves. You may even want to try some inputs that you think might break the program, but we will focus on this more later. To exit the program, type exit in place of a username. All of the "correct" username and password combinations can be found in create_db.sql, which the Makefile uses to generate the database.
2.3 Inspect the Program Code
Now that you understand the basic behavior of the program (and maybe even an input that breaks it), you should take a look at the implementation 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
Look through the code first to understand the basic control flow and logic. Once you can follow the program logic, focus on the checkPW method to understand the purpose of the catch blocks. Begin thinking about what exceptions the program might not be handling correctly or at all. An important note to remember is that Unchecked Exceptions do not need to be "caught" whereas Checked Exceptions (like the SQLException) must be within a try-catch block. Some examples of unchecked exceptions are NullPointerException, ClassCastException, IllegalArgumentException, and ArrayIndexOutOfBoundsException.
After looking at the code, you might be thinking that you will need to trigger a SQLException. You would be right. Find an attack vector from the user's input at the terminal to one of the method calls that can throw a SQLException. Specifically, find which method within the try block has an argument containing the user's input.
2.4 Exploit the Vulnerability
Now that you know where your input can reach the exception-throwing code, it's time to cause an exception. Run the program again and test inputs that you think will cause an exception.
When you successfully trigger a SQLException, you will see that it also causes a NullPointerException! Congratulations, you've exploited an exception handling vulnerability to crash the program and reveal sensitive information about the SQL query! If this was a network service, you would have caused a successful denial of service attack. The information you revealed about the SQL query will be very useful for a potential SQL Injection attack.
Before continuing to the mitigation, inspect the code again to understand why we got a NullPointerException in addition to the SQLException that we were expecting. When the SQL statement fails, the code jumps into the catch block without assigning the SQL object variables. After printing the debugging information, the code continues to "cleanup" the SQL objects by calling the close method on each of them. The checked SQLException forces the programmer to address it, but this program ignores the possibility of a NullPointerException. Since the results variable isn't assigned until after the point where our code failed, it is still null by the time our code calls results.close().
2.5 Mitigate the Vulnerability
Your exploit above revealed two problems with exception handling in this program: an unhandled NullPointerExceptionthat crashes the program and a SQLException that reveals information about the program and database structure.
First, mitigate the NullPointerException problem. This should be fairly simple because a null SQL object does not need to be closed. Two possible options are by checking that each object is not null before closing it or by catching the NullPointerException and ignoring it. Fixing this issue is simple on the surface, but there is an unsolved discussion underlying the consequences of ignoring exceptions. Note that checking for the null object is logically equivalent to ignoring the NullPointerException. Because a programmer has no way of knowing exactly what future situations may cause the exception, it would be unwise to ignore it entirely. However, since the programmer can expect that the common cause of this exception is a result of the previous code failing, it would be unnecessary to count ever failure twice. For this exercise, it is sufficient to ignore the exception (primarily because it has no logging framework), but it is important to take special care whenever considering the option to ignore an exception.
Next, you will mitigate the information leak in our SQLException handling. It is very common for programmers to accidentally leave sensitive debugging information in production code. Before mitigating the vulnerability in this exercise, we will cover some background on error logging. The best practice is to log errors in a safe place using some logging framework like Log4j. This may be a secure log file, a remote logging server, or some other safe system for recording the information. An important note is to never log private user information like passwords, personally identifiable information, credit card numbers, etc. Instead, you should log the relevant program state. The user will never see this logged information. Instead, you should display a generic error message to the user. In more advanced systems, you may even include a reference number (also recorded in the log file) that a user can give to a support team to find details about the error. For more information on related practices, see the chapter on Exceptions. For this exercise, there is no logging framework, so you can simply report the generic error to the user on the error stream.
Implement these fixes in Main.java. Don't forget to compile the program using the make command every time you change the source file. Run the program with the java
Main command and test your implementation. First test that the correct program behavior is unchanged (i.e., correct username and password combinations succeed and incorrect username and password combinations fail). You may have to repeat this process several times to get the program to compile and run correctly. Now test your previous exploit input. You should see the generic error message that you report in the SQLException catch block when you are successful.
Now there's another problem! Your output should also show that the login was successful! Once again, you are experiencing the tendency of error handling problems to cascade, hiding and revealing other errors. This is a thirdproblem with error handling that your exploit only revealed after fixing the NullPointerException problem.
Implement the fix for this third problem and repeat the same testing procedure as before. This time, continue until none of your inputs cause a successful login message except correct username and password combinations. Note that a SQL Injection is still possible, but you can ignore this until the injection exercise.
Congratulations! You have successfully mitigated an improper exception handling vulnerability!
3. Delivery Instructions
Deadline: February 26 at 11am.
You'll need to deliver a short report containing:
Screenshots showing the input(s) used for the attack, and the output(s) you got from the system.
Your commented code for the mitigation.
Screenshots showing the input(s) and output(s) after fixing the vulnerability.
A short explanation on your attack and your mitigation.