In the recent Patchstack Alliance CTF S01E01, I am thrilled to share that I earned 2nd place and successfully solved all the challenges presented. Below is my detailed write-up of each challenge:
In this challenge, we will exploit insecure deserialization in a WordPress environment. The challenge itself uses a custom plugin named "simple-donate-plugin" that is vulnerable to deserialization.
The interesting part about this challenge is that it uses Faker, which has an intriguing __call
method. This method is invoked when a method is not found within a class, triggering the __call
method. We will delve into this further in the write-up below.
If we take a look at the source code, we will see that the application itself stores our meta_value
using an update query:
...snip...
$anonymous = get_user_meta($user_id, 'anonymous', true);
...snip...
$is_anonymous = isset($_POST['is_anonymous']) ? $_POST['is_anonymous'] : 0;
...snip...
if ($existing_meta_key) {
// Update the existing meta value if the key exists
$wpdb->update(
$wpdb->usermeta,
['meta_value' => stripslashes_deep($is_anonymous)],
['meta_key' => $existing_meta_key]
);
...snip...
This approach isn't secure because storing data in meta_value
without using the update_user_meta
function is risky. When get_user_meta
is called, the data will get deserialized, potentially exposing the application to a deserialization exploit that can lead to remote code execution (RCE) in this case.
To gain deserialization, we need to supply the $is_anonymous
function with our deserialization payload. This gets interesting because researching the deserialization gadget requires the thoroughness of a real "Cyber Security Researcher". TL;DR, I found the gadget to gain RCE. Here's the flow:
By searching for the possible gadget in the vendor, I found a gadget chain involving a magic method __call
that has call_user_func
in it. This __call
magic method is invoked when a method is not found within a class. For example, $class->notfound
will trigger the __call
method if the method notfound
doesn't exist in the class. Here, we use a gadget from fakerphp
:
After identifying where we can gain RCE from the class mentioned above, we need a way to actually invoke it. I used the __destruct
magic method to trigger it when the class gets destructed, as shown below: