Headline
CVE-2021-24851: Changeset 2614442 for insert-pages – WordPress Plugin Repository
The Insert Pages WordPress plugin before 3.7.0 allows users with a role as low as Contributor to access content and metadata from arbitrary posts/pages regardless of their author and status (ie private), using a shortcode. Password protected posts/pages are not affected by such issue.
insert-pages/trunk/insert-pages.php
r2539194
r2614442
8
8
* Domain Path: /languages
9
9
* License: GPL2
10
* Version: 3.6.1
10
* Requires at least: 3.0.1
11
* Version: 3.7.0
11
12
*
12
13
* @package insert-pages
…
…
423
424
}
424
425
426
// Prevent unprivileged users from inserting private posts from others.
427
if ( is\_object( $inserted\_page ) && 'publish' !== $inserted\_page->post\_status ) {
428
$post\_type = get\_post\_type\_object( $inserted\_page->post\_type );
429
$parent\_post\_author\_id = intval( get\_the\_author\_meta( 'ID' ) );
430
if ( ! user\_can( $parent\_post\_author\_id, $post\_type->cap->read\_post, $inserted\_page->ID ) ) {
431
$inserted\_page = null;
432
}
433
}
434
425
435
// If inserted page's status is private, don't show to anonymous users
426
436
// unless 'public' option is set.
…
…
649
659
}
650
660
echo $content;
651
/\*\*
652
\* Meta.
653
\*
654
\* @see https://core.trac.wordpress.org/browser/tags/4.4/src/wp-includes/post-template.php#L968
655
\*/
656
$keys = get\_post\_custom\_keys( $inserted\_page->ID );
657
if ( $keys ) {
658
echo "<ul class='post-meta'>\\n";
659
foreach ( (array) $keys as $key ) {
660
$keyt = trim( $key );
661
if ( is\_protected\_meta( $keyt, 'post' ) ) {
662
continue;
663
}
664
$value = get\_post\_custom\_values( $key, $inserted\_page->ID );
665
if ( is\_array( $value ) ) {
666
$values = array\_map( 'trim', $value );
667
$value = implode( $values, ', ' );
668
}
669
670
/\*\*
671
\* Filter the HTML output of the li element in the post custom fields list.
672
\*
673
\* @since 2.2.0
674
\*
675
\* @param string $html The HTML output for the li element.
676
\* @param string $key Meta key.
677
\* @param string $value Meta value.
678
\*/
679
echo apply\_filters( 'the\_meta\_key', "<li><span class='post-meta-key'>$key:</span> $value</li>\\n", $key, $value );
680
}
681
echo "</ul>\\n";
682
}
661
$this->the\_meta( $inserted\_page->ID );
683
662
break;
684
663
…
…
756
735
);
757
736
}
737
758
738
// We save the previous query state here instead of using
759
739
// wp\_reset\_query() because wp\_reset\_query() only has a single stack
…
…
762
742
$old\_query = $GLOBALS\['wp\_query'\];
763
743
$posts = query\_posts( $args );
744
745
// Prevent unprivileged users from inserting private posts from others.
746
if ( have\_posts() ) {
747
$can\_read = true;
748
$parent\_post\_author\_id = intval( get\_the\_author\_meta( 'ID' ) );
749
foreach ( $posts as $post ) {
750
$post\_type = get\_post\_type\_object( $post->post\_type );
751
if ( ! user\_can( $parent\_post\_author\_id, $post\_type->cap->read\_post, $post->ID ) ) {
752
$can\_read = false;
753
}
754
}
755
if ( ! $can\_read ) {
756
// Force an empty query so we don't show any posts.
757
$posts = query\_posts( array( 'post\_\_in' => array( 0 ) ) );
758
}
759
}
760
764
761
if ( have\_posts() ) {
765
762
// Start output buffering so we can save the output to string.
…
…
948
945
echo get\_the\_content();
949
946
}
950
the\_meta();
947
$this->the\_meta();
951
948
// Render any <!--nextpage--> pagination links.
952
949
wp\_link\_pages( array(
…
…
1035
1032
}
1036
1033
}
1037
1038
1034
1039
1035
return $content;
…
…
1427
1423
);
1428
1424
1425
// Show non-admins only their own posts if the option is enabled.
1426
$options = get\_option( 'wpip\_settings' );
1427
if (
1428
! empty( $options\['wpip\_classic\_editor\_hide\_others\_posts'\] ) &&
1429
'enabled' === $options\['wpip\_classic\_editor\_hide\_others\_posts'\] &&
1430
! current\_user\_can( 'edit\_others\_posts' )
1431
) {
1432
$query\['author'\] = get\_current\_user\_id();
1433
}
1434
1429
1435
$args\['pagenum'\] = isset( $args\['pagenum'\] ) ? absint( $args\['pagenum'\] ) : 1;
1430
1436
$query\['offset'\] = $args\['pagenum'\] > 1 ? $query\['posts\_per\_page'\] \* ( $args\['pagenum'\] - 1 ) : 0;
…
…
1456
1462
$results = array();
1457
1463
foreach ( $posts as $post ) {
1464
// Prevent unprivileged users (e.g., Contributors) from seeing and
1465
// inserting other user's private posts.
1466
$post\_type = get\_post\_type\_object( $post->post\_type );
1467
if ( 'publish' !== $post->post\_status && ! current\_user\_can( $post\_type->cap->read\_post, $post->ID ) ) {
1468
continue;
1469
}
1470
1458
1471
if ( 'post' === $post->post\_type ) {
1459
1472
$info = mysql2date( 'Y/m/d', $post->post\_date );
…
…
1566
1579
}
1567
1580
1581
/\*\*
1582
\* Render post meta as an unordered list.
1583
\*
1584
\* Note: This function sanitizes postmeta value via wp\_kses\_post(); the
1585
\* core WordPress function the\_meta() does not.
1586
\*
1587
\* @see https://developer.wordpress.org/reference/functions/the\_meta/
1588
\*
1589
\* @param int $post\_id Post ID.
1590
\*/
1591
public function the\_meta( $post\_id = 0 ) {
1592
if ( empty( $post\_id ) ) {
1593
$post\_id = get\_the\_ID();
1594
}
1595
1596
$keys = get\_post\_custom\_keys( $post\_id );
1597
if ( $keys ) {
1598
$li\_html = '';
1599
foreach ( (array) $keys as $key ) {
1600
$keyt = trim( $key );
1601
if ( is\_protected\_meta( $keyt, 'post' ) ) {
1602
continue;
1603
}
1604
1605
$values = array\_map( 'trim', get\_post\_custom\_values( $key, $post\_id ) );
1606
$value = implode( ', ', $values );
1607
1608
// Sanitize post meta values.
1609
$value = wp\_kses\_post( $value );
1610
1611
$html = sprintf(
1612
"<li><span class='post-meta-key'>%s</span> %s</li>\\n",
1613
/\* translators: %s: Post custom field name. \*/
1614
sprintf( \_x( '%s:', 'Post custom field name' ), $key ),
1615
$value
1616
);
1617
1618
/\*\*
1619
\* Filters the HTML output of the li element in the post custom fields list.
1620
\*
1621
\* @since 2.2.0
1622
\*
1623
\* @param string $html The HTML output for the li element.
1624
\* @param string $key Meta key.
1625
\* @param string $value Meta value.
1626
\*/
1627
$li\_html .= apply\_filters( 'the\_meta\_key', $html, $key, $value );
1628
}
1629
1630
if ( $li\_html ) {
1631
echo "<ul class='post-meta'>\\n{$li\_html}</ul>\\n";
1632
}
1633
}
1634
}
1635
1568
1636
}
1569
1637
}
insert-pages/trunk/options.php
r2187208
r2614442
64
64
'wpip\_section'
65
65
);
66
add\_settings\_field(
67
'wpip\_classic\_editor\_hide\_others\_posts',
68
\_\_( 'TinyMCE capabilities', 'insert-pages' ),
69
'wpip\_classic\_editor\_hide\_others\_posts\_render',
70
'wpipSettings',
71
'wpip\_section'
72
);
66
73
}
67
74
add_action( 'admin_init’, ‘wpip_settings_init’ );
…
…
103
110
if ( ! array\_key\_exists( 'wpip\_gutenberg\_block', $options ) ) {
104
111
$options\['wpip\_gutenberg\_block'\] = 'enabled';
112
}
113
114
if ( empty( $options\['wpip\_classic\_editor\_hide\_others\_posts'\] ) ) {
115
$options\['wpip\_classic\_editor\_hide\_others\_posts'\] = 'disabled';
105
116
}
106
117
…
…
220
231
<?php
221
232
}
233
234
/**
235
* Print ‘TinyMCE capabilities’ setting.
236
*
237
* @return void
238
*/
239
function wpip_classic_editor_hide_others_posts_render() {
240
$options = get\_option( 'wpip\_settings' );
241
if ( false === $options || ! is\_array( $options ) || empty( $options\['wpip\_classic\_editor\_hide\_others\_posts'\] ) ) {
242
$options = wpip\_set\_defaults();
243
}
244
?>
245
<input type='radio' name='wpip\_settings\[wpip\_classic\_editor\_hide\_others\_posts\]' <?php checked( $options\['wpip\_classic\_editor\_hide\_others\_posts'\], 'enabled' ); ?> id="wpip\_classic\_editor\_hide\_others\_posts\_enabled" value='enabled'><label for="wpip\_classic\_editor\_hide\_others\_posts\_enabled">Authors and Contributors only see their own content to insert.</label><br />
246
<input type='radio' name='wpip\_settings\[wpip\_classic\_editor\_hide\_others\_posts\]' <?php checked( $options\['wpip\_classic\_editor\_hide\_others\_posts'\], 'disabled' ); ?> id="wpip\_classic\_editor\_hide\_others\_posts\_disabled" value='disabled'><label for="wpip\_classic\_editor\_hide\_others\_posts\_disabled">Authors and Contributors see all published content to insert.</label><br />
247
<small><em>Note: this option only restricts Contributors and Authors (i.e., the roles without the <code>edit\_others\_posts</code> capability) from seeing other's content in the TinyMCE Insert Page popup; they can still insert any published content if they know the page slug.</em></small>
248
<?php
249
}
insert-pages/trunk/readme.txt
r2539194
r2614442
2
2
Contributors: figureone, the_magician
3
3
Tags: insert, pages, shortcode, embed
4
Requires at least: 3.0.1
5
Tested up to: 5.7
4
Tested up to: 5.8.1
6
5
Stable tag: trunk
7
6
License: GPLv2 or later
…
…
111
110
112
111
== Changelog ==
112
113
= 3.7.0 =
114
* Security: Prevent unprivileged users from inserting private posts by others.
115
* Security: Filter out possible XSS in post meta using wp_kses_post() when using display=all.
116
* New Setting: Only show Authors and Contributors their own content in the TinyMCE Insert Pages popup.
113
117
114
118
= 3.6.1 =
…
…
461
465
= 1.0 =
462
466
Upgrade to v1.0 to get the first stable version.
467
468
== Upgrade Notice ==
469
470
= 3.7.0 =
471
Note: if you insert private pages/posts, please review the post authors of the pages containing the inserted page and confirm they have the capability to read the private content. This upgrade enforces private page visibility based on the role of the author of the page that inserts any private content.