SQL Injection in GraphQL WebSocket Escalated to PII & Document Leak
quality 9/10 · excellent
0 net
Tags
SQL Injection in GraphQL WebSocket Escalated to PII & Document Leak | by Ahmed Ghadban - Freedium
Milestone: 20GB Reached
We’ve reached 20GB of stored data — thank you for helping us grow!
Patreon
Ko-fi
Liberapay
Close
< Go to the original
SQL Injection in GraphQL WebSocket Escalated to PII & Document Leak
Bounty: $2,000 Severity: Critical
Ahmed Ghadban
Follow
~3 min read
·
April 5, 2026 (Updated: April 5, 2026)
·
Free: Yes
📌 Overview
During a recent engagement on an application designed for managing and customizing legal templates, I discovered a critical vulnerability within a GraphQL WebSocket endpoint. What initially appeared to be an unexploitable Insecure Direct Object Reference (IDOR) was successfully escalated into an Error-Based PostgreSQL Injection. By chaining these two vulnerabilities, I was able to bypass high-entropy ID protections to leak extensive Personally Identifiable Information (PII) and gain unauthorized read/lock access to private legal documents.
🔍 Reconnaissance & Initial Discovery
While proxying traffic and interacting with the web application, I noticed a persistent, quiet WebSocket connection communicating with the endpoint /graphql-ws .
The traffic over this socket was minimal and seemingly benign, periodically sending keep-alive or heartbeat requests:
{"type": "ka"}
Curious about its actual purpose, I dug into the application's client-side code. By analyzing the main.js file and searching for the /graphql-ws path, I reverse-engineered the allowed GraphQL operations. I discovered that this WebSocket endpoint supported hidden functionalities to read and lock documents by passing a document ID.
🚧 The Initial Blocker: An "Unexploitable" IDOR
I immediately tested the endpoint for an Insecure Direct Object Reference (IDOR) by attempting to access a document belonging to another test account.
The Result: The application was vulnerable to IDOR; the backend did not validate ownership, allowing me to read and lock the document. The Catch: The document IDs were securely generated 25-digit numeric strings. Because the entropy was so high, brute-forcing the IDs to access victim documents was practically impossible. Reporting a purely theoretical IDOR without a way to find the IDs usually leads to a downgrade in severity, so I needed a way to leak them.
💥 Escalation: Finding the SQL Injection
To see how the application handled unexpected input, I began fuzzing the ID parameter in the WebSocket request, replacing the 25-digit integer with random alphanumeric characters and special symbols.
The server responded with an unexpectedly verbose error. The application failed to sanitize the input, resulting in a syntax error leak that exposed the underlying database structure.
Key details leaked from the initial error:
Database Type: PostgreSQL.
Schema Leakage: The error message explicitly referenced the email column.
🛠️ Exploitation: Error-Based SQLi
Knowing the database was PostgreSQL and having a valid column name ( email ), I shifted to an Error-Based SQL Injection methodology.
I injected the leaked email column name directly into the vulnerable ID parameter, intentionally causing a type conversion error (e.g., trying to evaluate text as a 25-digit integer).
Result: The database threw an exception, and the error message reflected back the actual email address of a random user!
I expanded the payload to enumerate other columns. Through trial and error, I successfully extracted:
Full Names
Physical Addresses
Signature IDs
Messages
Document IDs
Bypassing the Single-Row Limitation
Initially, the error reflection only returned data for a single user. To extract data across the entire database, I utilized PostgreSQL's string concatenation operator || .
By structuring payloads using ||'|'|| , I was able to concatenate columns from other tables and force the database to evaluate them in the error output. This allowed me to systematically dump PII and sensitive data for arbitrary users across the platform.
🔗 The Kill Chain: Putting It All Together
With the Error-Based SQLi fully operational, the final step was to close the loop on the initial IDOR.
Extract IDs: Used the Error-Based SQLi to dump the 25-digit document_id values for private users.
Exploit IDOR: Passed the newly leaked, high-entropy document IDs back into the original /graphql-ws WebSocket request.
Impact: Successfully bypassed all authorization controls to read and lock highly sensitive, customized legal documents belonging to other organizations and PII Leakage.
💡 Takeaways
Don't ignore the quiet endpoints: Keep-alive WebSockets ( {"type": "ka"} ) often hide larger, undocumented API surfaces. Client-side source code analysis is crucial for finding these hidden operations.
High entropy is not authorization: Using 25-digit random IDs is a good security practice (CSPRNG), but it is a defense-in-depth measure, not a replacement for proper Object-Level Authorization (BOLA/IDOR checks).
Always try to chain: A low-impact vulnerability (unexploitable IDOR) combined with an information disclosure (Error-Based SQLi) can quickly become a critical, system-compromising exploit.
#bug-bounty #cybersecurity #sql #bug-bounty-tips #hacking
Reporting a Problem
Sometimes we have problems displaying some Medium posts.
If you have a problem that some images aren't loading - try using VPN. Probably you have problem with
access to Medium CDN (or fucking Cloudflare's bot detection algorithms are blocking you).