diff --git a/plugins/akismet/akismet.php b/plugins/akismet/akismet.php index eee45b17..0a9086bb 100644 --- a/plugins/akismet/akismet.php +++ b/plugins/akismet/akismet.php @@ -6,7 +6,7 @@ Plugin Name: Akismet Anti-Spam Plugin URI: https://akismet.com/ Description: Used by millions, Akismet is quite possibly the best way in the world to protect your blog from spam. It keeps your site protected even while you sleep. To get started: activate the Akismet plugin and then go to your Akismet Settings page to set up your API key. -Version: 4.1.8 +Version: 4.1.9 Author: Automattic Author URI: https://automattic.com/wordpress-plugins/ License: GPLv2 or later @@ -37,7 +37,7 @@ exit; } -define( 'AKISMET_VERSION', '4.1.8' ); +define( 'AKISMET_VERSION', '4.1.9' ); define( 'AKISMET__MINIMUM_WP_VERSION', '4.0' ); define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'AKISMET_DELETE_LIMIT', 100000 ); diff --git a/plugins/akismet/class.akismet.php b/plugins/akismet/class.akismet.php index d293d006..f848ac4a 100644 --- a/plugins/akismet/class.akismet.php +++ b/plugins/akismet/class.akismet.php @@ -10,8 +10,14 @@ class Akismet { private static $prevent_moderation_email_for_these_comments = array(); private static $last_comment_result = null; private static $comment_as_submitted_allowed_keys = array( 'blog' => '', 'blog_charset' => '', 'blog_lang' => '', 'blog_ua' => '', 'comment_agent' => '', 'comment_author' => '', 'comment_author_IP' => '', 'comment_author_email' => '', 'comment_author_url' => '', 'comment_content' => '', 'comment_date_gmt' => '', 'comment_tags' => '', 'comment_type' => '', 'guid' => '', 'is_test' => '', 'permalink' => '', 'reporter' => '', 'site_domain' => '', 'submit_referer' => '', 'submit_uri' => '', 'user_ID' => '', 'user_agent' => '', 'user_id' => '', 'user_ip' => '' ); - private static $is_rest_api_call = false; + /** + * Is the comment check happening in the context of an API call? Of if false, then it's during the POST that happens after filling out a comment form. + * + * @var type bool + */ + private static $is_api_call = false; + public static function init() { if ( ! self::$initiated ) { self::init_hooks(); @@ -131,7 +137,7 @@ public static function added_option( $option_name, $value ) { } public static function rest_auto_check_comment( $commentdata ) { - self::$is_rest_api_call = true; + self::$is_api_call = true; return self::auto_check_comment( $commentdata ); } @@ -240,7 +246,7 @@ public static function auto_check_comment( $commentdata ) { update_option( 'akismet_spam_count', get_option( 'akismet_spam_count' ) + $incr ); } - if ( self::$is_rest_api_call ) { + if ( self::$is_api_call ) { return new WP_Error( 'akismet_rest_comment_discarded', __( 'Comment discarded.', 'akismet' ) ); } else { @@ -250,7 +256,7 @@ public static function auto_check_comment( $commentdata ) { die(); } } - else if ( self::$is_rest_api_call ) { + else if ( self::$is_api_call ) { // The way the REST API structures its calls, we can set the comment_approved value right away. $commentdata['comment_approved'] = 'spam'; } @@ -1420,16 +1426,100 @@ public static function pre_check_pingback( $method ) { if ( $method !== 'pingback.ping' ) return; + // A lot of this code is tightly coupled with the IXR class because the xmlrpc_call action doesn't pass along any information besides the method name. + // This ticket should hopefully fix that: https://core.trac.wordpress.org/ticket/52524 + // Until that happens, when it's a system.multicall, pre_check_pingback will be called once for every internal pingback call. + // Keep track of how many times this function has been called so we know which call to reference in the XML. + static $call_count = 0; + + $call_count++; + global $wp_xmlrpc_server; - + if ( !is_object( $wp_xmlrpc_server ) ) return false; - - // Lame: tightly coupled with the IXR class. - $args = $wp_xmlrpc_server->message->params; - - if ( !empty( $args[1] ) ) { - $post_id = url_to_postid( $args[1] ); + + self::$is_api_call = true; + + $is_multicall = false; + $multicall_count = 0; + + if ( 'system.multicall' === $wp_xmlrpc_server->message->methodName ) { + $is_multicall = true; + + if ( 0 === $call_count ) { + // Only pass along the number of entries in the multicall the first time we see it. + $multicall_count = count( $wp_xmlrpc_server->message->params ); + } + + /* + * $wp_xmlrpc_server->message looks like this: + * + ( + [message] => + [messageType] => methodCall + [faultCode] => + [faultString] => + [methodName] => system.multicall + [params] => Array + ( + [0] => Array + ( + [methodName] => pingback.ping + [params] => Array + ( + [0] => http://www.example.net/?p=1 // Site that created the pingback. + [1] => https://www.example.com/?p=1 // Post being pingback'd on this site. + ) + ) + [1] => Array + ( + [methodName] => pingback.ping + [params] => Array + ( + [0] => http://www.example.net/?p=1 // Site that created the pingback. + [1] => https://www.example.com/?p=2 // Post being pingback'd on this site. + ) + ) + ) + ) + */ + + // Use the params from the nth pingback.ping call in the multicall. + $pingback_calls_found = 0; + + foreach ( $wp_xmlrpc_server->message->params as $xmlrpc_action ) { + if ( 'pingback.ping' === $xmlrpc_action['methodName'] ) { + $pingback_calls_found++; + } + + if ( $call_count === $pingback_calls_found ) { + $pingback_args = $xmlrpc_action['params']; + break; + } + } + } else { + /* + * $wp_xmlrpc_server->message looks like this: + * + ( + [message] => + [messageType] => methodCall + [faultCode] => + [faultString] => + [methodName] => pingback.ping + [params] => Array + ( + [0] => http://www.example.net/?p=1 // Site that created the pingback. + [1] => https://www.example.com/?p=2 // Post being pingback'd on this site. + ) + ) + */ + $pingback_args = $wp_xmlrpc_server->message->params; + } + + if ( ! empty( $pingback_args[1] ) ) { + $post_id = url_to_postid( $pingback_args[1] ); // If pingbacks aren't open on this post, we'll still check whether this request is part of a potential DDOS, // but indicate to the server that pingbacks are indeed closed so we don't include this request in the user's stats, @@ -1442,23 +1532,33 @@ public static function pre_check_pingback( $method ) { $pingbacks_closed = true; } + // Note: If is_multicall is true and multicall_count=0, then we know this is at least the 2nd pingback we've processed in this multicall. + $comment = array( - 'comment_author_url' => $args[0], + 'comment_author_url' => $pingback_args[0], 'comment_post_ID' => $post_id, 'comment_author' => '', 'comment_author_email' => '', 'comment_content' => '', 'comment_type' => 'pingback', 'akismet_pre_check' => '1', - 'comment_pingback_target' => $args[1], + 'comment_pingback_target' => $pingback_args[1], 'pingbacks_closed' => $pingbacks_closed ? '1' : '0', + 'is_multicall' => $is_multicall, + 'multicall_count' => $multicall_count, ); $comment = Akismet::auto_check_comment( $comment ); - if ( isset( $comment['akismet_result'] ) && 'true' == $comment['akismet_result'] ) { - // Lame: tightly coupled with the IXR classes. Unfortunately the action provides no context and no way to return anything. + if ( + is_wp_error( $comment ) // This triggered a 'discard' directive. + || ( isset( $comment['akismet_result'] ) && 'true' == $comment['akismet_result'] ) // It was just a normal spam response. + ) { + // Sad: tightly coupled with the IXR classes. Unfortunately the action provides no context and no way to return anything. $wp_xmlrpc_server->error( new IXR_Error( 0, 'Invalid discovery target' ) ); + + // Also note that if this was part of a multicall, a spam result will prevent the subsequent calls from being executed. + // This is probably fine, but it raises the bar for what should be acceptable as a false positive. } } } diff --git a/plugins/akismet/readme.txt b/plugins/akismet/readme.txt index 0e52ae4f..505aebf1 100644 --- a/plugins/akismet/readme.txt +++ b/plugins/akismet/readme.txt @@ -2,8 +2,8 @@ Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eoigal, cfinke, automattic, jgs, procifer, stephdau Tags: comments, spam, antispam, anti-spam, contact form, anti spam, comment moderation, comment spam, contact form spam, spam comments Requires at least: 4.6 -Tested up to: 5.6 -Stable tag: 4.1.8 +Tested up to: 5.7 +Stable tag: 4.1.9 License: GPLv2 or later The best anti-spam protection to block spam comments and spam in a contact form. The most trusted antispam solution for WordPress and WooCommerce. @@ -30,6 +30,11 @@ Upload the Akismet plugin to your blog, activate it, and then enter your Akismet == Changelog == += 4.1.9 = +*Release Date - 2 March 2021* + +* Improved handling of pingbacks in XML-RPC multicalls + = 4.1.8 = *Release Date - 6 January 2021*