CVE Hunting Made Easy
In just three Sunday afternoons, I discovered 14 CVEs - and you can too! CVE hunting is more accessible than many realise, and the methodology outlined here requires only a bit of coding knowledge.
When most people think of CVE hunting, they picture 14-hour hacking sessions and sifting through binary firmware dumps. Indeed, many of the most impactful vulnerabilities are found this way, but it doesn’t have to be that way.
Vulnerable software is plentiful, and with access to source code, we can automate the discovery of high-impact CVEs by focusing on breadth rather than depth.
Obligatory video mention (warning headphone users).
The Idea
Here was the idea, how many serious security issues can we find just by running some SAST scans on every WordPress plugin?
Let's get cooking.
Downloading All Plugins
Challenge 1: How do we get a list of all plugins and download them reliably?
As it turns out, this was a non-issue. WordPress publishes a public API that provides information about all plugins in their catalog, and it even includes a download link for each plugin. Awesome!
If you want to take a look yourself, check out some of the raw output: WordPress Plugin API.
With a bit of python, we have part of a script that:
- Retrieves the full list of Wordpress Plugins including information like active install count, and the last date it was updated.
- Inserts this information into a database for later use.
- Downloads and unzips each plugin that has been updated within the last 2 years.
Auditing All Plugins (and Staying Organised)
Challenge 2: SAST tools, in general, produce a lot of output. How do I triage and query this efficiently?
I figured the best way to handle all this output was to store it in a SQL database. This way, the results could be easily queried and searched as needed.
The next part of our script:
- Runs Semgrep (unaffiliated) with the 'p/php' ruleset across each plugin.
- Stores the raw Semgrep output in a JSON file in the plugin directory.
- Parses this output and inserts it into a database.
$ python3 wordpress-plugin-audit.py --audit
Auditing plugins: 10%|█████████████▍
This script is published here if you want to follow along:
Now that we have all the output, I set myself a couple of rules for triaging the corpus so I wouldn't end up spending too much time on this project:
- I only looked at the output for LFI (Local File Inclusion) and SQL injection rules.
- The plugin must have a active install base greater than 0.
- Vulnerabilities only exploitable by administrators will be ignored.
- A maximum of 5 minutes will be spent triaging each Semgrep finding that looked interesting.
- If something seems possibly vulnerable, I’ll spend a maximum of 15 minutes attempting exploitation.
Triaging Output
With our database of findings in place, we can easily search for specific types of vulnerabilities and sort them by how many installations they affect - all with a SQL query.
This results in rows of rows of interesting output.
But just because a line of code looks like it could be vulnerable doesn’t mean it actually is. Our next step is to double-check that the issues flagged by Semgrep are genuinely exploitable.
What we're effectively doing here is Sink -> Source code review.
Semgrep is identifying potentially vulnerable sinks, however there's no guarantee that these weak points are actually exploitable. The code might be safe for a couple of reasons:
- Input Sanitisation: User inputs might be cleaned up or filtered before they even reach the sink, making the finding a false alarm.
- Code Artifacts: The code might not even be used anymore. It could just be remnants from development with no real connection to any user inputs.
Attempting Exploitation
Once we’ve identified a plugin that looks like it might be exploitable, the next step is to validate it through actual exploitation.
WPScan's Wordpress test bench makes this step easier.
Once setup, plugins can be installed and activated using their slug name.
$ ddev wp plugin install woocommerce --activate-network
If we’ve done our triage right, this step is mostly about figuring out how to use the plugin to hit the vulnerability source.
Sometimes, you’ll need to dig around the plugin menus to figure out how certain features can be enabled. Other times, the vulnerabilities might be directly accessible through the WordPress REST API or AJAX interface, which can make things a bit easier.
*A full worked example is provided at the end.
The Findings
So, what did we uncover just by triaging the Semgrep results? Here’s the full list of CVEs disclosed to the WPScan CNA.
Unauthenticated .php LFI/Exeuction
- ultimate-classified-listings v1.2 - CVE-2024-5882
- news-element v1.0.4 - CVE-2024-6459
- ymc-smart-filter v2.8.29 - CVE-2024-6164
- tradedoubler-affiliate-tracker v2.0.21 - CVE-2024-6460
Authenticated .php LFI/Execution
- still coordinating disclosure - CVE-2024-6228
Unauthenticated SQLi
- payplus-payment-gateway v6.6.8 - CVE-2024-6205
- wpstickybar-sticky-bar-sticky-header v2.1.0 - CVE-2024-5765
- push-notification-for-post-and-buddypress v1.8.7 - CVE-2024-6159
- cz-loan-management v1.1 - CVE-2024-5975
- simple-media-directory v1.4.1 - CVE-2024-6809
- truebooker-appointment-booking v1.0.0 - CVE-2024-6924
- viral-signup v2.1 - CVE-2024-6926
- opti-marketing v2.0.9 - CVE-2024-6928
Authenticated SQLi
- quiz-master-next v9.0.1 - CVE-2024-5606
Takeaways
This was a fun project with a few takeaway learnings:
- Unsurprisingly, most of plugins we disclosed issues for had a small install base, around 200-2,000 users, with the largest being quiz-master-next, which had over 40,000 installs at the time of writing.
- There's more than one way to go CVE hunting. Usually, the focus is on depth—picking one product or codebase and reverse-engineering it thoroughly. Here, we took the opposite approach, going for breadth rather than depth.
- The importance of shifting security left. If these plugin developers had run Semgrep or any other SAST tools on their own plugins, they likely would have found the same vulnerabilities I did.
- If you're a pentester working on a WordPress site in your next test, take a closer look at the plugins installed on your customer's site. Just because there aren't any disclosed issues doesn't mean there aren't any easily discoverable vulnerabilities waiting to be found.
- This method is definitely replicable (also why stop at Wordpress plugins). I didn’t spend a lot of time on each plugin, nor did I look at every vulnerability class, so there’s undoubtedly stuff I missed.
- Do you want to give it a go? The full SQL dataset is published here.
- If there's enough interest, I can keep my computer busy and publish a new dataset every other month.
- Do you want to give it a go? The full SQL dataset is published here.
Worked Example
push-notification-for-post-and-buddypress <=1.93 SQLi
Scrolling through the corpus, this particular line caught my eye.
A PHP variable $onesignal_externalid
is inserted directly into a SQL query.
e.g. Your input ' or 1=1 becomes \' or 1=1.
Opening the plugin's source code we can quickly spot the vulnerable Sink that Semgrep has highlighted and the Source for the variable.
Although it appears that the user input is sanitised using sanitize_text_field, a quick trip the developer docs shows that this function looks like it's intended to protect against XSS and won't help with preventing SQLi.
Triage complete. ✅
Let's install the plugin and figure out how to call this code and exploit it.
Searching through the code base, we can spot that the affected file is included as a part of this function - PNFPB_icpushcallback_callback.
Tracing further upstream we find that this callback function can be reached via Wordpress's AJAX interface and to top it off, this can be reached without authentication!
Now with what we know we can craft a simple POC to add a 1 second sleep to the SQL query while providing the relevant parameters to reach our desired code path.