Get Started
Guides April 16, 2026 6 min read

SQL Injection in WordPress: What It Actually Looks Like (And Why $wpdb->prepare() Matters)

SQL injection is one of the most misunderstood vulnerabilities in web security. Here's a concrete, technical look at what it actually looks like in WordPress code - and what stands between your database and an attacker.

SQL Injection WordPress Example. SQL injection has been on the OWASP Top 10 list for well over a decade. It shows up in CVE reports constantly. Security vendors cite it in every annual threat report. And yet most explanations of it are either too abstract to be useful or too academic to be interesting.

This post gives a concrete look at how SQL injection works in a WordPress context. It is not a warning or a story. It is a mechanical walkthrough of the attack. Understanding it at this level changes how you think about plugin updates and database access patterns.

SQL injection WordPress example: What SQL Injection Is, Precisely

A web application takes input from a user (a search term, a username, a product ID) and uses that input to query a database. SQL injection happens when the application does not properly separate the input from the query itself. The input gets interpreted as SQL code rather than data.

That is the precise definition. The implication is significant: when input becomes code, the attacker is no longer constrained to what the application could retrieve. They can query any table, bypass authentication, read credentials, or in some configurations, execute operating system commands.

SQL injection WordPress example: A Vulnerable WordPress Query

WordPress uses the $wpdb global object for database access. Developers can use it to write custom queries. Here is a simplified example of the kind of code that appears in vulnerable plugins:

$search = $_GET['s'];
$results = $wpdb->get_results(
    "SELECT * FROM wp_posts WHERE post_title LIKE '%" . $search . "%'"
);

This looks functional. It takes a search term from the URL, drops it into a query, and returns matching posts. The problem is the direct concatenation. The query takes the value of $search verbatim from the HTTP request and embeds it in SQL syntax without any transformation. The database will execute whatever arrives there as part of the query.

What the Attacker Actually Sends

With the vulnerable query above, consider what happens when an attacker sends this as the search term:

%' UNION SELECT user_login, user_pass, user_email, NULL, NULL FROM wp_users - 

The percent sign and apostrophe close out the original LIKE clause. The UNION SELECT appends a second query that reads from wp_users – WordPress’s user table. The double dash comments out the rest of the original query so it does not break the syntax. The database receives this as a single valid query and returns usernames, password hashes, and email addresses alongside whatever posts matched.

The attacker does not need access to the admin panel. They do not need to guess a password. They need a vulnerable input field and a browser. The query executes with the same database permissions as the WordPress application itself. That typically means read and write access to the entire WordPress database.

Blind Injection: When There’s No Output

Not all injection vulnerabilities return data directly to the page. Blind SQL injection is common in more defensively coded applications that suppress error messages and do not echo results to the browser. The attack still works – it just requires a different technique.

With boolean-based blind injection, the attacker infers database contents by asking true/false questions. A payload like ' AND 1=1 - returns normal results; ' AND 1=2 - returns nothing. The attacker extracts data one bit at a time. They ask whether the first character of the admin password hash is greater than ‘M’, then ‘P’, then ‘S’, converging on the answer through bisection.

Automated tools like SQLMap can run thousands of such requests in seconds. Even a vulnerability that looks unexploitable because it returns no visible output can still be fully exploited against a large database in under an hour.

Time-based blind injection is a variation that does not need response differences. It uses SQL functions like SLEEP() or BENCHMARK() to cause measurable delays when a condition is true. The attacker reads the database through timing signals in the HTTP response latency.

How $wpdb->prepare() Actually Fixes This

WordPress’s built-in defense is $wpdb->prepare(). The secure version of the earlier query looks like this:

$search = '%' . $wpdb->esc_like( $_GET['s'] ) . '%';
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM wp_posts WHERE post_title LIKE %s",
        $search
    )
);

The placeholder %s tells prepare() that this position expects a string. The function escapes the value, wrapping it in quotes and prefixing special characters with backslashes. Whatever the user sends, the database receives it as a literal string value, not as SQL syntax. The UNION SELECT, the comment markers, the closing quotes: all of it gets escaped and treated as a search string rather than a command.

This uses parameterized queries. The query structure and the data remain separate at the protocol level. The database knows what is a query and what is data before it ever touches the input, which is why this defense is robust even against sophisticated bypass techniques.

Where This Fails in Plugins

The pattern breaks down in several predictable places:

Unparameterized ORDER BY clauses. prepare() cannot parameterize column names or SQL keywords, only values. A plugin that builds an ORDER BY clause dynamically from user input (ORDER BY . $_GET['sort']) is vulnerable even if the rest of the query uses prepare(). The fix is an allowlist: only accept specific, hardcoded column names.

Nested queries and string building. Developers sometimes prepare a partial query, then concatenate it with other dynamically built segments before executing. The prepared portion is safe; the concatenated portion is not.

Unsanitized LIKE clauses. prepare() handles SQL injection, but it does not handle LIKE wildcards. An input of % or _ matches everything in a LIKE clause, which may not be a security issue but is a correctness issue. The $wpdb->esc_like() function handles this separately, which is why it appears in the secure example above.

Second-order injection. An application stores data safely in the database after sanitizing it on write, then retrieves it and uses it unsanitized in a later query. The sanitization on write creates a false sense of safety; the injection happens on read.

What’s Actually at Risk in WordPress

The WordPress database contains the wp_users table, which stores usernames and password hashes for every account including admins. A SQL injection vulnerability with SELECT permissions on that table means those hashes are readable. Modern bcrypt hashes are slow to crack, but MD5-based hashes – still used in some legacy WordPress deployments – can be cracked at billions of attempts per second on consumer hardware.

Beyond credentials: wp_options stores site configuration including API keys and plugin license keys. wp_postmeta stores custom field data, which in WooCommerce stores includes order details and billing addresses. wp_usermeta stores user metadata. A plugin with write access (INSERT, UPDATE, DELETE) on a vulnerable query can modify any of this data, not just read it.

In environments where MySQL has FILE privileges and the web server can write to the filesystem, SQL injection can escalate to remote code execution via SELECT INTO OUTFILE. This is uncommon in hardened hosting environments but not rare in shared hosting configurations where those controls do not apply by default.

Recognizing It In the Wild

SQL injection vulnerabilities in published plugins show up in CVE databases regularly. The NVD entry format for WordPress CVEs typically identifies the affected component, the parameter that is injectable, and the authentication requirement. This tells you whether the attack is accessible to unauthenticated visitors or requires at least a subscriber-level account. Unauthenticated SQL injection vulnerabilities get higher ratings (CVSS 9.8 in critical cases) because they require no foothold at all.

When you see a plugin update described as “fixes SQL injection vulnerability in [parameter]” – even a minor-sounding update: that is a patch for something fully exploitable before the fix. The window between a vulnerability disclosure and patching on live sites is where active exploitation happens. Automated scanners probe for known-vulnerable versions within hours of a public disclosure.

The Practical Implication for Site Owners

You do not need to audit plugin source code yourself. What matters is keeping plugins updated and knowing when a security-relevant update exists. Trusti Security’s vulnerability scanner checks your installed plugins against a database of known vulnerabilities. It flags when you run a version with a published CVE, so you have the information before the window closes, not after.

SQL injection is a solved problem at the code level. $wpdb->prepare() exists, it works, and it has been documented in WordPress core for fifteen years. The vulnerabilities that appear in plugins are not mysterious. They are the predictable result of missing a function call or taking a shortcut in dynamic query construction. Understanding the mechanism makes the update notifications make sense. It also clarifies why “I’ll update it next week” is a different calculation than it feels like.

Related Articles