--- a/wpforo/includes/functions.php
+++ b/wpforo/includes/functions.php
@@ -2845,6 +2845,55 @@
return $data;
}
+/**
+ * Sanitize orderby parameter for SQL queries.
+ * Validates against a whitelist of allowed column names to prevent SQL injection.
+ *
+ * @param string $orderby The orderby value to sanitize
+ * @param string $context The context: 'topics', 'posts', 'members', or 'search'
+ * @param string $default Default value if validation fails
+ * @return string Sanitized orderby value
+ */
+function wpforo_sanitize_orderby( $orderby, $context = 'topics', $default = '' ) {
+ $orderby = sanitize_text_field( $orderby );
+
+ // Define allowed columns for each context
+ $allowed = [
+ 'topics' => [
+ 'topicid', 'forumid', 'userid', 'title', 'slug', 'created', 'modified',
+ 'views', 'posts', 'type', 'status', 'private', 'closed', 'solved',
+ 'has_attach', 'first_postid', 'last_postid', 'pollid', 'prefix'
+ ],
+ 'posts' => [
+ 'postid', 'forumid', 'topicid', 'userid', 'title', 'created', 'modified',
+ 'status', 'private', 'is_answer', 'is_first_post', 'votes', 'root', 'parentid'
+ ],
+ 'members' => [
+ 'userid', 'posts', 'questions', 'answers', 'comments', 'reactions', 'points',
+ 'online_time', 'registered', 'display_name', 'user_registered'
+ ],
+ 'search' => [
+ 'relevancy', 'date', 'user', 'forum', 'created', 'modified'
+ ],
+ ];
+
+ // Get the whitelist for this context
+ $whitelist = isset( $allowed[$context] ) ? $allowed[$context] : [];
+
+ // Also allow common aliases
+ $whitelist = array_merge( $whitelist, [ 'id', 'date', 'name' ] );
+
+ // Check if orderby is in the whitelist (case-insensitive)
+ $orderby_lower = strtolower( $orderby );
+ foreach( $whitelist as $allowed_col ) {
+ if( strtolower( $allowed_col ) === $orderby_lower ) {
+ return $allowed_col; // Return the properly cased version
+ }
+ }
+
+ // If not in whitelist, return the default
+ return $default;
+}
if( ! function_exists( 'sanitize_textarea_field' ) && ! function_exists( '_sanitize_text_fields' ) ) {
function sanitize_textarea_field( $str ) {
--- a/wpforo/themes/classic/recent.php
+++ b/wpforo/themes/classic/recent.php
@@ -29,7 +29,7 @@
$end_date = time() - ( intval( $days ) * 24 * 60 * 60 );
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'] = ( $paged - 1 ) * wpforo_setting( 'topics', 'topics_per_page' );
$args['row_count'] = wpforo_setting( 'topics', 'topics_per_page' );
@@ -71,7 +71,7 @@
} else {
$end_date = time() - ( intval( $days ) * 24 * 60 * 60 );
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'] = ( $paged - 1 ) * wpforo_setting( 'topics', 'posts_per_page' );
$args['row_count'] = wpforo_setting( 'topics', 'posts_per_page' );
--- a/wpforo/wpforo.php
+++ b/wpforo/wpforo.php
@@ -5,7 +5,7 @@
* Description: WordPress Forum plugin. wpForo is a full-fledged forum solution for your community. Comes with multiple modern forum layouts.
* Author: gVectors Team
* Author URI: https://gvectors.com/
-* Version: 2.4.14
+* Version: 2.4.15
* Requires at least: 5.2
* Requires PHP: 7.2
* Text Domain: wpforo
@@ -14,7 +14,7 @@
namespace wpforo;
-define( 'WPFORO_VERSION', '2.4.14' );
+define( 'WPFORO_VERSION', '2.4.15' );
//Exit if accessed directly
if( ! defined( 'ABSPATH' ) ) exit;
@@ -1033,7 +1033,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';
}
@@ -1074,7 +1074,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'];
@@ -1150,7 +1150,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'];