OttoKit <= 1.1.20 - Authenticated (Administrator+) SQL Injection
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:NTechnical Details
<=1.1.20What Changed in the Fix
Changes introduced in v1.1.21
Source Code
WordPress.org SVN## 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-statustab=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
- Entry Point: An administrator navigates to the plugin's status page:
admin.php?page=suretriggers-status&tab=st_outgoing_requests. - File Loading: WordPress loads
src/Admin/Views/st-admin-outgoing-req-page.php. - Class Initialization: The script instantiates
SureTriggersWebhookRequestsTable, which extendsWP_List_Table. - Data Preparation: The
prepare_items()method is invoked (line 166). - Input Capture: The code retrieves unsanitized (only
sanitize_text_fieldfiltered) input from$_REQUEST['orderby']and$_REQUEST['order'](lines 182-184). - SQL Sink: The input is used in
$wpdb->get_results()via a flawed$wpdb->prepare()call (line 189).
If// 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 );%sis used, the query becomesORDER 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.
- Navigate to the page: Use
browser_navigatetowp-admin/admin.php?page=suretriggers-status&tab=st_outgoing_requests. - Extract Nonce: The nonce is stored in a hidden input field with the name
_wpnonce. - Action String: The nonce is created using
wp_create_nonce( 'suretriggers_requests_nonce_action' ). - Variable extraction: Use
browser_evalto 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-statustab: `
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
@@ -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.