diff --git a/src/pentesting-web/sql-injection/mysql-injection/README.md b/src/pentesting-web/sql-injection/mysql-injection/README.md index 5990df348..0dfb18ef2 100644 --- a/src/pentesting-web/sql-injection/mysql-injection/README.md +++ b/src/pentesting-web/sql-injection/mysql-injection/README.md @@ -198,6 +198,55 @@ mysql> select @@version; mysql> select version(); ``` +## MySQL Full-Text Search (FTS) BOOLEAN MODE operator abuse (WOR) + +This is not a classic SQL injection. When developers pass user input into `MATCH(col) AGAINST('...' IN BOOLEAN MODE)`, MySQL executes a rich set of Boolean search operators inside the quoted string. Many WAF/SAST rules only focus on quote breaking and miss this surface. + +Key points: +- Operators are evaluated inside the quotes: `+` (must include), `-` (must not include), `*` (trailing wildcard), `"..."` (exact phrase), `()` (grouping), `<`/`>`/`~` (weights). See MySQL docs. +- This allows presence/absence and prefix tests without breaking out of the string literal, e.g. `AGAINST('+admin*' IN BOOLEAN MODE)` to check for any term starting with `admin`. +- Useful to build oracles such as “does any row contain a term with prefix X?” and to enumerate hidden strings via prefix expansion. + +Example query built by the backend: + +```sql +SELECT tid, firstpost +FROM threads +WHERE MATCH(subject) AGAINST('+jack*' IN BOOLEAN MODE); +``` + +If the application returns different responses depending on whether the result set is empty (e.g., redirect vs. error message), that behavior becomes a Boolean oracle that can be used to enumerate private data such as hidden/deleted titles. + +Sanitizer bypass patterns (generic): +- Boundary-trim preserving wildcard: if the backend trims 1–2 trailing characters per word via a regex like `(\b.{1,2})(\s)|(\b.{1,2}$)`, submit `prefix*ZZ`. The cleaner trims the `ZZ` but leaves the `*`, so `prefix*` survives. +- Early-break stripping: if the code strips operators per word but stops processing when it finds any token with length ≥ min length, send two tokens: the first is a junk token that meets the length threshold, the second carries the operator payload. For example: `&&&&& +jack*ZZ` → after cleaning: `+&&&&& +jack*`. + +Payload template (URL-encoded): + +``` +keywords=%26%26%26%26%26+%2B{FUZZ}*xD +``` + +- `%26` is `&`, `%2B` is `+`. The trailing `xD` (or any two letters) is trimmed by the cleaner, preserving `{FUZZ}*`. +- Treat a redirect as “match” and an error page as “no match”. Don’t auto-follow redirects to keep the oracle observable. + +Enumeration workflow: +1) Start with `{FUZZ} = a…z,0…9` to find first-letter matches via `+a*`, `+b*`, … +2) For each positive prefix, branch: `a* → aa* / ab* / …`. Repeat to recover the whole string. +3) Distribute requests (proxies, multiple accounts) if the app enforces flood control. + +Why titles often leak while contents don’t: +- Some apps apply visibility checks only after a preliminary MATCH on titles/subjects. If control-flow depends on the “any results?” outcome before filtering, existence leaks occur. + +Mitigations: +- If you don’t need Boolean logic, use `IN NATURAL LANGUAGE MODE` or treat user input as a literal (escape/quote disables operators in other modes). +- If Boolean mode is required, strip or neutralize all Boolean operators (`+ - * " ( ) < > ~`) for every token (no early breaks) after tokenization. +- Apply visibility/authorization filters before MATCH, or unify responses (constant timing/status) when the result set is empty vs. non-empty. +- Review analogous features in other DBMS: PostgreSQL `to_tsquery`/`websearch_to_tsquery`, SQL Server/Oracle/Db2 `CONTAINS` also parse operators inside quoted arguments. + +Notes: +- Prepared statements do not protect against semantic abuse of `REGEXP` or search operators. An input like `.*` remains a permissive regex even inside a quoted `REGEXP '.*'`. Use allow-lists or explicit guards. + ## Other MYSQL injection guides - [PayloadsAllTheThings – MySQL Injection cheatsheet](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/MySQL%20Injection.md) @@ -206,9 +255,10 @@ mysql> select version(); - [PayloadsAllTheThings – MySQL Injection cheatsheet](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/MySQL%20Injection.md) - [Pre-auth SQLi to RCE in Fortinet FortiWeb (watchTowr Labs)](https://labs.watchtowr.com/pre-auth-sql-injection-to-rce-fortinet-fortiweb-fabric-connector-cve-2025-25257/) +- [MySQL Full-Text Search – Boolean mode](https://dev.mysql.com/doc/refman/8.4/en/fulltext-boolean.html) +- [MySQL Full-Text Search – Overview](https://dev.mysql.com/doc/refman/8.4/en/fulltext-search.html) +- [MySQL REGEXP documentation](https://dev.mysql.com/doc/refman/8.4/en/regexp.html) +- [ReDisclosure: New technique for exploiting Full-Text Search in MySQL (myBB case study)](https://exploit.az/posts/wor/) {{#include ../../../banners/hacktricks-training.md}} - - -