image.png

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:

Donor

Challenge Information

Understanding the 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.

Reconnaissance

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.

Exploitation

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: