Akismet Anti-Spam - Version 4.1.9

Version Description

Release Date - 2 March 2021

  • Improved handling of pingbacks in XML-RPC multicalls
Download this release

Release Info

Developer cfinke
Plugin Icon 128x128 Akismet Anti-Spam
Version 4.1.9
Comparing to
See all releases

Code changes from version 4.1.8 to 4.1.9

Files changed (3) hide show
  1. akismet.php +2 -2
  2. class.akismet.php +115 -15
  3. readme.txt +7 -2
akismet.php CHANGED
@@ -6,7 +6,7 @@
6
  Plugin Name: Akismet Anti-Spam
7
  Plugin URI: https://akismet.com/
8
  Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. 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.
9
- Version: 4.1.8
10
  Author: Automattic
11
  Author URI: https://automattic.com/wordpress-plugins/
12
  License: GPLv2 or later
@@ -37,7 +37,7 @@ if ( !function_exists( 'add_action' ) ) {
37
  exit;
38
  }
39
 
40
- define( 'AKISMET_VERSION', '4.1.8' );
41
  define( 'AKISMET__MINIMUM_WP_VERSION', '4.0' );
42
  define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
43
  define( 'AKISMET_DELETE_LIMIT', 100000 );
6
  Plugin Name: Akismet Anti-Spam
7
  Plugin URI: https://akismet.com/
8
  Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. 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.
9
+ Version: 4.1.9
10
  Author: Automattic
11
  Author URI: https://automattic.com/wordpress-plugins/
12
  License: GPLv2 or later
37
  exit;
38
  }
39
 
40
+ define( 'AKISMET_VERSION', '4.1.9' );
41
  define( 'AKISMET__MINIMUM_WP_VERSION', '4.0' );
42
  define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
43
  define( 'AKISMET_DELETE_LIMIT', 100000 );
class.akismet.php CHANGED
@@ -10,8 +10,14 @@ class Akismet {
10
  private static $prevent_moderation_email_for_these_comments = array();
11
  private static $last_comment_result = null;
12
  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' => '' );
13
- private static $is_rest_api_call = false;
14
 
 
 
 
 
 
 
 
15
  public static function init() {
16
  if ( ! self::$initiated ) {
17
  self::init_hooks();
@@ -131,7 +137,7 @@ class Akismet {
131
  }
132
 
133
  public static function rest_auto_check_comment( $commentdata ) {
134
- self::$is_rest_api_call = true;
135
 
136
  return self::auto_check_comment( $commentdata );
137
  }
@@ -240,7 +246,7 @@ class Akismet {
240
  update_option( 'akismet_spam_count', get_option( 'akismet_spam_count' ) + $incr );
241
  }
242
 
243
- if ( self::$is_rest_api_call ) {
244
  return new WP_Error( 'akismet_rest_comment_discarded', __( 'Comment discarded.', 'akismet' ) );
245
  }
246
  else {
@@ -250,7 +256,7 @@ class Akismet {
250
  die();
251
  }
252
  }
253
- else if ( self::$is_rest_api_call ) {
254
  // The way the REST API structures its calls, we can set the comment_approved value right away.
255
  $commentdata['comment_approved'] = 'spam';
256
  }
@@ -1420,16 +1426,100 @@ p {
1420
  if ( $method !== 'pingback.ping' )
1421
  return;
1422
 
 
 
 
 
 
 
 
 
1423
  global $wp_xmlrpc_server;
1424
-
1425
  if ( !is_object( $wp_xmlrpc_server ) )
1426
  return false;
1427
-
1428
- // Lame: tightly coupled with the IXR class.
1429
- $args = $wp_xmlrpc_server->message->params;
1430
-
1431
- if ( !empty( $args[1] ) ) {
1432
- $post_id = url_to_postid( $args[1] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1433
 
1434
  // If pingbacks aren't open on this post, we'll still check whether this request is part of a potential DDOS,
1435
  // 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 @@ p {
1442
  $pingbacks_closed = true;
1443
  }
1444
 
 
 
1445
  $comment = array(
1446
- 'comment_author_url' => $args[0],
1447
  'comment_post_ID' => $post_id,
1448
  'comment_author' => '',
1449
  'comment_author_email' => '',
1450
  'comment_content' => '',
1451
  'comment_type' => 'pingback',
1452
  'akismet_pre_check' => '1',
1453
- 'comment_pingback_target' => $args[1],
1454
  'pingbacks_closed' => $pingbacks_closed ? '1' : '0',
 
 
1455
  );
1456
 
1457
  $comment = Akismet::auto_check_comment( $comment );
1458
 
1459
- if ( isset( $comment['akismet_result'] ) && 'true' == $comment['akismet_result'] ) {
1460
- // Lame: tightly coupled with the IXR classes. Unfortunately the action provides no context and no way to return anything.
 
 
 
1461
  $wp_xmlrpc_server->error( new IXR_Error( 0, 'Invalid discovery target' ) );
 
 
 
1462
  }
1463
  }
1464
  }
10
  private static $prevent_moderation_email_for_these_comments = array();
11
  private static $last_comment_result = null;
12
  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' => '' );
 
13
 
14
+ /**
15
+ * 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.
16
+ *
17
+ * @var type bool
18
+ */
19
+ private static $is_api_call = false;
20
+
21
  public static function init() {
22
  if ( ! self::$initiated ) {
23
  self::init_hooks();
137
  }
138
 
139
  public static function rest_auto_check_comment( $commentdata ) {
140
+ self::$is_api_call = true;
141
 
142
  return self::auto_check_comment( $commentdata );
143
  }
246
  update_option( 'akismet_spam_count', get_option( 'akismet_spam_count' ) + $incr );
247
  }
248
 
249
+ if ( self::$is_api_call ) {
250
  return new WP_Error( 'akismet_rest_comment_discarded', __( 'Comment discarded.', 'akismet' ) );
251
  }
252
  else {
256
  die();
257
  }
258
  }
259
+ else if ( self::$is_api_call ) {
260
  // The way the REST API structures its calls, we can set the comment_approved value right away.
261
  $commentdata['comment_approved'] = 'spam';
262
  }
1426
  if ( $method !== 'pingback.ping' )
1427
  return;
1428
 
1429
+ // 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.
1430
+ // This ticket should hopefully fix that: https://core.trac.wordpress.org/ticket/52524
1431
+ // Until that happens, when it's a system.multicall, pre_check_pingback will be called once for every internal pingback call.
1432
+ // Keep track of how many times this function has been called so we know which call to reference in the XML.
1433
+ static $call_count = 0;
1434
+
1435
+ $call_count++;
1436
+
1437
  global $wp_xmlrpc_server;
1438
+
1439
  if ( !is_object( $wp_xmlrpc_server ) )
1440
  return false;
1441
+
1442
+ self::$is_api_call = true;
1443
+
1444
+ $is_multicall = false;
1445
+ $multicall_count = 0;
1446
+
1447
+ if ( 'system.multicall' === $wp_xmlrpc_server->message->methodName ) {
1448
+ $is_multicall = true;
1449
+
1450
+ if ( 0 === $call_count ) {
1451
+ // Only pass along the number of entries in the multicall the first time we see it.
1452
+ $multicall_count = count( $wp_xmlrpc_server->message->params );
1453
+ }
1454
+
1455
+ /*
1456
+ * $wp_xmlrpc_server->message looks like this:
1457
+ *
1458
+ (
1459
+ [message] =>
1460
+ [messageType] => methodCall
1461
+ [faultCode] =>
1462
+ [faultString] =>
1463
+ [methodName] => system.multicall
1464
+ [params] => Array
1465
+ (
1466
+ [0] => Array
1467
+ (
1468
+ [methodName] => pingback.ping
1469
+ [params] => Array
1470
+ (
1471
+ [0] => http://www.example.net/?p=1 // Site that created the pingback.
1472
+ [1] => https://www.example.com/?p=1 // Post being pingback'd on this site.
1473
+ )
1474
+ )
1475
+ [1] => Array
1476
+ (
1477
+ [methodName] => pingback.ping
1478
+ [params] => Array
1479
+ (
1480
+ [0] => http://www.example.net/?p=1 // Site that created the pingback.
1481
+ [1] => https://www.example.com/?p=2 // Post being pingback'd on this site.
1482
+ )
1483
+ )
1484
+ )
1485
+ )
1486
+ */
1487
+
1488
+ // Use the params from the nth pingback.ping call in the multicall.
1489
+ $pingback_calls_found = 0;
1490
+
1491
+ foreach ( $wp_xmlrpc_server->message->params as $xmlrpc_action ) {
1492
+ if ( 'pingback.ping' === $xmlrpc_action['methodName'] ) {
1493
+ $pingback_calls_found++;
1494
+ }
1495
+
1496
+ if ( $call_count === $pingback_calls_found ) {
1497
+ $pingback_args = $xmlrpc_action['params'];
1498
+ break;
1499
+ }
1500
+ }
1501
+ } else {
1502
+ /*
1503
+ * $wp_xmlrpc_server->message looks like this:
1504
+ *
1505
+ (
1506
+ [message] =>
1507
+ [messageType] => methodCall
1508
+ [faultCode] =>
1509
+ [faultString] =>
1510
+ [methodName] => pingback.ping
1511
+ [params] => Array
1512
+ (
1513
+ [0] => http://www.example.net/?p=1 // Site that created the pingback.
1514
+ [1] => https://www.example.com/?p=2 // Post being pingback'd on this site.
1515
+ )
1516
+ )
1517
+ */
1518
+ $pingback_args = $wp_xmlrpc_server->message->params;
1519
+ }
1520
+
1521
+ if ( ! empty( $pingback_args[1] ) ) {
1522
+ $post_id = url_to_postid( $pingback_args[1] );
1523
 
1524
  // If pingbacks aren't open on this post, we'll still check whether this request is part of a potential DDOS,
1525
  // but indicate to the server that pingbacks are indeed closed so we don't include this request in the user's stats,
1532
  $pingbacks_closed = true;
1533
  }
1534
 
1535
+ // 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.
1536
+
1537
  $comment = array(
1538
+ 'comment_author_url' => $pingback_args[0],
1539
  'comment_post_ID' => $post_id,
1540
  'comment_author' => '',
1541
  'comment_author_email' => '',
1542
  'comment_content' => '',
1543
  'comment_type' => 'pingback',
1544
  'akismet_pre_check' => '1',
1545
+ 'comment_pingback_target' => $pingback_args[1],
1546
  'pingbacks_closed' => $pingbacks_closed ? '1' : '0',
1547
+ 'is_multicall' => $is_multicall,
1548
+ 'multicall_count' => $multicall_count,
1549
  );
1550
 
1551
  $comment = Akismet::auto_check_comment( $comment );
1552
 
1553
+ if (
1554
+ is_wp_error( $comment ) // This triggered a 'discard' directive.
1555
+ || ( isset( $comment['akismet_result'] ) && 'true' == $comment['akismet_result'] ) // It was just a normal spam response.
1556
+ ) {
1557
+ // Sad: tightly coupled with the IXR classes. Unfortunately the action provides no context and no way to return anything.
1558
  $wp_xmlrpc_server->error( new IXR_Error( 0, 'Invalid discovery target' ) );
1559
+
1560
+ // Also note that if this was part of a multicall, a spam result will prevent the subsequent calls from being executed.
1561
+ // This is probably fine, but it raises the bar for what should be acceptable as a false positive.
1562
  }
1563
  }
1564
  }
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eoigal, cfinke, automattic, jgs, procifer, stephdau
3
  Tags: comments, spam, antispam, anti-spam, contact form, anti spam, comment moderation, comment spam, contact form spam, spam comments
4
  Requires at least: 4.6
5
- Tested up to: 5.6
6
- Stable tag: 4.1.8
7
  License: GPLv2 or later
8
 
9
  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
30
 
31
  == Changelog ==
32
 
 
 
 
 
 
33
  = 4.1.8 =
34
  *Release Date - 6 January 2021*
35
 
2
  Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eoigal, cfinke, automattic, jgs, procifer, stephdau
3
  Tags: comments, spam, antispam, anti-spam, contact form, anti spam, comment moderation, comment spam, contact form spam, spam comments
4
  Requires at least: 4.6
5
+ Tested up to: 5.7
6
+ Stable tag: 4.1.9
7
  License: GPLv2 or later
8
 
9
  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
 
31
  == Changelog ==
32
 
33
+ = 4.1.9 =
34
+ *Release Date - 2 March 2021*
35
+
36
+ * Improved handling of pingbacks in XML-RPC multicalls
37
+
38
  = 4.1.8 =
39
  *Release Date - 6 January 2021*
40