Description
The Easy Digital Downloads WordPress Plugin, versions 3.1.0.2 & 3.1.0.3, is affected by an unauthenticated SQL injection vulnerability in the ’s’ parameter of its ’edd_download_search’ action.
Lab-Setup
mkdir cve-2023-23489
Dockerfile
FROM wordpress:latest
# Install development tools and dependencies
RUN apt-get update && apt-get install -y \
vim \
subversion \
git \
unzip \
mariadb-client \
less \
&& rm -rf /var/lib/apt/lists/*
# Install Xdebug for debugging
RUN pecl install xdebug && docker-php-ext-enable xdebug
# Properly configure Xdebug
RUN echo "zend_extension=xdebug" > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.remote_enable=on" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.remote_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.remote_port=9003" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.log=/tmp/xdebug.log" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
# Set permissions for WordPress files
RUN chown -R www-data:www-data /var/www/html
# Added phpinfo file
COPY phpinfo.php /var/www/html/phpinfo.php
COPY wp-config.php /var/www/html/wp-config.php
# Set working directory
WORKDIR /var/www/html
Note
I got the path of Xdebug Configuration files /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
when the docker container is up i executed this command to get it php --ini
docker-compose.yml
version: "3"
# Defines which compose version to use
services:
db:
image: mysql:5.7
restart: always
environment:
MYSQL_ROOT_PASSWORD: MySQLP@ssw0rd
MYSQL_DATABASE: WordPressDatabaseName
MYSQL_USER: WordPressUser
MYSQL_PASSWORD: P@ssw0rd
ports:
- "3306:3306" # Allow access to MySQL from host (optional)
volumes:
- mysql:/var/lib/mysql # Persist MySQL data
wordpress:
depends_on:
- db
build: .
restart: always
ports:
- "8000:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: WordPressUser
WORDPRESS_DB_PASSWORD: P@ssw0rd
WORDPRESS_DB_NAME: WordPressDatabaseName
volumes:
mysql: # Persistent MySQL data
phpinfo.php
<?php phpinfo(); ?>
I updated the wp-config.php
file to enable debugging
<?php
/**
* The base configuration for WordPress
*
* The wp-config.php creation script uses this file during the installation.
* You don't have to use the website, you can copy this file to "wp-config.php"
* and fill in the values.
*
* This file contains the following configurations:
*
* * Database settings
* * Secret keys
* * Database table prefix
* * ABSPATH
*
* This has been slightly modified (to read environment variables) for use in Docker.
*
* @link https://developer.wordpress.org/advanced-administration/wordpress/wp-config/
*
* @package WordPress
*/
// IMPORTANT: this file needs to stay in-sync with https://github.com/WordPress/WordPress/blob/master/wp-config-sample.php
// (it gets parsed by the upstream wizard in https://github.com/WordPress/WordPress/blob/f27cb65e1ef25d11b535695a660e7282b98eb742/wp-admin/setup-config.php#L356-L392)
// a helper function to lookup "env_FILE", "env", then fallback
if (!function_exists('getenv_docker')) {
// https://github.com/docker-library/wordpress/issues/588 (WP-CLI will load this file 2x)
function getenv_docker($env, $default) {
if ($fileEnv = getenv($env . '_FILE')) {
return rtrim(file_get_contents($fileEnv), "\r\n");
}
else if (($val = getenv($env)) !== false) {
return $val;
}
else {
return $default;
}
}
}
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', getenv_docker('WORDPRESS_DB_NAME', 'wordpress') );
/** Database username */
define( 'DB_USER', getenv_docker('WORDPRESS_DB_USER', 'example username') );
/** Database password */
define( 'DB_PASSWORD', getenv_docker('WORDPRESS_DB_PASSWORD', 'example password') );
/**
* Docker image fallback values above are sourced from the official WordPress installation wizard:
* https://github.com/WordPress/WordPress/blob/1356f6537220ffdc32b9dad2a6cdbe2d010b7a88/wp-admin/setup-config.php#L224-L238
* (However, using "example username" and "example password" in your database is strongly discouraged. Please use strong, random credentials!)
*/
/** Database hostname */
define( 'DB_HOST', getenv_docker('WORDPRESS_DB_HOST', 'mysql') );
/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', getenv_docker('WORDPRESS_DB_CHARSET', 'utf8') );
/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', getenv_docker('WORDPRESS_DB_COLLATE', '') );
/**#@+
* Authentication unique keys and salts.
*
* Change these to different unique phrases! You can generate these using
* the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
*
* You can change these at any point in time to invalidate all existing cookies.
* This will force all users to have to log in again.
*
* @since 2.6.0
*/
define( 'AUTH_KEY', getenv_docker('WORDPRESS_AUTH_KEY', 'f9b373ffa89b6310ea66306e2e256351c2293518') );
define( 'SECURE_AUTH_KEY', getenv_docker('WORDPRESS_SECURE_AUTH_KEY', 'e6b0cac823d4270c0d4240fecaa167579a2a77aa') );
define( 'LOGGED_IN_KEY', getenv_docker('WORDPRESS_LOGGED_IN_KEY', 'c0c276e7ab64e57ad8457844453a354083e271f4') );
define( 'NONCE_KEY', getenv_docker('WORDPRESS_NONCE_KEY', '786d6c04dea080c461b9510402c7231f5ae8e4f1') );
define( 'AUTH_SALT', getenv_docker('WORDPRESS_AUTH_SALT', '88e8eb4fe52c0289e6970de0082b58f7ec9fd3af') );
define( 'SECURE_AUTH_SALT', getenv_docker('WORDPRESS_SECURE_AUTH_SALT', '8dfeb7b37d302374d2a11b2f7267cd17013132d1') );
define( 'LOGGED_IN_SALT', getenv_docker('WORDPRESS_LOGGED_IN_SALT', '8585878381aa816109b8d23b85061b7c6e3b3740') );
define( 'NONCE_SALT', getenv_docker('WORDPRESS_NONCE_SALT', '570b9d081014b1380d940d93501c048aaaa35f5e') );
// (See also https://wordpress.stackexchange.com/a/152905/199287)
/**#@-*/
/**
* WordPress database table prefix.
*
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*
* At the installation time, database tables are created with the specified prefix.
* Changing this value after WordPress is installed will make your site think
* it has not been installed.
*
* @link https://developer.wordpress.org/advanced-administration/wordpress/wp-config/#table-prefix
*/
$table_prefix = getenv_docker('WORDPRESS_TABLE_PREFIX', 'wp_');
/**
* For developers: WordPress debugging mode.
*
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
*
* For information on other constants that can be used for debugging,
* visit the documentation.
*
* @link https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/
*/
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', '/var/www/html/wp-content/debug.log');
define('WP_DEBUG_DISPLAY', false);
@ini_set('log_errors', 1);
@ini_set('display_errors', 0);
/* Add any custom values between this line and the "stop editing" line. */
// If we're behind a proxy server and using HTTPS, we need to alert WordPress of that fact
// see also https://wordpress.org/support/article/administration-over-ssl/#using-a-reverse-proxy
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {
$_SERVER['HTTPS'] = 'on';
}
// (we include this by default because reverse proxying is extremely common in container environments)
if ($configExtra = getenv_docker('WORDPRESS_CONFIG_EXTRA', '')) {
eval($configExtra);
}
/* That's all, stop editing! Happy publishing. */
/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
This are all the created files
Install Xdebug
sudo apt install php-xdebug -y
Find
Xdebug
Configuration files
locate xdebug.ini
Xdebug Configurations
zend_extension = xdebug.so
xdebug.mode = debug
xdebug.start_with_request = trigger
xdebug.client_port = 9003
xdebug.client_host = host.docker.internal
xdebug.log = /tmp/xdebug.log
We will use Visual Studio Code for Debugging the Vulnerability
Install Visual Studio Code
After Installation go to Visual Studio Code Extensions
Install these Extensions
To build the Docker Container
docker-compose up
To see all running containers
When you go to http://localhost:8000/ you will see the wordpress installation page
I searched for
easy digital downloads
Go to Advanced View Section
The Description of the vulnerability says that version 3.1.0.3 is vulnerable we will download both 3.1.0.3 & 3.1.0.4 to see the patch of the vulnerability
First lets upload the vulnerable plugin to wordpress press Browse button and choose easy-digital-downloads.3.1.0.3.zip
Press
Install Now
Button
You will get this error, we need to change the max file size
Open a shell on docker container and execute these commands
echo "upload_max_filesize = 50M
post_max_size = 100M
max_execution_time = 300
memory_limit = 256M" > /usr/local/etc/php/conf.d/custom.ini
Then restart Docker container
If we try to install the plugin again
It will be uploaded successfully, Press the
Activate Plugin
button
The plugin is installed successfully
Open Visual Studio Code and press Remote Explorer
then choose Attackin Current Window
Then go to
Extensions
and Install PHP Debug
on the container will be needed for Xdebug
Choose Open Folder from File
This bar will appear
we need to open the Apache directory where WordPress files are installed which is under
/var/www/html
The content of the container appears
Lets test if the debugger is working by debugging first file phpinfo.php
Go to Run - Start Debugging
Then press on
create a launch.json file
launch.json file will show
I updated the launch.json file to work on the current directory that we opened
/var/www/html
Updated launch.json file :-
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"stopOnEntry": false,
"pathMappings": {"/var/www/html": "${workspaceFolder}"}
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 0,
"runtimeArgs": [
"-dxdebug.start_with_request=yes"
],
"env": {
"XDEBUG_MODE": "debug,develop",
"XDEBUG_CONFIG": "client_port=${port}"
}
},
{
"name": "Launch Built-in web server",
"type": "php",
"request": "launch",
"runtimeArgs": [
"-dxdebug.mode=debug",
"-dxdebug.start_with_request=yes",
"-S",
"localhost:0"
],
"program": "",
"cwd": "${workspaceRoot}",
"port": 9003,
"serverReadyAction": {
"pattern": "Development Server \\(http://localhost:([0-9]+)\\) started",
"uriFormat": "http://localhost:%s",
"action": "openExternally"
}
}
]
}
Then Go to Explorer
open phpinfo.php file and set breakpoint
Go to
Run - Start Debugging
You will see the debugging bar appears
when you visit http://localhost:8000/phpinfo.php/ at browser
You will see that the breakpoint is triggered
Let’s begin the analysis
unzip easy-digital-downloads.3.1.0.3.zip -d easy-digital-downloads.3.1.0.3
unzip easy-digital-downloads.3.1.0.4.zip -d easy-digital-downloads.3.1.0.4
We will use meld
to compare between the original file with the patched one to install Meld sudo apt install meld
From the vulnerability description the vulnerable action is edd_download_search
We will search all PHP files that contains this action edd_download_search
grep -r "edd_download_search" . | grep '\.php'
The
ajax-functions.php
is the file that contains the action edd_download_search
run meld
command
meld
Press File Tab
Press here to choose the first file easy-digital-downloads.3.1.0.3/easy-digital-downloads/includes/ajax-functions.php
Press here to choose the second file
/easy-digital-downloads.3.1.0.4/easy-digital-downloads/includes/ajax-functions.php
The press Compare Button
Check this box to show line numbers of the code
As you can see the variable
$new_search
in the patched version is sanitized
If you open the code in the vulnerable version, you will see the variable $new_search
is passed with GET Method of s
parameter
The variable $new_search
is under function edd_ajax_download_search()
If you searched through the code for edd_ajax_download_search
According to WordPress this is the format of AJAX Actions
do_action("wp_ajax_{$action}")
So:-
wp_ajax_edd_download_search
→ Runsedd_ajax_download_search()
for logged-in users.wp_ajax_nopriv_edd_download_search
→ Runsedd_ajax_download_search()
for non-logged-in users. so if we want to getedd_ajax_download_search
action it will beedd_download_search
so the url should be like that
http://localhost:8000/wp-admin/admin-ajax.php?action=edd_download_search&s={user-input}
We removed the ajax
keyword from edd_ajax_download_search
because WordPress automatically maps AJAX requests using admin-ajax.php
based on add_action()
hooks.This is the default entry point for AJAX requests in WordPress.
Let’s continue analysis
if you search for the variable $new_search
inside the code, you will find it’s passed to an array variable $args
Let’s set a breakpoint at this variable and make the request to ensure it is valid
If you go to the URL
http://localhost:8000/wp-admin/admin-ajax.php?action=edd_download_search&s=5555
You will find that the variable $new_search
value changed to 5555
If you search for variable $args
inside the function edd_ajax_download_search
, you will see it’s passed to get_posts
Let’s search the files to see where is the get_posts
function is located.
You can get it either from command line of a container shell
grep -r "function get_posts()" . | grep '\.php'
Or from Visual Studio Code, go to the search icon and type
function get_posts()
. You will see it’s in the file class-wp-query.php
The function get_posts()
is wordpress function used in the class WP_Query
We will add this code to functions.php to see the full SQL query
add_filter('posts_request', function($sql, $query) {
error_log("SQL Query: " . $sql);
return $sql;
}, 10, 2);
As you can see, the active theme is
Twenty Twenty-Five
theme
so well edit the functions.php file
/wp-content/themes/twentytwentyfive/functions.php
If you go to the url
http://localhost:8000/wp-admin/admin-ajax.php?action=edd_download_search&s=5555
A file called debug.log
will be generated under wp-content, containing the full SQL query.
As you can see, the user input isn’t sanitized, making it vulnerable to SQL injection.
http://localhost:8000/wp-admin/admin-ajax.php?action=edd_download_search&s=5555%27+AND+(SELECT+1+FROM+(SELECT(SLEEP(5)))a)--+-
We test this time-based SQLi query.
References
https://nvd.nist.gov/vuln/detail/CVE-2023-23489/ https://wpscan.com/vulnerability/c5a6830c-6420-42fc-b20c-8e20224d6f18/