Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# CHANGELOG

## 0.93.0

* Don't skip password protected filter when showing content.
* Sanitize post_status so some posts are only shown if user is Editor or Administrator.
* Addresses reported vulnerability: `CVE-2025-11377, Authenticated (Contributor+) Information Exposure`. `
* CVSS Severity Score: 4.3 (Medium)
* CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
* Organization: Wordfence
* Vulnerability Researcher(s): Athiwat Tiprasaharn (Jitlada)

This is a low risk vulnerability that could potentially be executed by an authenticated attacker, with contributor-level access and above. But it should be fixed with this version.

## 0.92.0

* Avoids potential SQL injection in `starting_with` parameter - CVE-2025-10163. This solves SQL injection and results in `starting_with` working as per the Wiki, but the previous code also allowed things like `[catlist starting_with="Hello"]` which would return posts starting with "Hello" but not just with "H". This new implementation would return both, because only the first character matters, which is ok because that's what is documented.
Expand Down
38 changes: 31 additions & 7 deletions include/lcp-catlist.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ private function set_lcp_parameters(){
// http://core.trac.wordpress.org/browser/tags/3.7.1/src/wp-includes/post.php#L1686
$args['posts_per_page'] = $args['numberposts'];

if (isset($args['post_status'])){
$args['post_status'] = $this->sanitize_status($args['post_status']);
}
do_action( 'lcp_pre_run_query', $args );

if ('no' === $this->params['main_query']) {
Expand Down Expand Up @@ -444,16 +447,23 @@ public function get_content($single) {
if (isset($this->params['content']) &&
($this->params['content'] =='yes' || $this->params['content'] == 'full') &&
$single->post_content){
// get_extended - get content split by <!--more-->
$lcp_extended = get_extended($single->post_content);
$lcp_content = $lcp_extended['main'];
$lcp_content = apply_filters('the_content', $lcp_content);
$lcp_content = str_replace(']]>', ']]&gt', $lcp_content);
// If the post is password protected, set the password form in the content.
if (post_password_required($single)) {
$lcp_content = get_the_password_form($single);
return $lcp_content;
Comment on lines +452 to +453
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@klemens-st I think it's ok to just return the content here, without the filters and replace we do in the else branch. Since get_the_password_form applies its own filters. The goal is not to show the content unless you've submitted the right password.
From my tests, if you input the right password, WordPress redirects you to the page and if you go back to where the posts are listed, it does show the content (if content=yes and show_protected=yes are set).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it works nicely and yes the get_the_password_form function seems to return display ready HTML and doesn't need further filters.

} else {
// get_extended - get content split by <!--more-->
$lcp_extended = get_extended($single->post_content);
$lcp_content = $lcp_extended['main'];
$lcp_content = apply_filters('the_content', $lcp_content);
$lcp_content = str_replace(']]>', ']]&gt', $lcp_content);
}

if ($this->params['content'] == 'full') {
$lcp_extended_content = str_replace(
']]>',
']]&gt', apply_filters('the_content', $lcp_extended['extended'])
']]&gt',
apply_filters('the_content', $lcp_extended['extended'])
);
$lcp_content .= $lcp_extended_content;
} else {
Expand All @@ -468,7 +478,7 @@ public function get_content($single) {
}
return $lcp_content;
} else {
return null;
return null;
}
}

Expand Down Expand Up @@ -598,4 +608,18 @@ public function get_pagination() {
);
return LcpPaginator::get_instance()->get_pagination($paginator_params);
}

// Sanitizes the statuses for post_status. Checks if current user is either editor or
// admininstrator. Other users can't see draft or private posts.
private function sanitize_status($statuses){
if (in_array('private', $statuses) || in_array('draft', $statuses)) {
if ( !( current_user_can('editor') || current_user_can('administrator')) ) {
$private_index = array_search('private', $statuses);
unset($statuses[$private_index]);
$draft_index = array_search('draft', $statuses);
unset($statuses[$draft_index]);
}
}
return implode(',', $statuses);
Comment on lines +614 to +623
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When array_search returns false, the unset statement destroys the first element of an array. Unfortunately PHP's type juggling converts false to 0 in numeric contexts so this method doesn't work as intended. Easiest fix:

private function sanitize_status($statuses){
    if (in_array('private', $statuses) || in_array('draft', $statuses)) {
      if ( !( current_user_can('editor') || current_user_can('administrator')) ) {
        $private_index = array_search('private', $statuses);
        if ($private_index !== false) {
          unset($statuses[$private_index]);
        }
        $draft_index = array_search('draft', $statuses);
        if ($draft_index !== false) {
          unset($statuses[$draft_index]);
        }
      }
    }
    return implode(',', $statuses);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the vulnerability anly about private and draft statuses? What about other statuses like pending or future? Once this method is fixed, users with Author role will be able to do e.g. [catlist post_status=future] and display a list of all scheduled posts to the public Internet, which will probably also raise some oversensitive eyebrows ;)

Another problem is this section of lcp-parameters.php:

//Get private posts
if( is_user_logged_in() ){
if ( !empty($args['post_status']) ){
$args['post_status'] = array_merge($args['post_status'], array('private'));
} else{
$args['post_status'] = array('private', 'publish');
}
}

No need to delete it but maybe just add a check if a user is Editor or Administrator.

}
}
2 changes: 1 addition & 1 deletion list-category-posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Plugin Name: List category posts
Plugin URI: https://github.com/picandocodigo/List-Category-Posts
Description: List Category Posts allows you to list posts by category in a post/page using the [catlist] shortcode. This shortcode accepts a category name or id, the order in which you want the posts to display, the number of posts to display and many more parameters. You can use [catlist] as many times as needed with different arguments. Usage: [catlist argument1=value1 argument2=value2].
Version: 0.92.0
Version: 0.93.0
Author: Fernando Briano
Author URI: http://fernandobriano.com

Expand Down
12 changes: 10 additions & 2 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ Contributors: fernandobt, zymeth25
Donate Link: http://picandocodigo.net/programacion/wordpress/list-category-posts-wordpress-plugin-english/#support
Tags: list, categories, posts, cms
Requires at least: 3.3
Tested up to: 6.8.2
Tested up to: 6.8.3
Requires PHP: 5.6
Stable tag: 0.92.0
Stable tag: 0.93.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -243,6 +243,14 @@ Template system has changed. Custom templates should be stored in WordPress them

See [CHANGELOG.md](https://github.com/picandocodigo/List-Category-Posts/blob/master/CHANGELOG.md) for full Changelog.

= 0.93.0 =

* Don't skip password protected filter when showing content.
* Sanitize post_status so some posts are only shown if user is Editor or Administrator.
* Addresses reported vulnerability: CVE-2025-11377, Authenticated (Contributor+) Information Exposure. Severity Score: 4.3 (Medium). CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N. Organization: Wordfence. Vulnerability Researcher(s): Athiwat Tiprasaharn (Jitlada)

This is a low risk vulnerability that could potentially be executed by an authenticated attacker, with contributor-level access and above. But it should be fixed with this version.

= 0.92.0 =

* Avoids potential SQL injection in `starting_with` parameter - CVE-2025-10163. This solves SQL injection and results in `starting_with` working as per the Wiki, but the previous code also allowed things like `[catlist starting_with="Hello"]` which would return posts starting with "Hello" but not just with "H". This new implementation would return both, because only the first character matters, which is ok because that's what is documented.
Expand Down