Menu

Finding vulnerabilities in WordPress: how it’s actually done

7 min read Manuel

WordPress powers an enormous slice of the web, and that makes it irresistible for anyone hunting for vulnerabilities. Not for a technical reason, but an economic one: a bug in a plugin with two million installations is worth more than a bug in software running on three servers.

The point almost everyone gets wrong is this: vulnerabilities in WordPress are not looked for "inside WordPress". The core is written by a team that audits it continuously, has a dedicated security page, and ships regular patches. The fertile ground is elsewhere.

Where you actually look for problems

Three targets, in order of profitability for the attacker:

Plugins. This is where the risk concentrates. A plugin is written by one person, often a single one, who does not perceive the plugin as an attack surface and drops in a form that calls $_GET and passes it to a query. The core has two hundred developers and a queue of reports; a plugin has one developer and none.

Themes. Less widespread than plugins, often sold for a few euros, often sold and then abandoned. A "nulled" theme — that is, bought and cracked — is already a vector: it often contains backdoors added by whoever pirated it, not by the original theme.

Multisite and plugins that touch authentication. Here the payoff is high because the component sits in a privileged position. A plugin that manages logins, permissions or roles has access to things a gallery plugin does not.

The workflow, in order

Serious work has steps, and skipping them is the reason most people find nothing.

1. Inventory: knowing what is inside

Before any attack, know what is running. The number of installed plugins is the best predictor of risk: a site with 82 plugins is a site with 82 possible vectors.

The fastest way to take inventory is to look at the page source and the licence file every theme leaves behind.

# Estrae il percorso dei temi dagli URL dei fogli di stile
curl -s https://target.example | grep -oE '/wp-content/themes/[^/]+' | sort -u

# Cerca i nomi dei file di licenza dei plugin, uno per riga
curl -s https://target.example | grep -oE '/wp-content/plugins/[^/]+' | sort -u

The reason this works is that WordPress prints the paths of themes and plugins in cleartext in the source. It is not a vulnerability: it is the CMS telling you where it keeps its files. But it exposes the complete inventory.

2. The version inventory

Knowing which version is installed turns the work from exploration into research. If you know the plugin is at 1.2.3, you can look for the 1.2.4 patch and read the changelog: what was fixed in 1.2.4 is the vulnerability you are looking for.

# Legge la versione dichiarata nell'intestazione di un plugin
curl -s https://target.example/wp-content/plugins/EXAMPLE/example.php \
  | head -20 | grep -i version

# Elenco dei plugin con le versioni, se il sito espone rest-api
curl -s "https://target.example/wp-json/wp/v2/plugins" | head -40

The second command almost always returns 401 or 404 on a healthy site, but it is worth trying: on some setups it stays exposed through a configuration error, and that is a breach in itself.

3. Patch diffs: the most profitable method

This is the trick almost nobody knows, and it is the most effective.

When a vendor ships a security patch, the diff between the vulnerable version and the fixed one is the exact description of the vulnerability. You do not have to guess: the vendor does the explaining.

The flow is:

  1. Download the declared version and the next one from a mirror such as WordPress.org
  2. Run the diff
  3. What changed is the weak point
  4. If the fix adds escaping or a permission check, you have found the hole
# Scarica due versioni di un plugin e confrontale
mkdir -p ~/wp-audit && cd ~/wp-audit
svn co https://plugins.svn.wordpress.org/EXAMPLE/tags/1.2.3 v123
svn co https://plugins.svn.wordpress.org/EXAMPLE/tags/1.2.4 v124

# Il diff è la vulnerabilità, in chiaro
diff -ru v123 v124 --exclude='*.min.*' --exclude='readme.txt' > patch.diff
wc -l patch.diff

This also works on abandoned plugins, which is the worst case: if the vendor no longer ships patches, there is no diff to read, and the bug is still there.

4. Reading code in the right places

When the diff is not enough, you read the code at the points where user input comes close to a query or a system command.

The three patterns to look for, in order of danger:

Unvalidated input in a query — SQL injection:

// pericoloso: $id arriva da $_GET e finisce in una query grezza
$id = $_GET['id'];
$row = $wpdb->get_row("SELECT * FROM wp_posts WHERE ID = $id");

// il controllo minimo: forzare un intero
$id = absint($_GET['id']);
$row = $wpdb->get_row("SELECT * FROM wp_posts WHERE ID = %d", $id);

Input in a shell command — command injection:

// pericoloso: nome del file in una shell
$file = $_GET['file'];
system("convert " . $file . " out.png");

// sicuro: escapeshellarg() mette il valore tra apici e lo neutralizza
system("convert " . escapeshellarg($file) . " out.png");

Input in an include or a require — LFI, and often RCE:

// pericoloso: l'utente sceglie il file da includere
$lang = $_GET['lang'];
include("lang/$lang.php");

// l'attaccante passa ../../wp-config.php e legge le credenziali

These three patterns cover most of the vulnerabilities found in plugins out in the wild. They are not a John the Baptist: they are the same structure, repeated.

Defence: the checklist that actually matters

If you run a WordPress site, these five things remove almost all of the risk, in order of effectiveness:

Fewer plugins, better. Every plugin you do not use is a plugin you will never patch. "Child" themes are almost always free and work well.

Update within a week of the patch being released. The average exploitation window after a CVE is published on a popular plugin is measured in days, not months. Whoever automates updates wins.

A WAF in front of the site. Wordfence or Cloudflare filter malformed requests before they reach PHP. They do not replace the patch, but they cut the noise down to something readable.

Fewer privileges. The database user must not be able to create tables, and the FTP user must not be able to execute PHP code. These are two limits which, if breached, widen the surface instead of keeping it tight.

Logs you actually read. The log file is the only defence that works afterwards. Many sites have a single login, from 2019, and nobody noticed.

Two notes on ethics, since the subject is sensitive

This article describes how vulnerabilities are hunted, and it is meant for those who have to fix them. Two things remain true regardless of how your day went.

The first: without written authorisation, hunting for vulnerabilities in a system that is not yours is a crime. It is not an ethical discussion, it is the Criminal Code. This holds in Italy as in every other country, and "I was only looking" exceptions do not exist. If you find a flaw on someone else's site, you report it to the owner and that is it.

The second, which holds for anyone who actually works in the field: the difference between someone who is good and someone who is useful is not technique, it is transparency. Whoever hires you wants to know what you look at, why, and what you will do when you find something. A pentester who cannot say "I have access to the database" is not discreet, they are just good at hiding. The relationship with the client is built on that, and it is often the only thing that separates a tester from a criminal.

If you want to do this work for real, the path does not go through an exploit found by chance: it goes through understanding how a system works from the inside, through reading someone else's code carefully, and through accepting that the real work is often an email with a clear description of a problem. That, and not the screenshot of the score, is what makes you credible.


TAG: #ai #sql #security