6 minute read

I was scrolling through the extensive list of WordPress plugins while being hungry, and there it was. My next snack would be the ReDi Restaurant Reservation plugin, which would hopefully still my security research hunger. In the following write-up, I’ll explain a vulnerability that I found in the ReDi Restaurant Reservation WordPress plugin CVE-2021-24299. This vulnerability allows an unauthenticated attacker to store malicious JavaScript code into the external ‘upcoming reservations’ webpage where restaurant reservations of the plugin are listed.

ReDi Restaurant Reservation is a WordPress plugin that provides a restaurant reservation system, where users can make and edit reservations. The admin of the website where the plugin is installed can look at overviews of reservations made. The plugin also provides an external website that the website owner can visit, from a tablet, to view and edit all the reservations for that day. ReDi Restaurant Reservation version 21.0307 and earlier contains an Unauthenticated Stored Cross-Site Scripting (XSS) vulnerability.

How the vulnerability works

Let’s have a bite of this snack! How does this vulnerability work? The plugin provides users the functionality to book a reservation for the restaurant. A user just has to visit the reservation page; it looks like this.

Figure 01. The reservation form - Step 1: Select date and time
Figure 01. The reservation form - Step 1: Select date and time
Figure 02. The reservation form - Step 2: Provide reservation details
Figure 02. The reservation form - Step 2: Provide reservation details

When the reservation form is submitted, a POST request is received by the website. The PHP code below shows how the data in the POST request is processed and saved to local variables. The code shows data is not sanitized or validated before being saved to the variables. However, the UserComments string length is capped at 250 characters.

$comment .= mb_substr(self::GetPost('UserComments', ''), 0, 250);

$params = array(
    'reservation' => array(
        'StartTime' => $startTimeISO,
        'EndTime' => $endTimeISO,
        'Quantity' => $persons,
        'UserName' => self::GetPost('UserName'),
        'UserEmail' => self::GetPost('UserEmail'),
        'UserComments' => $comment,
        'UserPhone' => self::GetPost('UserPhone'),
        'UserProfileUrl' => $user_profile_image,
        'Name' => 'Person',
        'Lang' => str_replace('_', '-', self::GetPost('lang')),
        'CurrentTime' => $currentTimeISO,
        'Version' => $this->version,
        'PrePayment' => 'false',
        'Source' => 'HOMEPAGE'
    )
);

Next, the saved variables are pushed to the database. Also note here, that variables are not sanitized or validated before being pushed to the database. This means the strings we submit through the form for the variables UserName, UserPhone, UserEmail and UserComments will be saved to the database without changes.

