The code creates an SQL prepared statement from a String
that was formed dynamically. This may be vulnerable to SQL injection attacks.
While prepared statements are generally safer than directly building queries and executing them, their effectiveness is reduced when they are created dynamically, especially when the queries are created from untrusted input. One use case of this is when parameters such as table or column names need to be selected dynamically. Prepared statements do not generally allow such values to be parameterized, so we may end up resorting to string concatenation again:
// ...
String table = request.getParameter("table");
String user = request.getParameter("user");
String pass = request.getParameter("pass");
String query = "SELECT * FROM " + table + " WHERE user = ? AND pass = ?"; // Unsafe...
PreparedStatement pStmt = connection.prepareStatement(query);
pStmt.setString(1, user);
pStmt.setString(2, pass);
ResultSet res = pStmt.execute();
// ...
A savvy attacker would be free to pass an input such as "users WHERE (user = ? AND pass = ?) OR 1=1 --"
in the table parameter which would allow them to trick the backend into fetching all rows of the users
table. The resultant query can be interpreted as shown below:
SELECT * FROM users WHERE (user = ? AND pass = ?) OR 1=1 -- WHERE user = ? AND pass = ?
| /* Commented part is ignored; (a AND b) OR true always evaluates to true. */
V
SELECT * FROM users WHERE 1=1
| /* We don't need the where clause either. */
V
SELECT * FROM users
There are a number of strategies that could prevent this:
table
parameter could be checked against a whitelist to ensure only valid table names are acceptedString table = request.getParameter("table");
if (!whitelist.contains(table)) {
// ...
} else {
// ...
}
Here's an example usage of ESAPI with Oracle SQL (Example taken from OWASP's SQL Injection Cheat Sheet):
Codec ORACLE_CODEC = new OracleCodec();
String query = "SELECT user_id FROM user_data WHERE user_name = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter("userID"))
+ "' and user_password = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter("pwd")) +"'";
int querySelector = Integer.parseInt(request.getParameter("query"));
if (querySelector < 0 || querySelector >= QUERIES.size) {
// ...
}
conn.execute(QUERIES[querySelector]);
If there is a solid guarantee that such an attack cannot occur, this issue can be safely ignored.