WordPress Security Reflections

I've been into WordPress security for many years, but 2024 was the first year I dedicated a significant amount of time to WordPress vulnerability hunting. As a result, I found 496 vulnerabilities and was rated the #1 hacker by number of found vulnerabilities, and #8 by number of websites protected, according to Wordfence (you can read the full paper here).

I thought it would be a good exercise to try to put together some takeaways from all these security findings, because behind every bug there is something to learn—and with many bugs, it's easier to connect the dots and find red lines.

Always look into the obvious

woocommerce_api_* was a WordPress hook registered by WooCommerce to handle instant payment notifications (IPN). A developer would create something like woocommerce_api_callback_name tied to a callback function to POST some payment data and update the order status. It would be the perfect entry point for an injection, because most of the time the code would run unauthenticated.

Too good to be true, but I was able to find 3 unauthenticated SQL injections in that very piece of code (CVE-2024-0610, CVE-2024-0705, one undisclosed).

Leverage the power of second-order injection

When an attacker is able to deposit some dangerous payload into the database, even if it seems harmless at first, there may always be another process handling the data and triggering the exploit. That was the case of CVE-2024-7419 (WP All Export Pro - Unauthenticated Remote Code Execution), where an attacker could simply create an order using PHP code in a standard checkout field (e.g. address, name), and the code would be executed later when exported by the export process.

Creative exploits = many vulnerabilities

I found a quite creative way to exfiltrate private/restricted WordPress content by leveraging the search function in the WP REST API. It was a progressive char-by-char brute force that could exfiltrate any content in just a few seconds, and all the content restriction plugins were found vulnerable!

Creativity in hacking pays off sometimes, and here is the simple exploit I was using: https://github.com/francescocarlucci/wp-content-exfiltrator

Don't trust the Core

The WordPress core is heavily overrated, but it contains many weaknesses. maybe_unserialize() is very prone to PHP object injection. I even proposed a fix for it, which will most probably be ignored. As a result, we have hundreds of PHP object injections in plugins that do not validate inputs passed to maybe_unserialize().

wp_safe_remote_* is not as safe as it was supposed to be, as I explained in another blog post. As a result, we had tons of SSRF in popular plugins like Gravity Forms (CVE-2024-13845), luckily admin-exploitable most of the time.

And yet, the WordPress Core bundles a fork of "PhpConcept Library - Zip Module 2.8.2", which is outdated and vulnerable to zip-slip attacks. As a result, if a developer is doing something like:

$archive = new PclZip($file);
$archive_files = $archive->extract(...);

He would be introducing a very bad and dangerous vulnerability (if $file is not previously sanitized)! I would have more examples, but this blog post is not a rant against the WP Core—it's just stating that the core is absolutely not a security-first product.

Filters can be dangerous too

Hooks and filters are two very important coding features, and one of the main reasons the core, themes, and plugins are so easy to extend. But there are filters that should be handled with white gloves.

Using wp_kses_allowed_html to whitelist a tag in the HTML validator means opening up an XSS door site-wide (see: CVE-2024-9184).

Using http_request_host_is_external improperly means disabling WordPress of all its SSRF protection defenses, as all hosts will be considered external—even the internal ones (see: CVE-2024-13924)!

The fact that some filters impact the whole application is a powerful attack vector, often underestimated by developers.

Look for the back door

When I discovered an XSS vulnerability in ACF, there was a defense mechanism that deregistered the native WP custom fields in favor of the ACF interface, which was secured. Still, I was able to deposit my payload via the XML-RPC interface.

Same goes for the dozens of Author+ SVG upload XSS vulnerabilities I reported. The WordPress media uploader was secured, but media_handle_sideload() was not. When the main door is closed, always go look for back doors!

Low impact * 3 = site takeover

During a private WordPress security audit, I found an IDOR allowing unauthenticated attackers to write anything in a custom post meta—no impact so far. Down the road, I discovered that the meta was deserialized by another function... interesting, but still no demonstrable impact. By analyzing dependencies, I discovered one vendor who had a POP chain in one of these functions—boom!

I know, this sounds very technical, but the bottom line is that 3 individual no-impact vulnerabilities can be chained together to achieve maximum impact and full site takeover!

Nowadays, POP chains are still not considered vulnerabilities by themselves—don’t ask me why.

Conclusion

Hacking on WordPress has been fun and fulfilling in 2024, and a great learning experience in how to write secure code for WordPress.

The more I am in the field, the more I believe that writing a feature to "make it work" and writing a secure piece of code are two different tasks. Security comes from mindset, and being focused only on the outcome almost always makes you forget that one validation, that one sanitization, that one open door that is all a hacker needs to bring your website down, or steal your data, or steal your money, or weaponize your server and so on.

I probably won't do any WordPress bug bounty in 2025, because private projects are currently taking all my time, and my focus is elsewhere at the moment.

In the end, this is the very root of the hacker mindset: stay curious, keep learning ;)

Francesco