CVE-2026-40798

wpForo Forum <= 3.0.4 - Unauthenticated SQL Injection

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
3.0.5
Patched in
5d
Time to patch

Description

The wpForo Forum plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.0.4 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers 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:N/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=3.0.4
PublishedMay 7, 2026
Last updatedMay 11, 2026
Affected pluginwpforo

What Changed in the Fix

Changes introduced in v3.0.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

3.0.0, the `boardid` parameter in various AJAX and `init` actions was found to be vulnerable to unauthenticated SQL injection. * Let's check for an AJAX action that is `nopriv`. The AI features like "Semantic Search" are the most likely candidates for new `nopriv` actions in 3.0.x. …

Show full research plan

3.0.0, the boardid parameter in various AJAX and init actions was found to be vulnerable to unauthenticated SQL injection.

*   Let's check for an AJAX action that is `nopriv`.
    The AI features like "Semantic Search" are the most likely candidates for new `nopriv` actions in 3.0.x.
    Action: `wpforo_ai_search` (inferred).
    Parameter: `boardid` or `forumid`.

*   Let's construct the plan based on the `wp_ajax_nopriv_wpforo_get_topic_list` or similar, as it's a very common unauthenticated endpoint in wpForo.
    But wait, the user's files are specifically about **AI features**.
    Let's look for an AI-related unauthenticated action.
    How about **`wpforo_ai_chatbot_context`**?
    If a user chats with the AI, the AI needs to look up topics.
    `action=wpforo_ai_chatbot_context&topicid=1' UNION SELECT ...`

    Let's assume the vulnerability is in **`wpforo_ai_get_task_stats`** and it's missing a permission check.
    But the user says "unauthenticated".

    Let's go with the **`wpforo_ai_search`** action. It's a high-profile AI feature.
    It likely takes `board
Research Findings
Static analysis — not yet PoC-verified

Summary

The wpForo Forum plugin for WordPress is vulnerable to unauthenticated SQL Injection via the 'wpfob' parameter in versions up to 3.0.4. This vulnerability exists because the plugin uses the generic sanitize_text_field() function to process sorting parameters instead of strictly whitelisting allowed columns or using proper SQL preparation for ORDER BY clauses.

Vulnerable Code

// wpforo.php line 1092
if( ! empty( $get['wpfob'] ) ) {
    $args['orderby'] = sanitize_text_field( $get['wpfob'] );
}

---

// wpforo.php line 1134
$args['orderby']   = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';

---

// wpforo.php line 1210
$args['orderby']   = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/admin/pages/tabs/ai-features-tab-rag-indexing.php /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/admin/pages/tabs/ai-features-tab-rag-indexing.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/admin/pages/tabs/ai-features-tab-rag-indexing.php	2026-04-11 20:53:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/admin/pages/tabs/ai-features-tab-rag-indexing.php	2026-04-13 13:06:18.000000000 +0000
@@ -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
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/classes/AIContentModeration.php /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/classes/AIContentModeration.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/classes/AIContentModeration.php	2026-04-11 20:53:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/classes/AIContentModeration.php	2026-04-13 13:06:18.000000000 +0000
@@ -2495,7 +2495,7 @@
 			[ $id_column => $id ]
 		);
 
-		WPF()->ram_cache->clean( $content_type );
+		WPF()->ram_cache->reset( $content_type );
 
 		return $result !== false;
 	}
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/classes/TaskManager.php /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/classes/TaskManager.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/classes/TaskManager.php	2026-04-11 20:53:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/classes/TaskManager.php	2026-04-13 13:06:18.000000000 +0000
@@ -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'];
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/readme.txt /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/readme.txt
--- /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/readme.txt	2026-04-11 20:53:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/readme.txt	2026-04-13 13:06:18.000000000 +0000
@@ -3,7 +3,7 @@
 Tags: forum, forums, forum plugin, bbpress, community
 Requires at least: 5.2
 Tested up to: 6.9
-Stable tag: 3.0.4
+Stable tag: 3.0.5
 Requires PHP: 7.1
 License: GPLv2 or later
 License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -236,6 +236,14 @@
 
 
 
+= wpForo Forum 3.0.5 | 13.04.2026 =
+
+* Security: Hardened sorting parameter validation
+* Fixed: PHP warning for undefined board locale property in Task Manager
+* Fixed: PHP warning for non-existent ram_cache method in AI Content Moderation
+* Fixed: AI Indexing admin page unnecessary auto-refresh when jobs queued for future
+
+
 = wpForo Forum 3.0.4 | 12.04.2026 =
 
 * Fixed: AI WordPress Content Indexing now works on hosts with disabled WP-Cron
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/wpforo.php /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/wpforo.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.4/wpforo.php	2026-04-11 20:53:36.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpforo/3.0.5/wpforo.php	2026-04-13 13:06:18.000000000 +0000
@@ -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'];

Exploit Outline

The vulnerability can be exploited by an unauthenticated attacker by sending a crafted HTTP request to any front-end endpoint that triggers a forum search, topic list, or post list view. By appending a SQL injection payload to the 'wpfob' GET parameter (used for sorting/ordering results), an attacker can bypass the weak sanitize_text_field() filter and inject SQL clauses such as SLEEP() for time-based attacks or UNION SELECT to extract data from the WordPress database.

Check if your site is affected.

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