This commit is contained in:
Mauro Torrez
2019-09-17 18:28:38 -03:00
commit 3a8e77323e
30 changed files with 3648 additions and 0 deletions

193
feature/comments.php Normal file
View File

@@ -0,0 +1,193 @@
<?php
/**
* Comment logging
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\notify_post_author' ) ) {
/**
* Log new comment
*
* @since 3.5.0
*
* @param bool $maybe_notify
* @param int $comment_ID
*
* @return bool
*
* @wp-f2b-extra Comment \d+
*/
function notify_post_author( $maybe_notify, $comment_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
syslog( LOG_INFO, "Comment {$comment_ID}" );
closelog();
// @codeCoverageIgnoreEnd
return $maybe_notify;
}
add_filter(
'notify_post_author',
__NAMESPACE__ . '\\notify_post_author',
10,
2
);
}
if ( defined( 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA' ) ) {
/** WPF2B_EVENT_COMMENT_NOT_FOUND */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20002 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_id_not_found' ) ) {
/**
* Log attempted comment on non-existent post
*
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comment post not found \d+
*/
function comment_id_not_found( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comment post not found {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_id_not_found', __NAMESPACE__ . '\\comment_id_not_found' );
}
}
/** LOG_ACTION_LOG_COMMENT_CLOSED */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20004 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_closed' ) ) {
/**
* Log attempted comment on closed post
*
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comments closed on post \d+
*/
function comment_closed( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comments closed on post {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_closed', __NAMESPACE__ . '\\comment_closed' );
}
}
/** LOG_ACTION_LOG_COMMENT_TRASH */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20008 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_on_trash' ) ) {
/**
* Log attempted comment on trashed post
*
* @since 4.0.2 Fix message
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comment attempt on trash post \d+
*/
function comment_on_trash( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comment attempt on trash post {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_on_trash', __NAMESPACE__ . '\\comment_on_trash' );
}
}
/** LOG_ACTION_LOG_COMMENT_DRAFT */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20010 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_on_draft' ) ) {
/**
* Log attempted comment on draft post
*
* @since 4.0.2 Fix message
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comment attempt on draft post \d+
*/
function comment_on_draft( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comment attempt on draft post {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_on_draft', __NAMESPACE__ . '\\comment_on_draft' );
}
}
/** LOG_ACTION_LOG_COMMENT_PASSWORD */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20020 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_on_password_protected' ) ) {
/**
* Log attempted comment on password-protected post
*
* @since 4.0.2 Fix message
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comment attempt on password-protected post \d+
*/
function comment_on_password_protected( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comment attempt on password-protected post {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_on_password_protected', __NAMESPACE__ . '\\comment_on_password_protected' );
}
}
}

144
feature/lib.php Normal file
View File

@@ -0,0 +1,144 @@
<?php
/**
* Library functions
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Wrapper for \openlog
*
* @since 3.5.0 Refactored for unit testing
*
* @param string $log
*/
function openlog( $log = 'WP_FAIL2BAN_AUTH_LOG' )
{
$tag = ( defined( 'WP_FAIL2BAN_SYSLOG_SHORT_TAG' ) && true === WP_FAIL2BAN_SYSLOG_SHORT_TAG ? 'wp' : 'wordpress' );
$host = ( array_key_exists( 'WP_FAIL2BAN_HTTP_HOST', $_ENV ) ? $_ENV['WP_FAIL2BAN_HTTP_HOST'] : $_SERVER['HTTP_HOST'] );
/**
* Some varieties of syslogd have difficulty if $host is too long
* @since 3.5.0
*/
if ( defined( 'WP_FAIL2BAN_TRUNCATE_HOST' ) && 1 < intval( WP_FAIL2BAN_TRUNCATE_HOST ) ) {
$host = substr( $host, 0, intval( WP_FAIL2BAN_TRUNCATE_HOST ) );
}
if ( false === \openlog( "{$tag}({$host})", WP_FAIL2BAN_OPENLOG_OPTIONS, constant( $log ) ) ) {
error_log( 'WPf2b: Cannot open syslog', 0 );
// @codeCoverageIgnore
} elseif ( defined( 'WP_FAIL2BAN_TRACE' ) ) {
error_log( 'WPf2b: Opened syslog', 0 );
// @codeCoverageIgnore
}
}
/**
* Wrapper for \syslog
*
* @since 3.5.0
*
* @param int $level
* @param string $msg
* @param string|null $remote_addr
*/
function syslog( $level, $msg, $remote_addr = null )
{
$msg .= ' from ';
$msg .= ( is_null( $remote_addr ) ? remote_addr() : $remote_addr );
if ( false === \syslog( $level, $msg ) ) {
error_log( "WPf2b: Cannot write to syslog: '{$msg}'", 0 );
// @codeCoverageIgnore
} elseif ( defined( 'WP_FAIL2BAN_TRACE' ) ) {
error_log( "WPf2b: Wrote to syslog: '{$msg}'", 0 );
// @codeCoverageIgnore
}
\closelog();
if ( defined( 'PHPUNIT_COMPOSER_INSTALL' ) ) {
echo "{$level}|{$msg}" ;
}
}
/**
* Graceful immediate exit
*
* @since 4.0.5 Add JSON support
* @since 3.5.0 Refactored for unit testing
*
* @param bool $is_json
*/
function bail( $is_json = false )
{
if ( $is_json ) {
return new \WP_Error( 403, 'Forbidden' );
} else {
wp_die( 'Forbidden', 'Forbidden', array(
'response' => 403,
) );
}
}
/**
* Compute remote IP address
*
* @return string
*
* @todo Test me!
* @codeCoverageIgnore
*/
function remote_addr()
{
static $remote_addr = null ;
/**
* @since 4.0.0
*/
if ( is_null( $remote_addr ) ) {
if ( defined( 'WP_FAIL2BAN_PROXIES' ) ) {
if ( array_key_exists( 'HTTP_X_FORWARDED_FOR', $_SERVER ) ) {
$ip = ip2long( $_SERVER['REMOTE_ADDR'] );
/**
* PHP 7 lets you define an array
* @since 3.5.4
*/
$proxies = ( is_array( WP_FAIL2BAN_PROXIES ) ? WP_FAIL2BAN_PROXIES : explode( ',', WP_FAIL2BAN_PROXIES ) );
foreach ( $proxies as $proxy ) {
if ( '#' == $proxy[0] ) {
continue;
} elseif ( 2 == count( $cidr = explode( '/', $proxy ) ) ) {
$net = ip2long( $cidr[0] );
$mask = ~(pow( 2, 32 - $cidr[1] ) - 1);
} else {
$net = ip2long( $proxy );
$mask = -1;
}
if ( $net == ($ip & $mask) ) {
return ( false === ($len = strpos( $_SERVER['HTTP_X_FORWARDED_FOR'], ',' )) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : substr( $_SERVER['HTTP_X_FORWARDED_FOR'], 0, $len ) );
}
}
}
}
/**
* For plugins and themes that anonymise requests
* @since 3.6.0
*/
$remote_addr = ( defined( 'WP_FAIL2BAN_REMOTE_ADDR' ) ? WP_FAIL2BAN_REMOTE_ADDR : $_SERVER['REMOTE_ADDR'] );
}
return $remote_addr;
}

37
feature/password.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
/**
* Password-related functionality
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5
*/
if ( !function_exists( __NAMESPACE__ . '\\retrieve_password' ) ) {
/**
* Log password reset requests
*
* @since 3.5.0
*
* @param string $user_login
*
* @wp-f2b-extra Password reset requested for .*
*/
function retrieve_password( $user_login )
{
openlog( 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG' );
syslog( LOG_NOTICE, "Password reset requested for {$user_login}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'retrieve_password', __NAMESPACE__ . '\\retrieve_password' );
}

233
feature/plugins.php Normal file
View File

@@ -0,0 +1,233 @@
<?php
/**
* Library functions
*
* @package wp-fail2ban
* @since 4.2.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Hook: plugins_loaded
*
* @since 4.2.0
*/
function plugins_loaded()
{
do_action( 'wp_fail2ban_register' );
}
add_action( 'plugins_loaded', __NAMESPACE__ . '\\plugins_loaded' );
/**
* Register plugin
*
* @since 4.2.0
*
* @param string $slug Plugin slug. This must be the actual plugin slug. Maximum length is 255 which should be more than enough.
* @param string $name Plugin display name. This should be an unescaped string - HTML is allowed.
*
* @return int|false ID
*/
function register_plugin( $slug, $name )
{
global $wp_fail2ban, $wpdb ;
if ( 255 < strlen( $slug ) ) {
throw new \LengthException( 'slug too long' );
}
if ( 255 < strlen( $name ) ) {
throw new \LengthException( 'name too long' );
}
if ( !is_array( @$wp_fail2ban['plugins'] ) ) {
$wp_fail2ban['plugins'] = [];
}
if ( array_key_exists( $slug, $wp_fail2ban['plugins'] ) ) {
return $wp_fail2ban['plugins'][$slug];
}
static $id = 0 ;
return $wp_fail2ban['plugins'][$slug] = [
'id' => ++$id,
'name' => $name,
'messages' => [],
];
}
add_action(
'wp_fail2ban_register_plugin',
__NAMESPACE__ . '\\register_plugin',
1,
2
);
/**
* Check if plugin is registered.
*
* @since 4.2.0
*
* @param string $plugin_slug
*
* @return bool
*/
function is_registered_plugin( $plugin_slug )
{
global $wp_fail2ban ;
return array_key_exists( $plugin_slug, $wp_fail2ban['plugins'] );
}
/**
* Register plugin message.
*
* @since 4.2.0
*
* @param string $plugin_slug
* @param array $msg [
* string slug: Message slug
* string fail: hard|soft|extra
* int facility: syslog facility
* int priority: syslog priority
* string event_class: Event Class
* int event_id: Event ID
* string message: Message with placeholders
* HOST: Remote IP
* USER: Current user name
* array vars: Array of [name => regex] pairs
*/
function register_message( $plugin_slug, array $msg )
{
global $wp_fail2ban ;
$event_classes = [
'auth' => WPF2B_EVENT_CLASS_AUTH,
'comment' => WPF2B_EVENT_CLASS_COMMENT,
'password' => WPF2B_EVENT_CLASS_PASSWORD,
'rest' => WPF2B_EVENT_CLASS_REST,
'spam' => WPF2B_EVENT_CLASS_SPAM,
'xmlrpc' => WPF2B_EVENT_CLASS_XMLRPC,
'other' => 0,
];
$args = [];
if ( !is_registered_plugin( $plugin_slug ) ) {
throw new \InvalidArgumentException( 'plugin not registered' );
}
if ( !array_key_exists( 'slug', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'slug'" );
}
if ( !is_string( $msg['slug'] ) ) {
throw new \InvalidArgumentException( "'slug' must be string" );
}
if ( !array_key_exists( 'fail', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'fail'" );
}
if ( !in_array( $msg['fail'], [ 'hard', 'soft', 'extra' ] ) ) {
throw new \UnexpectedValueException( "'fail' must be one of 'hard', 'soft', 'extra'" );
}
$args['fail'] = $msg['fail'];
if ( !array_key_exists( 'priority', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'priority'" );
}
if ( !in_array( $msg['priority'], [
LOG_CRIT,
LOG_ERR,
LOG_WARNING,
LOG_NOTICE,
LOG_INFO,
LOG_DEBUG
] ) ) {
throw new \UnexpectedValueException( "Invalid 'priority'" );
}
$args['priority'] = $msg['priority'];
if ( !array_key_exists( 'event_class', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'event_class'" );
}
if ( !array_key_exists( $event_class = strtolower( $msg['event_class'] ), $event_classes ) ) {
throw new \UnexpectedValueException( "Invalid 'event_class'" );
}
$args['class'] = $event_class;
$event_class = $event_classes[$event_class];
$log = sprintf( "WP_FAIL2BAN_%s_LOG", strtoupper( $event_class ) );
if ( !array_key_exists( 'event_id', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'event_id'" );
}
if ( ($msg['event_id'] & 0xffff) !== $msg['event_id'] ) {
throw new \UnexpectedValueException( "Invalid 'event_id'" );
}
$args['event_id'] = WPF2B_EVENT_TYPE_PLUGIN | $event_class | $msg['event_id'];
if ( !array_key_exists( 'message', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'message'" );
}
if ( !is_string( $msg['message'] ) ) {
throw new \UnexpectedValueException( "Invalid 'message'" );
}
$args['message'] = $msg['message'];
if ( !array_key_exists( 'vars', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'vars'" );
}
if ( !is_array( $msg['vars'] ) ) {
throw new \UnexpectedValueException( "Invalid 'vars'" );
}
$args['vars'] = $msg['vars'];
$wp_fail2ban['plugins'][$plugin_slug]['messages'][$msg['slug']] = $args;
}
add_action(
'wp_fail2ban_register_message',
__NAMESPACE__ . '\\register_message',
1,
2
);
/**
* Check if message is registered.
*
* NB: Assumes plugin is registered.
*
* @since 4.2.0
*
* @param string $plugin_slug
* @param string $message_slug
*
* @return bool
*/
function is_registered_plugin_message( $plugin_slug, $message_slug )
{
global $wp_fail2ban ;
return array_key_exists( $message_slug, $wp_fail2ban['plugins'][$plugin_slug]['messages'] );
}
/**
* Log plugin message.
*
* @since 4.2.0
*
* @param string $plugin_slug Plugin slug for registered message
* @param string $message_slug Message slug for registered message
* @param array $vars Substitution vars
*/
function log_message( $plugin_slug, $message_slug = null, array $vars = array() )
{
global $wp_fail2ban ;
if ( !is_registered_plugin( $plugin_slug ) ) {
throw new \InvalidArgumentException( 'plugin not registered' );
}
if ( !is_registered_plugin_message( $plugin_slug, $message_slug ) ) {
throw new \InvalidArgumentException( 'message not registered' );
}
$args = $wp_fail2ban['plugins'][$plugin_slug]['messages'][$message_slug];
$msg = $args['message'];
foreach ( $args['vars'] as $name => $regex ) {
if ( array_key_exists( $name, $vars ) ) {
$msg = str_replace( "___{$name}___", $vars[$name], $msg );
}
}
openlog( sprintf( 'WP_FAIL2BAN_PLUGIN_%s_LOG', strtoupper( $args['class'] ) ) );
syslog( $args['priority'], "({$plugin_slug}) {$msg}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action(
'wp_fail2ban_log_message',
__NAMESPACE__ . '\\log_message',
1,
3
);

60
feature/spam.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
/**
* Spam comments
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5
*/
if ( !function_exists( __NAMESPACE__ . '\\log_spam_comment' ) ) {
/**
* Catch comments marked as spam
*
* @since 3.5.0
*
* @param int $comment_id
* @param string $comment_status
*
* @wp-f2b-hard Spam comment \d+
*/
function log_spam_comment( $comment_id, $comment_status )
{
if ( 'spam' === $comment_status ) {
if ( is_null( $comment = get_comment( $comment_id, ARRAY_A ) ) ) {
/**
* @todo: decide what to do about this
*/
} else {
$remote_addr = ( empty($comment['comment_author_IP']) ? 'unknown' : $comment['comment_author_IP'] );
openlog( 'WP_FAIL2BAN_SPAM_LOG' );
syslog( LOG_NOTICE, "Spam comment {$comment_id}", $remote_addr );
closelog();
// @codeCoverageIgnoreEnd
}
}
}
add_action(
'comment_post',
__NAMESPACE__ . '\\log_spam_comment',
10,
2
);
add_action(
'wp_set_comment_status',
__NAMESPACE__ . '\\log_spam_comment',
10,
2
);
}

104
feature/user-enum.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
/**
* User enumeration
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\_log_bail_user_enum' ) ) {
/**
* Common enumeration handling
*
* @since 4.1.0 Add JSON support
* @since 4.0.0
*
* @param bool $is_json
*
* @return \WP_Error
*
* @wp-f2b-hard Blocked user enumeration attempt
*/
function _log_bail_user_enum( $is_json = false )
{
openlog();
syslog( LOG_NOTICE, 'Blocked user enumeration attempt' );
closelog();
// @codeCoverageIgnoreEnd
return bail( $is_json );
}
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\parse_request' ) ) {
/**
* Catch traditional user enum
*
* @see \WP::parse_request()
*
* @since 3.5.0 Refactored for unit testing
* @since 2.1.0
*
* @param \WP $query
*
* @return \WP
*/
function parse_request( $query )
{
if ( !current_user_can( 'list_users' ) && intval( @$query->query_vars['author'] ) ) {
_log_bail_user_enum();
}
return $query;
}
add_filter(
'parse_request',
__NAMESPACE__ . '\\parse_request',
1,
2
);
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\rest_user_query' ) ) {
/**
* Catch RESTful user list
*
* @see \WP_REST_Users_Controller::get_items()
*
* @since 4.0.0
*
* @param array $prepared_args
* @param \WP_REST_Request $request
*
* @return array|\WP_Error
*/
function rest_user_query( $prepared_args, $request )
{
if ( !current_user_can( 'list_users' ) ) {
return _log_bail_user_enum( true );
}
return $prepared_args;
}
add_filter(
'rest_user_query',
__NAMESPACE__ . '\\rest_user_query',
10,
2
);
}

61
feature/user.php Normal file
View File

@@ -0,0 +1,61 @@
<?php
/**
* Blocked user functionality
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\authenticate' ) ) {
/**
* Catched blocked users
*
* @since 3.5.0 Refactored for unit testing
* @since 2.0.0
*
* @param mixed|null $user
* @param string $username
* @param string $password
*
* @return mixed|null
*
* @wp-f2b-hard Blocked authentication attempt for .*
*/
function authenticate( $user, $username, $password )
{
if ( !empty($username) ) {
/**
* @since 3.5.0 Arrays allowed in PHP 7
*/
$matched = ( is_array( WP_FAIL2BAN_BLOCKED_USERS ) ? in_array( $username, WP_FAIL2BAN_BLOCKED_USERS ) : preg_match( '/' . WP_FAIL2BAN_BLOCKED_USERS . '/i', $username ) );
if ( $matched ) {
openlog();
syslog( LOG_NOTICE, "Blocked authentication attempt for {$username}" );
closelog();
// @codeCoverageIgnoreEnd
bail();
}
}
return $user;
}
add_filter(
'authenticate',
__NAMESPACE__ . '\\authenticate',
1,
3
);
}

108
feature/xmlrpc.php Normal file
View File

@@ -0,0 +1,108 @@
<?php
/**
* XML-RPC functionality
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_login_error' ) ) {
/**
* Catch multiple XML-RPC authentication failures
*
* @see \wp_xmlrpc_server::login()
*
* @since 4.0.0 Return $error
* @since 3.5.0 Refactored for unit testing
* @since 3.0.0
*
* @param \IXR_Error $error
* @param \WP_Error $user
*
* @return \IXR_Error
*
* @wp-f2b-hard XML-RPC multicall authentication failure
*/
function xmlrpc_login_error( $error, $user )
{
static $attempts = 0 ;
if ( ++$attempts > 1 ) {
openlog();
syslog( LOG_NOTICE, 'XML-RPC multicall authentication failure' );
closelog();
// @codeCoverageIgnoreEnd
bail();
} else {
return $error;
}
}
add_action(
'xmlrpc_login_error',
__NAMESPACE__ . '\\xmlrpc_login_error',
10,
2
);
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_pingback_error' ) ) {
/**
* Catch failed pingbacks
*
* @see \wp_xmlrpc_server::pingback_error()
*
* @since 4.0.0 Return $ixr_error
* @since 3.5.0 Refactored for unit testing
* @since 3.0.0
*
* @param \IXR_Error $ixr_error
*
* @return \IXR_Error
*
* @wp-f2b-hard Pingback error .* generated
*/
function xmlrpc_pingback_error( $ixr_error )
{
if ( 48 !== $ixr_error->code ) {
openlog();
syslog( LOG_NOTICE, 'Pingback error ' . $ixr_error->code . ' generated' );
closelog();
// @codeCoverageIgnoreEnd
}
return $ixr_error;
}
add_filter( 'xmlrpc_pingback_error', __NAMESPACE__ . '\\xmlrpc_pingback_error', 5 );
}
/**
* @since 4.0.0 Refactored
* @since 2.2.0
*/
if ( defined( 'WP_FAIL2BAN_LOG_PINGBACKS' ) && true === WP_FAIL2BAN_LOG_PINGBACKS ) {
require_once 'xmlrpc/pingback.php';
}
/**
* @since 4.0.0 Refactored
* @since 3.6.0
*/
if ( defined( 'WP_FAIL2BAN_XMLRPC_LOG' ) && '' < WP_FAIL2BAN_XMLRPC_LOG ) {
require_once 'xmlrpc/log.php';
}

35
feature/xmlrpc/log.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
/**
* XML-RPC Request logging
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if (!defined('ABSPATH')) {
exit;
}
/**
* Log XML-RPC requests
*
* It seems attackers are doing weird things with XML-RPC. This makes it easy to
* log them for analysis and future blocking.
*
* @since 4.0.0 Fix: Removed HTTP_RAW_POST_DATA
* https://wordpress.org/support/?p=10971843
* @since 3.6.0
*
* @codeCoverageIgnore
*/
if (false === ($fp = fopen(WP_FAIL2BAN_XMLRPC_LOG, 'a+'))) {
// TODO: decided whether to log this
} else {
$raw_data = (version_compare(PHP_VERSION, '7.0.0') >= 0)
? file_get_contents('php://input')
: $HTTP_RAW_POST_DATA;
fprintf($fp, "# ---\n# Date: %s\n# IP: %s\n\n%s\n", date(DATE_ATOM), remote_addr(), $raw_data);
fclose($fp);
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* pingback logging
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_call' ) ) {
/**
* Log pingbacks
*
* @since 3.5.0 Refactored for unit testing
* @since 2.2.0
*
* @param string $call
*/
function xmlrpc_call( $call )
{
if ( 'pingback.ping' == $call ) {
openlog( 'WP_FAIL2BAN_PINGBACK_LOG' );
syslog( LOG_INFO, 'Pingback requested' );
closelog();
// @codeCoverageIgnoreEnd
}
}
add_action( 'xmlrpc_call', __NAMESPACE__ . '\\xmlrpc_call' );
}