

Second-Order SQL Injection in Gnuboard4: Variable Pollution Meets Identifier Injection
When we talk about SQL injection, most folks picture a simple quote escape gone wrong in a value. But some of the most dangerous exploits happen in less obvious places—like identifiers (table names) and across multiple steps of request handling. In this post, we’ll walk through a second-order SQL injection in Gnuboard4’s bbs/search.php
that’s enabled by variable pollution and executed as an identifier injection in the FROM
clause.
TL;DR
- The array
search_table
can be polluted via request parameters. - Later, the polluted value is used as a table name without whitelist validation or quoting.
- This enables SQL injection in the identifier position, which typical escaping can’t fix.
- Impact: error-based and blind data exfiltration, JOIN injection, query truncation.
Affected Code
The vulnerable usage occurs when composing the FROM
clause from $search_table[$idx]
:
$tmp_write_table = $g4[write_prefix] . $search_table[$idx]; |
A similar pattern exists in the counting query:
$tmp_write_table = $g4[write_prefix] . $g4_search[tables][$i]; |
In legacy PHP stacks, request parameters are often extracted into same-named variables. That means an attacker can directly post search_table[1]=...
and table_index=1
, overwriting the server-side array and steering the subsequent query.
Why This Is Second-Order Injection
- First order: the attacker “poisons” server state by overriding
search_table[]
andtable_index
via POST. - Second order: the poisoned value is later treated as a table identifier in
FROM
, where normal string escaping is ineffective. Because the position is an identifier (not a quoted value), this allows injection of JOINs, ORDER BY, and even comment-based truncation.
Proof of Concept (POST)
Replace <host>
with your target host. To improve stability, replace z2_0
with a board suffix that exists in your environment (e.g., free
, notice
) so g4_write_<suffix>
actually exists.
POST /gnuboard4/bbs/search.php HTTP/1.1 |
What happens:
$tmp_write_table
becomesg4_write_z2_0 order by updatexml(...)#
#
comments out the rest of the query (WHERE ... ORDER BY wr_id ... LIMIT ...
)updatexml()
raises a controlled error embedding the subquery result—classic error-based exfiltration.
Even if error messages are suppressed, the same primitive can be adapted to time-based (blind) injection.
Impact
- Exfiltration of sensitive data (e.g., credentials from
g4_member
) via error-based or blind SQLi. - Injection of arbitrary JOINs to expand the result set and bypass intended filters.
- Query logic truncation by injecting comment markers.
Root Causes
- Variable pollution: server trusts same-named request parameters to populate application variables/arrays.
- Identifier injection: unvalidated, unquoted table identifiers are constructed from user-controlled values.
- Lack of defense-in-depth on key control variables (
search_table
,table_index
, etc.).
Minimal, Practical Remediation
- Rebuild sensitive arrays before use to defeat pollution:
// Prevent variable pollution: rebuild server-side state |
- Whitelist and quote identifiers; only allow
[A-Za-z0-9_]
within a bounded length, and wrap with backticks:
// Identifier hardening: whitelist and quote |
- Enforce integer types for indices/paging:
$page = (int)$page ?: 1; |
- Avoid
extract($_REQUEST)
style patterns entirely. If refactoring is not feasible, explicitlyunset
sensitive names from$_REQUEST
before any extraction.
Verification Checklist
- Before fix: observe SQL errors containing
updatexml
andFROM g4_write_... order by ... #
. - After fix: same PoC should neither alter the
FROM
clause nor leak data;search_table[]
andtable_index
from the request must not influence server-side arrays.
Closing Thoughts
Second-order bugs thrive in systems that mix legacy request handling with dynamic SQL generation. Any time a user-controlled value can shape identifiers or query structure—not just values—you need whitelists and strict rebuilding of server state. This case in Gnuboard4 is a compact example of how variable pollution escalates into full SQL injection at the identifier level.
—
If you maintain a similar stack, audit for:
- Any place identifiers are built from variables.
- Any global extraction of request parameters.
- Any “reusable” arrays rebuilt across requests without sanitization.