function save_reservation($params, $reservation)
{
    if (isset($reservation['Error']))
    {
        return;
    }

    global $wpdb;
    
    $reservParams = $params['reservation'];

    $wpdb->insert( $this->table_name, [ 
        'reservation_number' => $reservation['ID'],
        'name'               => $reservParams['UserName'],
        'phone'              => $reservParams['UserPhone'],
        'email'              => $reservParams['UserEmail'],
        'date_from'          => $reservParams['StartTime'],
        'date_to'            => $reservParams['EndTime'],
        'guests'             => $reservParams['Quantity'],
        'comments'           => $reservParams['UserComments'],                 
        'prepayment'         => $reservParams['PrePayment'],                   
        'currenttime'        => $reservParams['CurrentTime'],                   
        'language'           => $reservParams['Lang']   
    ] );

Let’s see how we can exploit this vulnerability. We submit a reservation including the following JavaScript payload.

<script>alert("XSS")</script>

This payload, when executed, will create an alert in the browser containing the text ‘XSS’. However, between the script tags, you can put any JavaScript code. We fill in the form, in the ‘Comment’ field we type our JavaScript payload.

Figure 03. The reservation form – Step 3: Submit form including JavaScript payload
Figure 03. The reservation form – Step 3: Submit form including JavaScript payload

We see the following success alert:

Figure 04. The reservation form – Step 3: We created a reservation, including our JavaScript payload as comment
Figure 04. The reservation form – Step 3: We created a reservation, including our JavaScript payload as comment

So we submitted the reservation form to the website. How are the submitted variables passing through the code?

When we submitted the form as shown above, a POST request is sent to the server. The PHP code gets the data from the POST request by using GetPost(‘variable’). Below you can see how the variables are set with the data we submitted.

$comment .= '<script>alert("XSS")</script>'

$params = array(
    'reservation' => array(
        'StartTime' => $startTimeISO,
        'EndTime' => $endTimeISO,
        'Quantity' => $persons,
        'UserName' => 'Test',
        'UserEmail' => 'test@test.com',
        'UserComments' => '<script>alert("XSS")</script>',
        'UserPhone' => '06123456789',
        'UserProfileUrl' => $user_profile_image,
        'Name' => 'Person',
        'Lang' => str_replace('_', '-', 'en',
        'CurrentTime' => $currentTimeISO,
        'Version' => $this->version,
        'PrePayment' => 'false',
        'Source' => 'HOMEPAGE'
    )
);

Next, the $params array variable gets pushed to the database. Below you can see how the variables are pushed to the database.

function save_reservation($params, $reservation)
{
    if (isset($reservation['Error']))
    {
        return;
    }

    global $wpdb;
    
    $reservParams = $params['reservation'];

    $wpdb->insert( $this->table_name, [ 
        'reservation_number' => '001',
        'name'               => 'Test',
        'phone'              => '06123456789',
        'email'              => 'test@test.com',
        'date_from'          => '14-04-2021',
        'date_to'            => '14-04-2021',
        'guests'             => '2',
        'comments'           => '<script>alert("XSS")</script>',                 
        'prepayment'         => 'true',                   
        'currenttime'        => '20:20',                   
        'language'           => 'en'   
    ] );

So, our JavaScript payload is saved to the database. The next question is, how is the payload loaded into the webpage?

The ReDi Restaurant Reservation plugin’s admin menu has a submenu called ‘Upcoming (Tablet PC)’. This is a webpage where you can view the reservations made for a specific time period. This page isn’t a WordPress webpage, but an external page that is loaded within an iframe, as can be seen in the PHP code.

function redi_restaurant_admin_upcoming()
     {
         $iframe_url = '//upcoming.reservationdiary.eu/Entry/' . $this->ApiKey;
         require_once(REDI_RESTAURANT_TEMPLATE . 'iframe.php');
     }

The url that is loaded within the iframe takes the url https://upcoming.reservationdiary.eu/Entry/ and appends it with the API-key that is registered in your ReDi Restaurant Reservation plugin. When visiting this url, it shows all the made reservations for a specific time period.

The code of the website loaded within an iframe upcoming.reservationdiary.eu isn’t part of the source code of the plugin. Therefore, I can’t show you in detail how the loading of the reservation works on the webpage. However, when the https://upcoming.reservationdiary.eu/Entry/[API-key]/ is loaded, and you click on ‘View upcoming reservations’ the JavaScript payload is executed twice.

Figure 05. Executing the stored payload – Step 4: The payload is executed when https://upcoming.reservationdiary.eu/Entry/[API-key] is loaded
Figure 05. Executing the stored payload – Step 4: The payload is executed when https://upcoming.reservationdiary.eu/Entry/[API-key] is loaded

Impact

The unauthenticated stored XSS vulnerability in ReDi Restaurant Reservation version 20.0307 poses a risk for website owners who use this plugin. A malicious attacker can make a reservation which includes malicious JavaScript code in the comment field. When the website owner visits the ‘Upcoming (Tablet PC)’ submenu or https://upcoming.reservationdiary.eu/Entry/[API-key]/ and clicks on the ‘View upcoming reservations’ button, the JavaScript payload executes twice.

This makes it possible for malicious attackers to for example steal the plugin API-key and potentially steal information about customers that made reservations, steal cookies or other sensitive data.

Proof of Concept

Video 01. Proof of Concept video of unauthenticated stored XSS in ReDi Restaurant Reservation version 20.0307

Conclusion

ReDi Restaurant Reservations 20.0307 and earlier versions contains an unauthenticated stored XSS vulnerability. This makes it possible store malicious JavaScript code into the external ‘upcoming reservations’ webpage where reservations are listed.

The vulnerability exists because the ‘Comment’ message field of the reservation form does not sanitize and validate user input, therefore code can be saved to the database. In addition, the ‘Comment’ field string that is saved in the database is loaded into the webpage without special characters (such as <, >, &, “, and ‘) being converted to HTML entities, which leads to the code being loaded into the webpage. Therefore, the code will be loaded and executed into the webpage where made reservations are listed.

Timeline

15 April, 2021: WPScan – Vendor contacted
21 April, 2021: WPScan – Escalated to WordPress
25 April, 2021: Vulnerability fixed by vendor
06 May, 2021: WPScan notified me that the disclosure is delayed due to the severity of the vulnerability
10 May, 2021: Disclosure on WPScan and CVE-2021-24299 assigned
23 May, 2021: Proof of Concept disclosure

References

https://wpscan.com/vulnerability/fd6ce00b-8c5f-4180-b648-f47b37303670
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-24299
https://wordpress.org/plugins/redi-restaurant-reservation/
https://www.exploit-db.com/exploits/49903

Updated: