Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/delicious-recipes/assets/build/Components.asset.php
+++ b/delicious-recipes/assets/build/Components.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '87e13b8e186687e12cb3');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '58fbc8248276ac1edce2');
--- a/delicious-recipes/assets/build/blocks.asset.php
+++ b/delicious-recipes/assets/build/blocks.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-url'), 'version' => '33cb150de9d44ff13b30');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-url'), 'version' => 'ced9787ddc65cb429eb1');
--- a/delicious-recipes/assets/build/global.asset.php
+++ b/delicious-recipes/assets/build/global.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'f9daabfbad6c7f3cc8cf');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '8481d27093104153007e');
--- a/delicious-recipes/assets/build/import.asset.php
+++ b/delicious-recipes/assets/build/import.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '903562126102290f2397');
+<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => 'cbd2c7cbb56019b72954');
--- a/delicious-recipes/assets/build/infiniteScroll.asset.php
+++ b/delicious-recipes/assets/build/infiniteScroll.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '11c51ab2edf43c97027a');
+<?php return array('dependencies' => array(), 'version' => '08216f0cdd07916112cd');
--- a/delicious-recipes/assets/build/onboard.asset.php
+++ b/delicious-recipes/assets/build/onboard.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '549944a8fb815cd42f0f');
+<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'c86e436b511cb4dbe946');
--- a/delicious-recipes/assets/build/promotionButtonsJS.asset.php
+++ b/delicious-recipes/assets/build/promotionButtonsJS.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n'), 'version' => '0d6e0d78388369137428');
+<?php return array('dependencies' => array('react', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n'), 'version' => 'cd63692faa0ad7a30661');
--- a/delicious-recipes/assets/build/recipe.asset.php
+++ b/delicious-recipes/assets/build/recipe.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '512e6b3b3937a7504d7c');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '33b50c3ff38030a7fdd8');
--- a/delicious-recipes/assets/build/singleRecipe.asset.php
+++ b/delicious-recipes/assets/build/singleRecipe.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-i18n'), 'version' => 'b8582902ac39d61f98aa');
+<?php return array('dependencies' => array('wp-i18n'), 'version' => '37f3bdb8ae23c2d08868');
--- a/delicious-recipes/assets/build/wpdArchiveJS.asset.php
+++ b/delicious-recipes/assets/build/wpdArchiveJS.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '5cffea458ca2ab71a944');
+<?php return array('dependencies' => array(), 'version' => '58d640f4f5659e728a99');
--- a/delicious-recipes/assets/build/wpdPublic.asset.php
+++ b/delicious-recipes/assets/build/wpdPublic.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '60d96e9d0ed80091c75c');
+<?php return array('dependencies' => array(), 'version' => '6b1fa57fb12108a62aa4');
--- a/delicious-recipes/delicious-recipes.php
+++ b/delicious-recipes/delicious-recipes.php
@@ -9,7 +9,7 @@
* License URI: https://www.gnu.org/licenses/gpl-3.0.html
* Text Domain: delicious-recipes
* Domain Path: /languages
- * Version: 1.10.1
+ * Version: 1.10.2
* Requires at least: 5.8
* Tested up to: 7.0
* Requires PHP: 7.4
@@ -79,7 +79,7 @@
}
if ( ! defined( 'DELICIOUS_RECIPES_VERSION' ) ) {
- define( 'DELICIOUS_RECIPES_VERSION', '1.10.1' );
+ define( 'DELICIOUS_RECIPES_VERSION', '1.10.2' );
}
/**
--- a/delicious-recipes/src/DeliciousPublic.php
+++ b/delicious-recipes/src/DeliciousPublic.php
@@ -401,6 +401,7 @@
'proEnabled' => function_exists( 'DEL_RECIPE_PRO' ),
'license_validity' => $license_validity,
'isUserLoggedIn' => is_user_logged_in(),
+ 'like_nonce' => wp_create_nonce( 'delicious_recipes_like_nonce' ),
);
wp_register_script(
--- a/delicious-recipes/src/classes/class-delicious-recipes-likes-wishlists.php
+++ b/delicious-recipes/src/classes/class-delicious-recipes-likes-wishlists.php
@@ -49,6 +49,26 @@
}
/**
+ * Get the server-side identifier used to track a "like" for the current visitor.
+ *
+ * Logged-in users are tracked by user ID. Logged-out visitors are tracked by a
+ * salted hash of their real IP address (REMOTE_ADDR only — forwarded headers are
+ * spoofable) so the identity cannot be forged or cycled by the client to inflate
+ * the like counter or grow the stored meta without bound.
+ *
+ * @return int|string
+ */
+ public function get_like_identifier() {
+ if ( is_user_logged_in() ) {
+ return get_current_user_id();
+ }
+
+ $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
+
+ return 'ip_' . md5( $ip . wp_salt() );
+ }
+
+ /**
* Check if user can like the recipes post
*
* @param integer $id Post ID.
@@ -60,24 +80,12 @@
return false;
}
- // if user is logged in, return true.
- if ( is_user_logged_in() ) {
- $recipe_likes_ip = get_post_meta( $id, '_recipe_likes_ip', true );
- $curr_user_id = get_current_user_id();
- if ( ! empty( $recipe_likes_ip ) && in_array( $curr_user_id, $recipe_likes_ip ) ) {
- return false;
- }
+ $recipe_likes_ip = get_post_meta( $id, '_recipe_likes_ip', true );
+ if ( ! empty( $recipe_likes_ip ) && is_array( $recipe_likes_ip ) && in_array( $this->get_like_identifier(), $recipe_likes_ip, true ) ) {
+ return false;
}
- return true; // Default case
-
- /**
- * @deprecated code @since 1.8.0 since we changed checking likes from IP Address to Unique User ID we created and assigned for not logged in users.
- */
- // $ip_list = ( $ip = get_post_meta( $id, '_recipe_likes_ip', true ) ) ? $ip : array();
- // if ( ( $ip_list == '' ) || ( is_array( $ip_list ) && ! in_array( $this->get_real_ip_address(), $ip_list ) ) ) {
- // return true;
- // }
+ return true; // Default case.
}
/**
@@ -86,15 +94,9 @@
* @return void
*/
public function check_like() {
- if ( isset( $_POST['id'] ) && isset( $_POST['unique_user_id'] ) ) :
- $post_id = intval( $_POST['id'] );
- $ip_list = ( $ip = get_post_meta( $post_id, '_recipe_likes_ip', true ) ) ? $ip : array();
- $unique_user_id = sanitize_text_field( wp_unslash( $_POST['unique_user_id'] ) );
- if ( ( $ip_list == '' ) || ( is_array( $ip_list ) && ! in_array( $unique_user_id, $ip_list ) ) ) :
- wp_send_json_success( array( 'can_like' => true ) );
- else :
- wp_send_json_success( array( 'can_like' => false ) );
- endif;
+ if ( isset( $_POST['id'] ) ) :
+ $post_id = intval( $_POST['id'] );
+ wp_send_json_success( array( 'can_like' => $this->can_like( $post_id ) ) );
else :
wp_send_json_error();
endif;
@@ -130,59 +132,45 @@
* @return void
*/
public function recipe_like_cb() {
+ // Verify the request originated from our frontend.
+ check_ajax_referer( 'delicious_recipes_like_nonce', 'nonce' );
+
if ( isset( $_POST['id'] ) && isset( $_POST['add_remove'] ) ) :
$post_id = intval( $_POST['id'] );
+
+ // Only allow liking actual recipe posts, never arbitrary post IDs.
+ if ( DELICIOUS_RECIPE_POST_TYPE !== get_post_type( $post_id ) ) :
+ wp_send_json_error( array( 'message' => 'Invalid recipe.' ) );
+ endif;
+
$likes = ( $count = get_post_meta( $post_id, '_recipe_likes', true ) ) ? absint( $count ) : 0;
$ip_list = ( $ip = get_post_meta( $post_id, '_recipe_likes_ip', true ) ) ? $ip : array();
+ if ( ! is_array( $ip_list ) ) :
+ $ip_list = array();
+ endif;
$add_remove = sanitize_title( wp_unslash( $_POST['add_remove'] ) );
- // Check if the user is logged in and get the user ID
- $current_user_id = is_user_logged_in() ? get_current_user_id() : null;
+ // Identity is derived server-side; never trust a client-supplied identifier.
+ $identifier = $this->get_like_identifier();
if ( $add_remove === 'add' && $this->can_like( $post_id ) ) :
- // Add the user ID if logged in, or the unique user ID if not logged in
- if ( $current_user_id ) {
- $ip_list[] = $current_user_id;
- } elseif ( isset( $_POST['unique_user_id'] ) ) {
- $unique_user_id = sanitize_text_field( wp_unslash( $_POST['unique_user_id'] ) );
- $ip_list[] = $unique_user_id;
- }
+ $ip_list[] = $identifier;
++$likes;
update_post_meta( $post_id, '_recipe_likes', absint( $likes ) );
update_post_meta( $post_id, '_recipe_likes_ip', $ip_list );
elseif ( $add_remove === 'remove' ) :
- // Remove the user ID from the list if logged in
- if ( $current_user_id ) :
- $key = array_search( $current_user_id, $ip_list );
- if ( $key !== false ) :
- --$likes;
- if ( $likes <= 0 ) :
- $likes = 0;
- endif;
- unset( $ip_list[ $key ] );
- update_post_meta( $post_id, '_recipe_likes', absint( $likes ) );
- update_post_meta( $post_id, '_recipe_likes_ip', $ip_list );
- endif;
- else :
- // Remove the unique user ID from the list if not logged in
- if ( isset( $_POST['unique_user_id'] ) ) :
- $unique_user_id = sanitize_text_field( wp_unslash( $_POST['unique_user_id'] ) );
- $key = array_search( $unique_user_id, $ip_list );
- if ( $key !== false ) :
- --$likes;
- if ( $likes <= 0 ) :
- $likes = 0;
- endif;
- unset( $ip_list[ $key ] );
- update_post_meta( $post_id, '_recipe_likes', absint( $likes ) );
- update_post_meta( $post_id, '_recipe_likes_ip', $ip_list );
- endif;
- else :
- wp_send_json_error( array( 'message' => 'Invalid request.' ) );
+ $key = array_search( $identifier, $ip_list, true );
+ if ( $key !== false ) :
+ --$likes;
+ if ( $likes <= 0 ) :
+ $likes = 0;
endif;
+ unset( $ip_list[ $key ] );
+ update_post_meta( $post_id, '_recipe_likes', absint( $likes ) );
+ update_post_meta( $post_id, '_recipe_likes_ip', $ip_list );
endif;
else :
--- a/delicious-recipes/vendor/composer/installed.php
+++ b/delicious-recipes/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'xqluz/delicious-recipes',
- 'pretty_version' => 'v1.10.1',
- 'version' => '1.10.1.0',
- 'reference' => 'c8aeafca846f369c4d9ebec28405431be99d428f',
+ 'pretty_version' => 'v1.10.2',
+ 'version' => '1.10.2.0',
+ 'reference' => '13fa0985e2c36f2f408c0adce7f8fd68dd59ce9d',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -11,9 +11,9 @@
),
'versions' => array(
'xqluz/delicious-recipes' => array(
- 'pretty_version' => 'v1.10.1',
- 'version' => '1.10.1.0',
- 'reference' => 'c8aeafca846f369c4d9ebec28405431be99d428f',
+ 'pretty_version' => 'v1.10.2',
+ 'version' => '1.10.2.0',
+ 'reference' => '13fa0985e2c36f2f408c0adce7f8fd68dd59ce9d',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),