A "New" Bug - PHP Object Injection via Insecure Instantiation

Published 12 August 2022
Updated 24 July 2023
Robert Rowley
Author at Patchstack
Table of Contents

Recently, I learned something new. A new twist on a security bug in PHP that I am already familiar with: PHP Object Injection. What was new, was this security bug can be found when code uses "new" to create an object or class in PHP, and gets passed the Class name from user input.

Today, I will share some details about the vulnerability, how I scanned the WordPress plugin repository for instances of it, and what I found and patched.

A new security bug emerges

This new to me knowledge (or knowledge of insecure usages of new) works similar to PHP Object Injection security bugs. "Insecure Deserialize" security bugs are something I've spoken about on Patchstack Weekly. But, instead of insecure usage of deserialize(), this security bug is present when you have insecure usage of new when creating or instantiating new objects in PHP.

Insecure instantiation security bugs require similar conditions to insecure deserialize bugs. A "POP chain" or "Gadget chain" must already exist in the code-base that allows the attacker to leverage the security bug as an attack vector.

What are these POP or Gadget chains? They are just a fancy way of saying an existing class defined in the code base. An important detail is this existing class must have a "Magic Method" which can be taken advantage of to perform an attack. This is a huge limitation of Object Injection related security bugs, without the POP or Gadget chain, the vulnerability poses no risk.

Some developers take a stance that lower severity or complex attack requirements is enough to argue with security reporters that the security bug doesn't deserve a patch, or can be treated as informational only. I disagree.

Every security bug, even low-risk low-severity bugs, deserves a patch. Every security bug is a chance to practice triage, sensitive patch pushing, and security communication skills. Every bug patched is an opportunity to let your users know you have a mature security model, and even shows that only low-risk or highly-unlikely security bugs are the only thing that gets reported against the project (and they still get patched).

I will share one plugin that does this exceptionally well later on in this post. First, I need to explain the security bug and its severity further.

What is insecure instantiation?

Insecure instantiation is a security bug, and an easy one to spot too. Whenever a developer wishes to create a new class or object in PHP, they use:

$foo = new className;

The code will create new object based on a class defined elsewhere in the code, during that object creation process (when instantiated) it will automatically run a few of the class's magic methods.

Object injection via insecure instantiation security bugs are present whenever the code base users a variable that is *controlled by the user* when creating a new object.

Here is an example of what it looks like:

$foo = new $_POST['className'];

The risk should already be apparent. This allows the browser (or client) to choose the name of what class or object available is being created! The user controls the value of $_POST['className'].

Some developers try to downplay this risk, citing they do not have any insecure classes in their code-base to abuse. However, their end user's website might. WordPress websites commonly install multiple plugins, any of those plugins could introduce a class that could be used in a PHP Object Injection attack.

The responsibility to patch these bug is ultimately up to the developer who is allowing their code to create objects based on user input. Only they can patch that bit of code.

So I looked

Now that I had an understanding of the risk and what to look for. It was time to start looking.

I did a simple static code analysis (e.g.. I used grep) of a local copy of the WordPress.org plugin repository. Immediately, I found examples of insecure instantiation, mostly in closed and abandoned projects. So I kept looking …

In no time at all, I struck bug bounty "gold". Two popular plugins appeared to have clear cut examples of PHP Object Injection via insecure instantiation.

Reporting the security bugs

After finding the security bug and confirming it with a working proof of concept. Now came the important step of reporting the issue to Patchstack Alliance and to the vendors.

Here is what the timeline looked like for the first plugin: Easy-Digital-Downloads

2022-07-20 Vulnerability discovered in source code
2022-07-25 Vulnerability validated
2022-07-27 Wrote report and notified vendor
2022-07-27 Vendor pushed the patch in release 3.0.2
2022-07-29 Vendor replied confirming patch was in 3.0.2

Report timeline for Easy Digital Downloads Object Injection

Same-day security bug patches are rare, and this vendor showed they are on the ball and handle security bugs immediately. However, this was nothing compared to the second plugin that patched the security bug before I could even report it!

2022-06-29 Vendor pushed the patch in release 2.21.2
2022-07-20 Vulnerability discovered in source code (against version 2.21.1)
2022-07-27 I failed to produce a Proof of Concept (against version 2.21.4)
2022-07-27 I noticed the vendor patched the bug in 2.21.2 on 2022-06-29 (a month prior!)

Security bug report timeline for GiveWP

Here is what the timeline looked like for the first plugin: GiveWP

What happened? I made a mistake ... I was looking at the source code of an older version of the plugin, but was testing against the current version. I wasted a lot of time trying to attack something that was already patched. When I installed the older version (2.21.1 or earlier) the proof of concept worked as expected, but since it was already patched in 2.21.2 I did not report the old bug (nothing for them to do!)

How did they know? My best guess is the developers were securing that part of the code base against another security bug in the same area of the code base. They probably spotted this lower severity object instantiation bug on their own and quickly wrote defensive code to secure it, handling two security bugs in one commit. The evidence to support this was in the GiveWP's changelogs.

