-
Notifications
You must be signed in to change notification settings - Fork 831
Commit
* test: Add failing test for simple query mode parameter injection
Adds a failing test to demonstrate how direct parameter injection in simple query
mode allows for modifying the executed SQL. The issue arises when a bind placeholder
is prefixed with a negation. The direct replacement of a negative value causes the
resulting token to be considered a line comment.
For example the SQL:
SELECT -?, ?
With parameter values of -1 and any text with a newline in the second parameter
allows arbitrary command execution, e.g. with values -1 and "\nWHERE false" causes
the query to return no rows. More complicated examples can be created by adding
statement terminators.
* fix: Escape literal parameter values in simple query mode
Escape all literal parameter values and wrap them in parentheses to prevent SQL injection
when using specially crafted parameters and SQL in simple query mode. Previously the raw
value of the parameter, e.g. 123, was injected into the ? placeholder. With this change
all parameters are injected as '...value...' literals that are cast to the desired type
by the server and wrapped in parentheses.
So the SQL SELECT -? with a parameter of -123 would become: SELECT -('-123'::int4)- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| /* | ||
| * Copyright (c) 2024, PostgreSQL Global Development Group | ||
| * See the LICENSE file in the project root for more information. | ||
| */ | ||
|
|
||
| package org.postgresql.jdbc; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| import org.postgresql.test.TestUtil; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import java.sql.Connection; | ||
| import java.sql.PreparedStatement; | ||
| import java.sql.ResultSet; | ||
|
|
||
| public class ParameterInjectionTest { | ||
| @Test | ||
|
Check failure on line 20 in pgjdbc/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java
|
||
| public void negateParameter() throws Exception { | ||
| try (Connection conn = TestUtil.openDB()) { | ||
|
Check failure on line 22 in pgjdbc/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java
|
||
| PreparedStatement stmt = conn.prepareStatement("SELECT -?"); | ||
|
Check failure on line 23 in pgjdbc/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java
|
||
|
|
||
| stmt.setInt(1, 1); | ||
|
Check failure on line 25 in pgjdbc/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java
|
||
| try (ResultSet rs = stmt.executeQuery()) { | ||
|
Check failure on line 26 in pgjdbc/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java
|
||
| assertTrue(rs.next()); | ||
|
Check failure on line 27 in pgjdbc/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java
|
||
| assertEquals(1, rs.getMetaData().getColumnCount(), "number of result columns must match"); | ||
|
Check failure on line 28 in pgjdbc/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java
|
||
| int value = rs.getInt(1); | ||
|
Check failure on line 29 in pgjdbc/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java
|
||
| assertEquals(-1, value); | ||
| } | ||
|
|
||
| stmt.setInt(1, -1); | ||
| try (ResultSet rs = stmt.executeQuery()) { | ||
| assertTrue(rs.next()); | ||
| assertEquals(1, rs.getMetaData().getColumnCount(), "number of result columns must match"); | ||
| int value = rs.getInt(1); | ||
| assertEquals(1, value); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void negateParameterWithContinuation() throws Exception { | ||
| try (Connection conn = TestUtil.openDB()) { | ||
| PreparedStatement stmt = conn.prepareStatement("SELECT -?, ?"); | ||
|
|
||
| stmt.setInt(1, 1); | ||
| stmt.setString(2, "\nWHERE false --"); | ||
| try (ResultSet rs = stmt.executeQuery()) { | ||
| assertTrue(rs.next(), "ResultSet should contain a row"); | ||
| assertEquals(2, rs.getMetaData().getColumnCount(), "rs.getMetaData().getColumnCount("); | ||
| int value = rs.getInt(1); | ||
| assertEquals(-1, value); | ||
| } | ||
|
|
||
| stmt.setInt(1, -1); | ||
| stmt.setString(2, "\nWHERE false --"); | ||
| try (ResultSet rs = stmt.executeQuery()) { | ||
| assertTrue(rs.next(), "ResultSet should contain a row"); | ||
| assertEquals(2, rs.getMetaData().getColumnCount(), "rs.getMetaData().getColumnCount("); | ||
| int value = rs.getInt(1); | ||
| assertEquals(1, value); | ||
| } | ||
| } | ||
| } | ||
| } | ||