Hunting for SQL injections (SQLis) and Cross-Site Request Forgeries (CSRFs) in WordPress Plugins
August 2020 by Tenable
This is a detailed overview of the bugs found while reviewing the source code of WordPress plugins. I cover 3 reported vulnerabilities (CVE-2020–5766, CVE-2020–5767 and CVE-2020–5768) which can be exploited for information disclosure and sending forged emails. I also draw attention to the dangers of having a misconfigured WordPress server, which could increase the impact of SQLis and possibly lead to remote code execution.
Recently I have been looking for SQLi vulnerabilities in WordPress plugins. This is a great target to practice bug hunting skills. There is a huge selection of plugins to pick from with a majority being written by third parties, it’s all open source and written in PHP.
WordPress does a great job of providing methods for validating and cleaning data. Some of these have been specifically designed for preventing SQLi attacks. For example, WordPress provides a function called sanitize_sql_orderby which can be used to ensure a string is a valid SQL ‘order by’ clause. WordPress has also gone to the extent of sanitizing user-supplied data from requests. All data that is fetched with $_GET, $_POST, $_REQUEST, $_SERVER and $_COOKIE is first sanitized through a function called wp_magic_quotes that escapes quotes, backslashes and null-byte characters with a backslash. This is done for security purposes and when it comes to SQLi, it makes it hard to inject single quotation marks which are widely used in these attacks.
Despite the efforts made by WordPress developers to minimize vulnerabilities introduced in third party plugins, they cannot guarantee that the developers of those plugins will use the proper functions provided for data sanitization and validation.
CVE-2020–5766: SRS Simple Hits Counter Plugin — Information Disclosure
The following code snippet is from a plugin called SRS Simple Hits Counter.
The srs_simple_hits_counter function sets the $post_id data from a GET request and feeds it directly into the update_view_visitors function where it is concatenated to the WHERE clause of an SQL query.
Here, there is no data sanitization or validation implemented on the user controlled $post_id variable, and therefore this function is vulnerable to SQLi. Since the results of the queries are not returned in the request response, a blind SQLi technique could be used to extract the contents of the WordPress database. Blind SQLi techniques consist of sending queries that could be answered with true or false statements. In this case, I utilized time-based SQLi by injecting a conditional MySQL SLEEP command in the query to allow distinguishing between true and false answers by measuring any delays in the request response.
Note that above the srs_simple_hits_counter function declaration, there are two Ajax handler hooks registered.
The first hook only fires for logged-in users, while the second hook fires for not logged-in users. This means that the srs_simple_hits_counter function can be accessed remotely through an Ajax request without any authentication.
The following request tests this vulnerability by attempting to compare the first character of the administrator’s password hash with the dollar sign character (ASCII code 36).
This request uses a SLEEP command to create a 5 second delay in the response if the hash does begin with the dollar sign character. This request can then be sent over and over, testing all possible characters until the whole password hash is revealed.
Note that the administrator’s username was encoded in ASCII code (97, 100, 109, 105, 110). This is to avoid using single quotes in the request data, which would get escaped with backslashes and cause the query to fail. CVE-2020–5768: Icegram Email Subscribers & Newsletters — Information Disclosure
In the following code snippet taken from a plugin called Email Subscribers & Newsletters, we see a similar issue.
Here, the broadcast_id variable is directly concatenated to the WHERE clause of an SQL statement, without any visible data sanitization. Instead of using $_GET to retrieve the broadcast_id from the user-supplied value ‘list’, a custom function ig_es_get_request_data is used instead. Image for post Image for post
The ig_es_get_request_data function retrieves the request data via $_REQUEST and then processes this through another custom function ig_es_get_data shown below.
The ig_es_get_data function passes the request data through wp_unslash which removes backslashes, and then through ig_es_clean which calls the WordPress sanitize_text_field function stripping all tags away from the request data.
As mentioned earlier, request data fetched with $_REQUEST is pre-sanitized by escaping special characters such as quotes, backslashes and null-bytes with backslashes. The custom function ig_es_get_data fetches the request data with $_REQUEST and then applies wp_unslash to the data which removes the backslashes.
Hence, it reverses the escape sanitization performed on the request data, and allows single quotes to be injected into the query.
This gives us a broader set of commands to inject in the query and allows us to escape from enclosed quotation marks surrounding user controlled variables. Most importantly, none of these functions properly validate or sanitize the contents of broadcast_id against its possible use to inject SQL code into the query.
As per the previous blind SQLi information disclosure found in the SRS Simple Hits Counter plugin, this could be exploited to leak the contents of the WordPress database. However, exploiting this issue requires administrator privileges to access the es-newsletters-settings-callback function. Icegram Email Subscribers & Newsletters — Arbitrary File Write leading to RCE (server misconfiguration)
When setting up a WordPress Windows target, I used the following guide How to Install XAMPP and WordPress Locally on Windows PC. This was the first link that came up on a Google search for the terms ‘install WordPress Windows’.
Without any prior experience with WordPress, I followed the guide, which instructed me to configure the WordPress application to use the ‘root’ MySQL database user as shown below.
This configuration gives the WordPress application the privileges of the MySQL root user. The MySQL root user has the FILE privilege which enables reading and writing files on the server. A crafted SQLi could abuse this privilege to write a malicious PHP script to the htdocs directory, where the script could later be accessed and executed remotely and without authentication.
Please note that configuring WordPress to authenticate to the database as ‘root’ is not recommended by WordPress!
Writing a malicious script could be achieved through the MySQL command INTO OUTFILE, which writes the result of a query into a file. The content of the data written can be controlled by combining the output of the query with a UNION operator.
I sent a crafted request to the server to exploit the same SQLi found in es_newsletters_settings_callback function. This time I was attempting to create a new file in the htdocs directory containing the following PHP script:
<?php system($_GET[“cmd”]); ?>
The ig_es_get_data_function sanitizes the request data through the sanitize_text_field function. This would remove the tags from the PHP script and therefore needs to be avoided by encoding the php script part of the payload in ASCII code.
Finally, since the SQLi vulnerability requires authentication, I have set the exploit to run from a hosted webpage via a CSRF script as shown below.
CSRF attacks target vulnerable websites by reusing cookies stored in the browser of a legitimate logged-in user to impersonate their identity and perform some malicious actions without their knowledge.
Even though the CSRF was successful in creating a file on the affected host through SQLi, this same approach cannot be used to exploit the information disclosure vulnerability discussed earlier. This is due to the Cross-Origin Resource Sharing (CORS) mechanism used in modern web browsers, which blocks response data from cross origin requests. CVE-2020–5767: Icegram Email Subscribers & Newsletters — Sending Forged Emails via a CSRF
Noting the possibility to trigger function calls on the WordPress server via a CSRF, I became curious as to whether I could find any other interesting functions to exploit via CSRFs. Since the CORS mechanism prevented me from reading responses, I searched for actions I could perform without depending on request responses.
Looking around the plugin user interface, I came across an interesting functionality in the settings menu for sending test emails. This functionality can be accessed through an Ajax request as shown below
The comment in the __construct function informs us that the user needs to have settings and campaigns permissions to send a test email. In the send_test_email function we can see that the email recipient, subject and content can be all set through the request.
Here’s a Proof of Concept (PoC) I wrote to test if I could send forged emails to myself from the WebPress email account.
Visiting the above web page while logged in as an administrator caused WordPress to send an email with the subject, content and recipient set by the PoC.
The research found three new vulnerabilities in WordPress plugins.
CVE-2020–5766: A blind SQLi information disclosure vulnerability in SRS Simple Hits Counter
CVE-2020–5767: A CSRF for Sending Arbitrary emails in Email Subscribers & Newsletters
CVE-2020–5768: A blind SQLi information disclosure vulnerability in Email Subscribers & Newsletters
The SQLi vulnerabilities found were limited to information disclosures, however, in the case where the WordPress application is misconfigured to use the ‘root’ MySQL database user, as recommended on many installation guides on the internet, the SQLi vulnerability in Email Subscribers & Newsletters becomes vulnerable to an arbitrary file write vulnerability which could lead to remote code execution. Vulnerability Disclosure
The vulnerabilities found have been reported to the developers through Tenable’s coordinated disclosure process.
The vulnerabilities have now been fixed in:
SRS Simple Hits Counter version 1.1.0
Email Subscribers & Newsletters plugin version 4.5.1.