CVE-2026-32365

Collapsing Archives <= 3.0.7 - Authenticated (Contributor+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
3.0.8
Patched in
59d
Time to patch

Description

The Collapsing Archives plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.0.7 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 contributor-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:L/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=3.0.7
PublishedFebruary 16, 2026
Last updatedApril 15, 2026
Affected plugincollapsing-archives

What Changed in the Fix

Changes introduced in v3.0.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-32365 ## 1. Vulnerability Summary The **Collapsing Archives** plugin (versions <= 3.0.7) is vulnerable to an authenticated SQL injection (Contributor+ level) due to improper sanitization and preparation of user-supplied parameters within its Gutenberg block a…

Show full research plan

Exploitation Research Plan - CVE-2026-32365

1. Vulnerability Summary

The Collapsing Archives plugin (versions <= 3.0.7) is vulnerable to an authenticated SQL injection (Contributor+ level) due to improper sanitization and preparation of user-supplied parameters within its Gutenberg block attributes. Specifically, the parameters inExcludeYears, sort, and post_type are concatenated directly into SQL queries within the list_archives() function in collapsArchList.php.

2. Attack Vector Analysis

  • Vulnerable Hook: Gutenberg block rendering (Gutenberg blocks pass attributes to a server-side render_callback).
  • Endpoint: Any page or post where a Contributor (or higher) can insert a block, or the REST API block-renderer endpoint.
  • Vulnerable Parameters: inExcludeYears, sort, post_type.
  • Authentication: Authenticated (Contributor+). Contributors can create posts and define block attributes.
  • Preconditions: The plugin must be active, and the attacker must have a user account with contributor, author, editor, or administrator roles.

3. Code Flow

  1. Entry Point: A Contributor creates or edits a post containing the create-block/collapsing-archives block (registered in collapsArch.php via create_block_collapsArch_block_init).
  2. **Registration
Research Findings
Static analysis — not yet PoC-verified

Summary

The Collapsing Archives plugin for WordPress is vulnerable to SQL Injection due to the direct concatenation of block attributes like 'inExcludeYears' into SQL queries without proper sanitization or use of prepared statements. This allows authenticated users with Contributor-level permissions or higher to execute arbitrary SQL commands and extract sensitive information from the database.

Vulnerable Code

// collapsArchList.php lines 64-80
	if ( !empty($inExcludeYear) && !empty($inExcludeYears) ) {
		$exterms = preg_split('/[,]+/',$inExcludeYears);
    if ($inExcludeYear=='include') {
      $in='IN';
    } else {
      $in='NOT IN';
    }
		if ( count($exterms) ) {
			foreach ( $exterms as $exterm ) {
				if (empty($inExclusionsYear))
					$inExclusionsYear = "'" .$exterm . "'";
				else
					$inExclusionsYear .= ", '" . $exterm . "' ";
			}
		}
	}
	if ( empty($inExclusionsYear) ) {
		$inExcludeYearQuery = "";
  } else {
    $inExcludeYearQuery ="AND YEAR($wpdb->posts.post_date) $in ($inExclusionsYear)";
  }

---

// collapsArchList.php lines 91-106
  $postquery= "SELECT $wpdb->terms.slug, $wpdb->posts.ID,
    $wpdb->posts.post_name, $wpdb->posts.post_title, $wpdb->posts.post_author,
    $wpdb->posts.post_date, YEAR($wpdb->posts.post_date) AS 'year',
    MONTH($wpdb->posts.post_date) AS 'month' ,
    $wpdb->posts.post_type
    FROM $wpdb->posts LEFT JOIN $wpdb->term_relationships ON $wpdb->posts.ID =
    $wpdb->term_relationships.object_id
		LEFT JOIN $wpdb->term_taxonomy ON $wpdb->term_taxonomy.term_taxonomy_id =
																			$wpdb->term_relationships.term_taxonomy_id
		LEFT JOIN $wpdb->terms ON $wpdb->terms.term_id =
		                          $wpdb->term_taxonomy.term_id
  WHERE post_status='publish' $postTypeQuery $inExcludeYearQuery $inExcludeCatQuery
  GROUP BY $wpdb->posts.ID
  ORDER BY $wpdb->posts.post_date $sort";

  $allPosts=$wpdb->get_results($postquery);

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/collapsing-archives/3.0.7/collapsArchList.php /home/deploy/wp-safety.org/data/plugin-versions/collapsing-archives/3.0.8/collapsArchList.php
--- /home/deploy/wp-safety.org/data/plugin-versions/collapsing-archives/3.0.7/collapsArchList.php	2025-05-05 06:53:26.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/collapsing-archives/3.0.8/collapsArchList.php	2026-02-12 03:40:52.000000000 +0000
@@ -52,28 +52,28 @@
   } else {
     $inExcludeCatQuery ="AND $wpdb->terms.slug $in ($inExclusionsCat)";
   }
-	$inExclusionsYear = array();
+	$inExcludeYearQuery = "";
 	if ( !empty($inExcludeYear) && !empty($inExcludeYears) ) {
-		$exterms = preg_split('/[,]+/',$inExcludeYears);
-    if ($inExcludeYear=='include') {
-      $in='IN';
-    } else {
-      $in='NOT IN';
-    }
+		$exterms = preg_split('/[,]+/', $inExcludeYears);
+		// Validate $inExcludeYear to prevent SQL injection.
+		$in = ( $inExcludeYear === 'include' ) ? 'IN' : 'NOT IN';
 		if ( count($exterms) ) {
+			$sanitized_years = array();
 			foreach ( $exterms as $exterm ) {
-				if (empty($inExclusionsYear))
-					$inExclusionsYear = "'" .$exterm . "'";
-				else
-					$inExclusionsYear .= ", '" . $exterm . "' ";
+				$year = absint( trim( $exterm ) );
+				if ( $year > 0 ) {
+					$sanitized_years[] = $year;
+				}
+			}
+			if ( ! empty( $sanitized_years ) ) {
+				$placeholders = implode( ', ', array_fill( 0, count( $sanitized_years ), '%d' ) );
+				$inExcludeYearQuery = $wpdb->prepare(
+					"AND YEAR($wpdb->posts.post_date) $in ($placeholders)",
+					$sanitized_years
+				);
 			}
 		}
 	}
-	if ( empty($inExclusionsYear) ) {
-		$inExcludeYearQuery = "";
-  } else {
-    $inExcludeYearQuery ="AND YEAR($wpdb->posts.post_date) $in ($inExclusionsYear)";
-  }

Exploit Outline

To exploit this vulnerability, an attacker must have at least Contributor-level authentication. The attacker can then utilize the WordPress REST API block-renderer endpoint (`/wp-json/wp/v2/block-renderer/create-block/collapsing-archives`) or create a post containing the plugin's Gutenberg block. By manipulating the `attributes` object in the request, specifically the `inExcludeYears` parameter, the attacker can inject SQL syntax. For example, a payload such as `2024') OR (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -` will break out of the string encapsulation in the `list_archives()` function, allowing the attacker to perform time-based blind SQL injection or extract data using UNION-based techniques.

Check if your site is affected.

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