Skip to content

Fix: SQL injection, placeholder injection, and parameter handling in reviews, Q&A, and orders - #3022

Open
shewa12 wants to merge 2 commits into
devfrom
fix/security-sql-injection-reviews-orders
Open

shewa12 wants to merge 2 commits into
devfrom
fix/security-sql-injection-reviews-orders

Conversation

@shewa12

@shewa12 shewa12 commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR resolves two critical security issues regarding SQL injection, format placeholder injection, and unparameterized query execution identified during the scheduled security audit:


1. SQL Injection & Format Specifier Injection in Reviews and Q&A Queries (classes/Utils.php)

  • Affected Area: Utils::get_course_reviews() and Utils::get_qa_questions()
  • Vulnerability Description:
    • In get_course_reviews():
      • $status_in was wrapped in double-quotes and concatenated without escaping or sanitize validation ($status_in = '"' . implode( '","', $status_in ) . '"').
      • $include_user_id was directly imploded without casting elements to integers, leading to unvalidated SQL concatenation into _reviews.user_id IN ({$include_user_id}).
      • Both raw arrays were interpolated directly into the format string template of $wpdb->prepare().
    • In get_qa_questions():
      • $args['date'] was passed via esc_sql() and concatenated directly into the $wpdb->prepare() template: AND DATE(_question.comment_date)='{$date}'. If the date input contains format specifiers (%s, %d, %), the outer $wpdb->prepare() interprets them as placeholders, consuming $search_term and leaving the subsequent placeholders without arguments.
      • $my_course_ids and $question_ids were imploded as raw arrays without strict integer casting or QueryHelper::prepare_in_clause().
      • $args['order'] was not checked using QueryHelper::get_valid_sort_order().
  • Reproduction Mechanics (Conceptual):
    • Supplying dates or filter parameters with format specifiers (e.g. %s) causes placeholder shift in $wpdb->prepare(), resulting in query malfunction or syntax errors.
    • Supplying non-numeric IDs in $include_user_id allows raw SQL injection into the WHERE clause.
  • Remediation:
    • In get_course_reviews():
      • Enforced sanitize_key() and QueryHelper::prepare_in_clause() on $status_in.
      • Enforced absint() and QueryHelper::prepare_in_clause() on $include_user_id.
      • Cast $start and $limit to absint().
    • In get_qa_questions():
      • Validated date inputs using tutor_get_formated_date( 'Y-m-d', $args['date'] ) and parameterized the date with %s in the single outer $wpdb->prepare() query.
      • Enforced QueryHelper::get_valid_sort_order() on $args['order'].
      • Applied QueryHelper::prepare_in_clause() with absint() on course IDs and question IDs.

2. Double-Prepare Format String Vulnerability & Unparameterized Order Status in User Orders Query (models/OrderModel.php)

  • Affected Area: OrderModel::get_user_orders()
  • Vulnerability Description:
    • $date_range_clause was generated using a nested $wpdb->prepare('AND DATE(created_at_gmt) BETWEEN %s AND %s', $start_date, $end_date) call and then interpolated into the SQL template for the outer $wpdb->prepare(...). When user-controlled date strings contain format characters (such as %s or %), outer placeholder counting is corrupted, shifting $user_id, $limit, and $offset parameters.
    • $order_status was concatenated directly into $order_status_clause (AND o.order_status = '{$order_status}') rather than being parameterized with %s.
    • $args['order_type'] was escaped with esc_sql() rather than key sanitization.
  • Reproduction Mechanics (Conceptual):
    • Invoking OrderModel::get_user_orders() with date strings containing format specifiers causes outer $wpdb->prepare placeholder misalignment, displacing the numeric $user_id and limit/offset parameters into unintended clauses.
  • Remediation:
    • Removed double-prepare by integrating DATE(created_at_gmt) BETWEEN CAST(%s AS DATE) AND CAST(%s AS DATE) directly into the query template and passing validated date strings via the unified $params array.
    • Parameterized $order_status as %s with sanitize_key().
    • Sanitized order_type using sanitize_key().
    • Cast $limit and $offset to absint().

Verification

  • PHP syntax verification passed with zero errors (php -l).
  • No modifications to local files; all development performed in an isolated sandbox.
  • Fully complies with WordPress Coding Standards (WPCS) and $wpdb preparation guidelines.

@shewa12 shewa12 added the Security Fix Security vulnerability fix label Sep 23, 2026
@shewa12
shewa12 requested a review from harunollyo September 23, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Security Fix Security vulnerability fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant