CVE-2026-39479

OttoKit <= 1.1.20 - Authenticated (Administrator+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
4.9
CVSS Score
4.9
CVSS Score
medium
Severity
1.1.21
Patched in
24d
Time to patch

Description

The OttoKit plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 1.1.20 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with administrator-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
High
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=1.1.20
PublishedMarch 23, 2026
Last updatedApril 15, 2026
Affected pluginsuretriggers

What Changed in the Fix

Changes introduced in v1.1.21

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

## Vulnerability Summary The OttoKit (suretriggers) plugin for WordPress is vulnerable to an authenticated SQL injection in version 1.1.20 and below. The vulnerability exists in the administrative "Outgoing Requests" view, where the `orderby` and `order` parameters are used in a database query witho…

Show full research plan

Vulnerability Summary

The OttoKit (suretriggers) plugin for WordPress is vulnerable to an authenticated SQL injection in version 1.1.20 and below. The vulnerability exists in the administrative "Outgoing Requests" view, where the orderby and order parameters are used in a database query without sufficient escaping or proper preparation (specifically using %s placeholders in wpdb->prepare() for SQL identifiers like column names, or direct concatenation). Since wpdb->prepare()'s %s placeholder quotes values as strings, it is unsuitable for ORDER BY clauses, often leading developers to bypass it with unsafe concatenation.

Attack Vector Analysis

  • Endpoint: wp-admin/admin.php
  • Query Parameters:
    • page=suretriggers-status
    • tab=st_outgoing_requests
  • Vulnerable Parameters: orderby, order
  • Authentication: Administrator+ (High privileges required).
  • Injection Type: Time-based Blind SQL Injection or Error-based SQL Injection.
  • Preconditions: The plugin must be active. The attacker must be logged in as an administrator.

Code Flow

  1. Entry Point: An administrator navigates to the plugin's status page: admin.php?page=suretriggers-status&tab=st_outgoing_requests.
  2. File Loading: WordPress loads src/Admin/Views/st-admin-outgoing-req-page.php.
  3. Class Initialization: The script instantiates SureTriggersWebhookRequestsTable, which extends WP_List_Table.
  4. Data Preparation: The prepare_items() method is invoked (line 166).
  5. Input Capture: The code retrieves unsanitized (only sanitize_text_field filtered) input from $_REQUEST['orderby'] and $_REQUEST['order'] (lines 182-184).
  6. SQL Sink: The input is used in $wpdb->get_results() via a flawed $wpdb->prepare() call (line 189).
    // src/Admin/Views/st-admin-outgoing-req-page.php
    $orderby = isset( $_REQUEST['orderby'] ) ? sanitize_text_field( $_REQUEST['orderby'] ) : 'id';
    $order   = isset( $_REQUEST['order'] ) ? sanitize_text_field( $_REQUEST['order'] ) : 'ASC';
    // ...
    $this->items = $wpdb->get_results( $wpdb->prepare( "SELECT ... FROM $this->table_name $where ORDER BY %s %s LIMIT %d OFFSET %d", $orderby, $order, $per_page, $offset ), ARRAY_A );
    
    If %s is used, the query becomes ORDER BY 'column' 'ASC', which is invalid SQL. If the vulnerable version instead used concatenation (e.g., ORDER BY $orderby $order), the injection is direct.

Nonce Acquisition Strategy

While the orderby and order parameters are typically handled via GET requests for sorting in WP_List_Table and may not require a nonce for simple viewing, the page includes a nonce for bulk actions.

  1. Navigate to the page: Use browser_navigate to wp-admin/admin.php?page=suretriggers-status&tab=st_outgoing_requests.
  2. Extract Nonce: The nonce is stored in a hidden input field with the name _wpnonce.
  3. Action String: The nonce is created using wp_create_nonce( 'suretriggers_requests_nonce_action' ).
  4. Variable extraction: Use browser_eval to get the value: document.querySelector('input[name="_wpnonce"]').value.

Exploitation Strategy

We will use a time-based blind SQL injection payload via the orderby parameter.

1. Confirm Vulnerability (Time-based)

  • Request Method: GET
  • URL: http://localhost:8080/wp-admin/admin.php
  • Parameters:
    • page: suretriggers-status
    • tab: `
Research Findings
Static analysis — not yet PoC-verified

Summary

The OttoKit plugin for WordPress is vulnerable to SQL Injection via the 'orderby' and 'order' parameters on the administrative 'Outgoing Requests' page. Authenticated administrators can exploit this by injecting malicious SQL commands into sorting clauses, allowing for the extraction of sensitive information from the database using time-based blind injection techniques.

Vulnerable Code

// src/Admin/Views/st-admin-outgoing-req-page.php lines 182-184
$status_filter = isset( $_REQUEST['status_filter'] ) ? sanitize_text_field( $_REQUEST['status_filter'] ) : '';
$orderby       = isset( $_REQUEST['orderby'] ) ? sanitize_text_field( $_REQUEST['orderby'] ) : 'id';
$order         = isset( $_REQUEST['order'] ) ? sanitize_text_field( $_REQUEST['order'] ) : 'ASC';

---

// src/Admin/Views/st-admin-outgoing-req-page.php line 189
$this->items = $wpdb->get_results( $wpdb->prepare( "SELECT id, response_code, request_data, status, error_info, created_at FROM $this->table_name $where ORDER BY %s %s LIMIT %d OFFSET %d", $orderby, $order, $per_page, $offset ), ARRAY_A );

Security Fix

--- a/src/Admin/Views/st-admin-outgoing-req-page.php
+++ b/src/Admin/Views/st-admin-outgoing-req-page.php
@@ -181,8 +181,18 @@
 		}
 
 		$status_filter = isset( $_REQUEST['status_filter'] ) ? sanitize_text_field( $_REQUEST['status_filter'] ) : '';
-		$orderby       = isset( $_REQUEST['orderby'] ) ? sanitize_text_field( $_REQUEST['orderby'] ) : 'id';
-		$order         = isset( $_REQUEST['order'] ) ? sanitize_text_field( $_REQUEST['order'] ) : 'ASC';
+
+		// Define allowed sortable columns to prevent SQL injection.
+		$allowed_orderby = array( 'id', 'created_at', 'status', 'response_code' );
+		$orderby         = ( isset( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], $allowed_orderby ) ) ? $_REQUEST['orderby'] : 'id';
+
+		// Define allowed order directions.
+		$order = 'ASC';
+		if ( isset( $_REQUEST['order'] ) ) {
+			$order = ( 'DESC' === strtoupper( $_REQUEST['order'] ) ) ? 'DESC' : 'ASC';
+		}
+
 		$per_page      = $this->get_items_per_page( 'webhook_requests_per_page', 10 );
 		$current_page  = $this->get_pagenum();
 
@@ -193,7 +203,7 @@
 
 		$offset = ( $current_page - 1 ) * $per_page;
 
-		$this->items = $wpdb->get_results( $wpdb->prepare( "SELECT id, response_code, request_data, status, error_info, created_at FROM $this->table_name $where ORDER BY %s %s LIMIT %d OFFSET %d", $orderby, $order, $per_page, $offset ), ARRAY_A );  //phpcs:ignore
+		$this->items = $wpdb->get_results( $wpdb->prepare( "SELECT id, response_code, request_data, status, error_info, created_at FROM $this->table_name $where ORDER BY $orderby $order LIMIT %d OFFSET %d", $per_page, $offset ), ARRAY_A );  //phpcs:ignore
 
 		$total_items = $wpdb->get_var( "SELECT COUNT(*) FROM {$this->table_name} $where" );  //phpcs:ignore

Exploit Outline

The exploit target is the administrative 'Outgoing Requests' view accessible via 'wp-admin/admin.php?page=suretriggers-status&tab=st_outgoing_requests'. An attacker requires Administrator-level privileges to access this page. The vulnerability is triggered by a GET or POST request containing a malicious payload in the 'orderby' parameter. Because the server uses user-supplied input directly in the ORDER BY clause of a database query, an attacker can append SQL logic. For example, a time-based blind SQL injection can be achieved by using a payload like '(CASE WHEN (1=1) THEN SLEEP(5) ELSE id END)'. If the database is vulnerable, the server response will be delayed by the sleep duration, confirming the injection point.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.