The developers of GiveWP do a great job of communicating security. Reviewing the plugin's changelogs, we can see they spent the last month squashing security bugs left and right. In the end, GiveWP shows us they do not shy away from addressing security issues (big and small), they communicate clearly security releases clearly and the project is worthy of their user's trust.

2.21.3: JULY 7TH, 2022
Security: Protect against CSRF and DOS attacks against the donation stats exporter
Security: Protect against XSS attacks for the currency endpoint

2.21.2: JUNE 29TH, 2022
Security: Updated some internal dependencies that had security fixes
Security: Better protection against invalid dates when exporting donation statistics
Security: Prevent overreach hack when using the exporters

+ more

GiveWP clearly communicating security

I will emphasize again: a changelog with security bug fixes is a great thing to see. Communication is critical when it comes to security bug fixes, so the end users know to update their plugin, theme, or other open source components.

But, not all projects are so open and transparent with their users. Some hide their security bug fixes from their users for their own reasoning, some downplay the risk and don't patch a bug at all!

Thankfully, the Patchstack plugin and App can help site owners easily identify when their sites are running known insecure components.

Bug bounty hunter's TTP.

TTP stands for: Tools, Techniques, and Procedures.

Instant POP Chain

As a bonus, today I will share the TTP for bug bounty hunters to help them detect Object Injection bugs in their own labs! Note, this really does only work in local labs and testing environments, because you must install your own plugins/code to the website.

With object injection security bugs, the POP or Gadget Chain is the hardest part of actual exploitation. It is one of the biggest limiters of the severity of these attacks.

Security bug bounty hunter's goals are to prove the bug's existence, not exploit or compromise sites. So we can take a shortcut.

The risk is in the insecure usage of new or deserialize(). Passing these functions user supplied data is a risk, not the Object or Class used in connection with the attack.

To simplify the bug bounty process and make writing a proof of concept easier for Object Injection attacks, I use the following trick.

I write my own Class. The Class only has one purpose, to show an object was created using it. To do this, I use a magic method (like __construct() or __wakeup() ) that writes to a file (e.g.. generates a log) recording that the object was created. This allows me to show evidence that a possible attack vector can be used to instantiate arbitrary objects available in the code base.

To do this in WordPress I created a new plugin, named "test-object" with the following content.

<?php
/*
Plugin Name: Object Injection Testing - Instant POP Chain
Plugin URI:
Description: Creates an class for security tests
Version: 0.1
Author: Robert R
Licence: GPL2
*/

class testingObject {
  function __construct() {
    $log_file = ‘/tmp/object.log’;
    file_put_contents($log_file, date(‘Y-m-d h:i’).” testingObject created, __construct() called - \n“, FILE_APPEND);
  }
}

Upload and activate the plugin on your test WordPress website and you will have an object to check for when you're doing PHP object injection bug bounty hunting.

OWASP ZAP

My preferred tool for web based bug bounty hunting is OWASP ZAP. It is open source, under the Apache License v2.0, and built for web developers by developers.

OWASP ZAP made it easy for me to create a breakpoint using the extremely helpful ZAP HUD so I could test the proof of concept if this security bug existed without having the handle authentication, nonces, and other aspects required to send the test attack payload.

There are more …

Sharing security bug knowledge makes the world more secure. Sharing information publicly allows security researchers, bug bounty hunters, developers, and any person who identifies as a mixture of the three helps them understand the risk posed.

After reading this post, you should know a little more about PHP Object injection via insecure instantiation or insecure deserialize(). You now know how to spot the issue and can help inform vendors to get them patched.

I took the first pass at the WordPress.org plugin repository in July. I found the most obvious instance of an insecure instantiation security bug, but I have not done a deeper dive. There are potentially more insecure instantiation or insecure deserialize() security bugs unreported and unverified in the repository which has been left for you to find!

Feel free to repeat the process outlined above of static code searching, proof of concept writing, and reporting to practice searching for these security bugs yourself.

If you do find something, please report it via the Patchstack Alliance, and join the many other security researchers, developers, and bug bounty hunters from around the world.

Credits

Thanks to the Patchstack Alliance team for helping with assigning a CVE for the security bug patched in Easy Digital Downloads.

Thank you, developers of Easy Digital Downloads for that extremely fast patch for a lower risk vulnerability. I see you waste no time addressing security issues.

Thanks and appreciation is owed to the GiveWP team for their mature security process. This team gives me something good to say every time I look into them.

Special thanks to Patchstack Alliance team member Yeraisci, even though your earlier security bug findings spoiled my security bug research into GiveWP. All that matters is that your bug reports lead to more bugs being patched!

A final thanks to PTSwarm and Arseniy Sharoglazov for their excellent write-up about Exploiting Arbitrary Object Instantiations in PHP (via an LDAP server of all things), this was the original article that inspired me to look into insecure usages of "new" in the WordPress.org plugin code base.

The latest in Security Advisories

Looks like your browser is blocking our support chat widget. Turn off adblockers and reload the page.
crossmenu