Fingerprint the DBMS before you waste UNION requests
Low-noise SQLi probes for MySQL, PostgreSQL, SQL Server, Oracle, and SQLite. Two signals before you lock the dropdown.
I ran LIMIT 1 extraction against SQL Server once because the error page "felt" like MySQL. Injection was real. Syntax was not. Twenty minutes gone.
Fingerprinting is cheap insurance. Five minutes of probes beats an hour of misfired UNION columns.
After detection on the crafter recipe, open the Fingerprint step. DBMS ID payloads are grouped there. This is how I read responses.
MySQL and MariaDB
Boolean that should stay quiet:
' AND connection_id()=connection_id()--
Version leak in error or reflection:
' AND @@version LIKE '%MariaDB%'--
Errors mentioning mysqli, mysql_fetch, or XPath extractvalue tricks leaning MySQL-family are common. MariaDB usually smells like MySQL until @@version says otherwise.
PostgreSQL
Watch ::int cast behavior, CHR(65) vs MySQL CHAR(65), error text containing PostgreSQL. Time probe if scope allows: pg_sleep(3) with measured baseline. ORMs on Postgres still concatenate badly in legacy modules.
Microsoft SQL Server
@@version in error-based payloads is the classic tell. Stacked ; WAITFOR DELAY '0:0:3' when multi-statement execution is in scope. OLE DB or "SQL Server" in driver errors. TOP 1 instead of LIMIT for follow-up enumeration.
Oracle
FROM dual in dummy selects. NVL instead of IFNULL. Comment and quoting rules punish MySQL cheatsheet copy-paste. Error-based Oracle tricks (UTL_INADDR, CTXSYS) are loud. Scope only.
SQLite
Embedded in mobile APIs and analytics sidecars more than people expect. sqlite_version() where version() does not. Strict typing on UNION null probes. No network primitives to lean on.
When signals disagree
MySQL comment works but @@version throws? Proxy sanitization, mixed stack, or you are not where you think you are. Run a third probe before you title the finding "MySQL injection."
Write the exact delta: status code, byte length, substring that changed.
Lock the DBMS dropdown only after two probes agree. Then regenerate detection, UNION, and blind sets for that engine only. "Looks like MySQL" is a note. "Boolean on connection_id() plus @@version substring in error" is evidence.
One more trap: ORM stack traces that mention Postgres while the legacy reporting module still hits MySQL on another port. Fingerprint the parameter you are actually injecting, not the framework name on the about page. If the app is behind PgBouncer or a read replica, error text can lie. Stick to boolean behavior on the wire you control.
FAQ
- Why fingerprint before choosing payloads?
- LIMIT, TOP, dual, comment chars, and version functions are not portable. Wrong dialect wastes requests and produces false negatives that read as not injectable in the report.
- Can one payload detect every database?
- No. Run a short tree: boolean quirks, version functions, comment style. Two independent signals beat one lucky error message.
- Is timing-based fingerprinting safe?
- It works but it is slow and shows up in latency dashboards. Prefer boolean or error leakage first. Save pg_sleep and WAITFOR for when scope and patience allow.