Skip to main content

CWE 89: SQL INJECTION

Flaw

CWE 89: SQL Injection flaws occur when you create a SQL statement by building a String that includes untrusted data, such as input from a web form, cookie, or URL query-string.

For example:

String accountBalanceQuery =
  "SELECT accountNumber, balance FROM accounts WHERE account_owner_id = "
  + request.getParameter("user_id");

try {
    Statement statement = connection.createStatement();
    ResultSet rs = statement.executeQuery(accountBalanceQuery);
    while (rs.next()) {
        page.addTableRow(rs.getInt("accountNumber"), rs.getFloat("balance"));
    }
} catch (SQLException e) { ... }

Under normal operation, the user with ID 984 might be logged in, and go to the URL https://bankingwebsite/show_balances?user_id=984

This means that accountBalanceQuery would then be:

SELECT accountNumber, balance FROM accounts WHERE account_owner_id = 984

This is passed to the database, and the accounts and balances for user 984 are returned, and rows are added to the page to display.

The attacker could change the parameter “user_id” in the URL to:

https://bankingwebsite/show_balances?user_id=984%20OR%201%3D1

Which would cause the user_id parameter to be interpreted as:

984 OR 1=1

And this results in accountBalanceQuery being:

SELECT accountNumber, balance FROM accounts WHERE account_owner_id = 984 OR 1=1

When this query is passed to the database, it will return all the account numbers and balances it has stored, and rows are added to the page to display. The attacker now knows every user’s account numbers and balances.

Fix

You can fix this flaw by using a Prepared Statement to create a Parameterized Query as below:

String accountBalanceQuery =
-  "SELECT accountNumber, balance FROM accounts WHERE account_owner_id = "
-  + request.getParameter("user_id");
+  "SELECT accountNumber, balance FROM accounts WHERE account_owner_id = ?";
 
 try {
-    Statement statement = connection.createStatement();
-    ResultSet rs = statement.executeQuery(accountBalanceQuery);
+    PreparedStatement statement = connection.prepareStatement(accountBalanceQuery);
+    statement.setInt(1, request.getParameter("user_id"));
+    ResultSet rs = statement.executeQuery();
     while (rs.next()) {
         page.addTableRow(rs.getInt("accountNumber"), rs.getFloat("balance"));
     }
view fixed code only

If an attacker attempts to supply a value that’s not a simple integer, then statement.setInt() will throw a SQLException rather than permitting the query to complete.

Note that the query string is composed using a placeholder (the "?" in this example) for the user-supplied data, and then the user-supplied data is bound to the PreparedStatement object. This allows the database system (the combination of JDBC, driver, and the database server itself) to ensure that the query can execute safely.

References

CWE ↪OWASP ↪WASC ↪