Skip to content

Commit 78de4a9

Browse files
PHPCS - WordPress.Security.NonceVerification (#113)
* turn off NonceVerification * verify nonce when ids are in params * remove variable instance for parameter * use core check_ajax_referer instead of inherited * use filter_has_var to not access superglobal $_GET. * ignore nonce verification due to limitation of phpcs * add nonce check and modernize js * add core nonce check * use core nonce check * remove unused ajax_referer on base * early return when possible * return early on image_sizes_notice * handle error * camelcase * move pagebuilder check to picture and allow test to override behaviour * format * still render details when nonce is invalid * format and typo * format
1 parent 01e2c46 commit 78de4a9

11 files changed

Lines changed: 129 additions & 105 deletions

phpcs.xml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
<exclude name="Generic.Commenting" />
1111
<exclude name="Squiz.PHP.CommentedOutCode.Found" />
1212

13-
<!-- Fix security issues -->
14-
<exclude name="WordPress.Security.NonceVerification" />
15-
1613
<!-- Fix AlternativeFunctons-->
1714
<exclude name="WordPress.WP.AlternativeFunctions" />
1815
<exclude name="WordPress.WP.GlobalVariablesOverride.Prohibited" />

src/class-tiny-helpers.php

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -111,36 +111,6 @@ public static function get_mimetype( $input ) {
111111
}
112112

113113

114-
/**
115-
* Checks wether a user is viewing from a page builder
116-
*
117-
* @since 3.6.5
118-
*/
119-
public static function is_pagebuilder_request() {
120-
$pagebuilder_keys = array(
121-
'fl_builder', // Beaver Builder
122-
'et_fb', // Divi Builder
123-
'bricks', // Bricks Builder
124-
'breakdance', // Breakdance Builder
125-
'breakdance_browser', // Breakdance Builder
126-
'ct_builder', // Oxygen Builder
127-
'fb-edit', // Avada Live Builder
128-
'builder', // Avada Live Builder
129-
'spio_no_cdn', // Site Origin
130-
'tatsu', // Tatsu Builder
131-
'tve', // Thrive Architect
132-
'tcbf', // Thrive Architect
133-
);
134-
135-
foreach ( $pagebuilder_keys as $key ) {
136-
if ( isset( $_GET[ $key ] ) ) {
137-
return true;
138-
}
139-
}
140-
141-
return false;
142-
}
143-
144114
/**
145115
* Gets or initializes the WordPress filesystem instance.
146116
*

src/class-tiny-notices.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public function remove( $name ) {
148148
}
149149

150150
public function dismiss() {
151-
if ( empty( $_POST['name'] ) || ! $this->check_ajax_referer() ) {
151+
if ( ! check_ajax_referer( 'tiny-compress', '_nonce', false ) || empty( $_POST['name'] ) ) {
152152
echo json_encode( false );
153153
exit();
154154
}

src/class-tiny-picture.php

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,62 @@ public function __construct( $settings, $base_dir = ABSPATH, $domains = array()
6565
return;
6666
}
6767

68-
if ( Tiny_Helpers::is_pagebuilder_request() ) {
68+
if ( static::is_pagebuilder_request() ) {
6969
return;
7070
}
7171

7272
add_action( 'template_redirect', array( $this, 'on_template_redirect' ) );
7373
}
7474

75+
/**
76+
* Checks whether the current request originates from a page builder.
77+
*
78+
* Detects known page builder query parameters to prevent picture-element
79+
* injection from interfering with builder previews.
80+
*
81+
* @since 3.6.5
82+
*
83+
* @return bool True if a page builder query parameter is present.
84+
*/
85+
protected static function is_pagebuilder_request() {
86+
$pagebuilder_keys = array(
87+
'fl_builder', // Beaver Builder
88+
'et_fb', // Divi Builder
89+
'bricks', // Bricks Builder
90+
'breakdance', // Breakdance Builder
91+
'breakdance_browser', // Breakdance Builder
92+
'ct_builder', // Oxygen Builder
93+
'fb-edit', // Avada Live Builder
94+
'builder', // Avada Live Builder
95+
'spio_no_cdn', // Site Origin
96+
'tatsu', // Tatsu Builder
97+
'tve', // Thrive Architect
98+
'tcbf', // Thrive Architect
99+
);
100+
101+
foreach ( $pagebuilder_keys as $key ) {
102+
if ( static::has_get_var( $key ) ) {
103+
return true;
104+
}
105+
}
106+
107+
return false;
108+
}
109+
110+
/**
111+
* Checks whether a GET variable exists in the original request.
112+
*
113+
* Wraps filter_has_var() to allow overriding in tests via late static binding.
114+
*
115+
* @since 3.6.5
116+
*
117+
* @param string $key The query string key to check.
118+
* @return bool True if the key exists in the original GET request.
119+
*/
120+
protected static function has_get_var( $key ) {
121+
return filter_has_var( INPUT_GET, $key );
122+
}
123+
75124
public function on_template_redirect() {
76125
$conversion_enabled = $this->settings->get_conversion_enabled();
77126
if ( apply_filters( 'tiny_replace_with_picture', $conversion_enabled ) ) {

src/class-tiny-plugin.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -517,9 +517,8 @@ public function compress_on_upload() {
517517
* or success array ['data' => [$id, $metadata]]
518518
*/
519519
private function validate_ajax_attachment_request() {
520-
if ( ! $this->check_ajax_referer() ) {
521-
exit();
522-
}
520+
check_ajax_referer( 'tiny-compress', '_nonce' );
521+
523522
if ( ! current_user_can( 'upload_files' ) ) {
524523
return array(
525524
'error' => esc_html__(
@@ -614,11 +613,14 @@ public function compress_image_for_bulk() {
614613
);
615614
wp_update_attachment_metadata( $id, $tiny_image->get_wp_metadata() );
616615

616+
// Nonce verified in validate_ajax_attachment_request().
617+
// phpcs:disable WordPress.Security.NonceVerification.Missing
617618
$current_library_size = isset( $_POST['current_size'] ) ?
618619
intval( wp_unslash( $_POST['current_size'] ) )
619620
: 0;
620-
$size_after = $image_statistics['compressed_total_size'];
621-
$new_library_size = $current_library_size + $size_after - $size_before;
621+
// phpcs:enable WordPress.Security.NonceVerification.Missing
622+
$size_after = $image_statistics['compressed_total_size'];
623+
$new_library_size = $current_library_size + $size_after - $size_before;
622624

623625
$result['message'] = $tiny_image->get_latest_error();
624626
$result['image_sizes_compressed'] = $image_statistics['image_sizes_compressed'];
@@ -655,7 +657,8 @@ public function compress_image_for_bulk() {
655657
}
656658

657659
public function ajax_optimization_statistics() {
658-
if ( $this->check_ajax_referer() && current_user_can( 'upload_files' ) ) {
660+
if ( check_ajax_referer( 'tiny-compress', '_nonce', false ) &&
661+
current_user_can( 'upload_files' ) ) {
659662
$stats = Tiny_Bulk_Optimization::get_optimization_statistics( $this->settings );
660663
echo json_encode( $stats );
661664
}
@@ -703,6 +706,7 @@ public function media_library_bulk_action() {
703706
$location = 'upload.php?mode=list&ids=' . $ids;
704707

705708
$location = add_query_arg( 'action', $action, $location );
709+
$location = add_query_arg( '_tiny_nonce', wp_create_nonce( 'tiny-bulk-ids' ), $location );
706710

707711
if ( ! empty( $_REQUEST['paged'] ) ) {
708712
$location = add_query_arg( 'paged', absint( $_REQUEST['paged'] ), $location );
@@ -758,6 +762,18 @@ public function show_media_info() {
758762
}
759763

760764
private function render_compress_details( $tiny_image ) {
765+
$images_to_compress = array();
766+
767+
if ( ! empty( $_GET['ids'] ) ) {
768+
$nonce = isset( $_GET['_tiny_nonce'] ) ?
769+
sanitize_key( wp_unslash( $_GET['_tiny_nonce'] ) ) : '';
770+
771+
if ( $nonce && wp_verify_nonce( $nonce, 'tiny-bulk-ids' ) ) {
772+
$request_ids = sanitize_text_field( wp_unslash( $_GET['ids'] ) );
773+
$images_to_compress = array_map( 'intval', explode( '-', $request_ids ) );
774+
}
775+
}
776+
761777
$in_progress = $tiny_image->filter_image_sizes( 'in_progress' );
762778
if ( count( $in_progress ) > 0 ) {
763779
include __DIR__ . '/views/compress-details-processing.php';
@@ -816,7 +832,7 @@ public function add_dashboard_widget() {
816832
/*
817833
This might be deduplicated with the admin script localization, but
818834
the order of including scripts is sometimes different. So in that
819-
case we need to make sure that the order of inclusion is correc.t */
835+
case we need to make sure that the order of inclusion is correct. */
820836
wp_localize_script(
821837
self::NAME . '_dashboard_widget',
822838
'tinyCompressDashboard',

src/class-tiny-settings.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ public function add_options_to_page() {
160160
}
161161

162162
public function image_sizes_notice() {
163+
check_ajax_referer( 'tiny-compress' );
164+
163165
if ( current_user_can( 'manage_options' ) ) {
164166
$selected_sizes = isset( $_GET['image_sizes_selected'] ) ?
165167
intval( $_GET['image_sizes_selected'] ) : 0;
@@ -827,9 +829,8 @@ public function render_pending_status() {
827829
}
828830

829831
public function create_api_key() {
830-
if ( ! $this->check_ajax_referer() ) {
831-
exit;
832-
}
832+
check_ajax_referer( 'tiny-compress', '_nonce' );
833+
833834
$compressor = $this->get_compressor();
834835
if ( ! current_user_can( 'manage_options' ) ) {
835836
$status = (object) array(
@@ -905,9 +906,7 @@ public function create_api_key() {
905906
}
906907

907908
public function update_api_key() {
908-
if ( ! $this->check_ajax_referer() ) {
909-
exit;
910-
}
909+
check_ajax_referer( 'tiny-compress', '_nonce' );
911910

912911
$key = null;
913912
if ( ! current_user_can( 'manage_options' ) ) {

src/class-tiny-wp-base.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,6 @@ protected function get_user_id() {
8787
return get_current_user_id();
8888
}
8989

90-
protected function check_ajax_referer() {
91-
return check_ajax_referer( 'tiny-compress', '_nonce', false );
92-
}
93-
9490
public function init() {
9591
}
9692

src/js/admin.js

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@
301301
eventOn('click', 'button.tiny-mark-as-compressed', onClickButtonMarkAsCompressed);
302302

303303
setPropOf('button.tiny-compress', 'disabled', null);
304-
305304
compressImageSelection();
306305
watchCompressingImages();
307306

@@ -329,29 +328,33 @@
329328
});
330329
}
331330

332-
eventOn('click', 'input[name*=tinypng_sizes], #tinypng_resize_original_enabled', function() {
333-
/* Unfortunately, we need some additional information to display
334-
the correct notice. */
335-
var totalSelectedSizes = jQuery('input[name*=tinypng_sizes]:checked').length;
336-
var compressWr2x = propOf('#tinypng_sizes_wr2x', 'checked');
337-
if (compressWr2x) {
338-
totalSelectedSizes--;
339-
}
331+
async function refreshSizeDescriptionNotice() {
332+
const totalSelectedSizes = document.querySelectorAll('input[name*=tinypng_sizes]:checked').length;
333+
const compressWr2x = document.querySelector('#tinypng_sizes_wr2x')?.checked ?? false;
334+
const selectedCount = compressWr2x ? totalSelectedSizes - 1 : totalSelectedSizes;
340335

341-
var image_count_url = ajaxurl + (ajaxurl.indexOf( '?' ) > 0 ? '&' : '?') + 'action=tiny_image_sizes_notice&image_sizes_selected=' + totalSelectedSizes;
342-
if (propOf('#tinypng_resize_original_enabled', 'checked') && propOf('#tinypng_sizes_0', 'checked')) {
343-
image_count_url += '&resize_original=true';
336+
const separator = ajaxurl.includes('?') ? '&' : '?';
337+
let imageCountUrl = `${ajaxurl}${separator}action=tiny_image_sizes_notice&image_sizes_selected=${selectedCount}&_ajax_nonce=${tinyCompress.nonce}`;
338+
339+
const resizeOriginalChecked = document.querySelector('#tinypng_resize_original_enabled')?.checked;
340+
const compressOriginalChecked = document.querySelector('#tinypng_sizes_0')?.checked;
341+
if (resizeOriginalChecked && compressOriginalChecked) {
342+
imageCountUrl += '&resize_original=true';
344343
}
345344
if (compressWr2x) {
346-
image_count_url += '&compress_wr2x=true';
345+
imageCountUrl += '&compress_wr2x=true';
347346
}
348-
jQuery('#tiny-image-sizes-notice').load(image_count_url);
349-
});
350-
351-
eventOn('click', '#tinypng_auto_compress_enabled', function() {
352-
updateSettings();
353-
});
347+
try {
348+
const sizeDescriptionHtml = await fetch(imageCountUrl).then(r => r.text());
349+
document.querySelector('#tiny-image-sizes-notice').innerHTML = sizeDescriptionHtml;
350+
} catch (err) {
351+
document.querySelector('#tiny-image-sizes-notice').innerHTML = '';
352+
console.error(err);
353+
}
354+
}
354355

356+
eventOn('click', 'input[name*=tinypng_sizes], #tinypng_resize_original_enabled', refreshSizeDescriptionNotice);
357+
eventOn('click', '#tinypng_auto_compress_enabled', updateSettings);
355358
jQuery('#tinypng_sizes_0, #tinypng_resize_original_enabled').click(updateSettings);
356359
updateSettings();
357360
}

src/views/compress-details.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
/**
33
* Compression details on media overview page.
44
*
5-
* @var Tiny_Plugin $this The plugin instance.
6-
* @var Tiny_Image $tiny_image The image being compressed.
5+
* @var Tiny_Plugin $this The plugin instance.
6+
* @var Tiny_Image $tiny_image The image being compressed.
7+
* @var int[] $images_to_compress The IDs that are being compressed
78
*/
89

910
$available_sizes = array_keys( $this->settings->get_sizes() );
@@ -22,11 +23,6 @@
2223
$size_exists = array_fill_keys( $available_sizes, true );
2324
ksort( $size_exists );
2425

25-
$images_to_compress = array();
26-
if ( ! empty( $_REQUEST['ids'] ) ) {
27-
$request_ids = sanitize_text_field( wp_unslash( $_REQUEST['ids'] ) );
28-
$images_to_compress = array_map( 'intval', explode( '-', $request_ids ) );
29-
}
3026
?>
3127
<div class="details-container">
3228
<div class="details">

test/unit/TinyHelpersTest.php

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -111,26 +111,6 @@ public function test_uppercase_extension_and_mimetype_case_insensitive()
111111
$this->assertEquals($expected, Tiny_Helpers::replace_file_extension('image/avif', $input));
112112
}
113113

114-
public function test_is_pagebuilder_request_returns_false_when_no_pagebuilder_keys()
115-
{
116-
$_GET = array();
117-
$this->assertFalse(Tiny_Helpers::is_pagebuilder_request());
118-
}
119-
120-
public function test_is_pagebuilder_request_returns_true_for_beaver_builder()
121-
{
122-
$_GET = array('fl_builder' => '1');
123-
$this->assertTrue(Tiny_Helpers::is_pagebuilder_request());
124-
$_GET = array();
125-
}
126-
127-
public function test_is_pagebuilder_request_returns_false_for_non_pagebuilder_keys()
128-
{
129-
$_GET = array('page' => 'settings', 'post_id' => '123');
130-
$this->assertFalse(Tiny_Helpers::is_pagebuilder_request());
131-
$_GET = array();
132-
}
133-
134114
public function test_str_starts_with_returns_true_when_haystack_starts_with_needle()
135115
{
136116
$this->assertTrue(Tiny_Helpers::str_starts_with('hello world', 'hello'));

0 commit comments

Comments
 (0)