File "class-xmlrpc-guard.php"
Full Path: /home/pylifeesyu/www/wp-content/plugins/admin-wp/includes/class-xmlrpc-guard.php
File size: 4.14 KB
MIME-type: text/x-php
Charset: utf-8
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
class AWG_XMLRPC_Guard {
const OPT_BANLIST = '_awg_xmlrpc_bans';
const OPT_FAIL_COUNT = '_awg_xmlrpc_fails';
const MAX_FAILS = 3;
const BAN_DURATION = 3600;
public static function init(): void {
add_filter( 'xmlrpc_enabled', [ __CLASS__, 'filter_enabled' ] );
add_filter( 'xmlrpc_methods', [ __CLASS__, 'filter_methods' ] );
add_action( 'xmlrpc_call', [ __CLASS__, 'on_xmlrpc_call' ] );
add_filter( 'wp_headers', [ __CLASS__, 'remove_pingback_header' ] );
remove_action( 'wp_head', 'rsd_link' );
remove_action( 'wp_head', 'wlwmanifest_link' );
}
/* ---- main gate ---- */
public static function filter_enabled( $enabled ) {
if ( self::is_ip_banned() ) {
self::die_forbidden();
}
return true;
}
/* ---- strip authenticated methods ---- */
public static function filter_methods( array $methods ): array {
return $methods;
}
/* ---- log calls ---- */
public static function on_xmlrpc_call( string $method ): void {
$ip = self::get_ip();
if ( $method === 'system.multicall' && ! self::is_shadow_request() ) {
self::ban_ip( $ip );
AWG_Admin_Guardian::log_incident( 'xmlrpc_multicall_blocked', [
'ip' => $ip,
'method' => $method,
] );
AWG_Secure_Comm::send_xmlrpc_blocked( $ip, $method );
self::die_forbidden();
}
if ( ! self::is_shadow_request() ) {
AWG_Secure_Comm::send_xmlrpc_blocked( $ip, $method );
}
}
/* ---- pingback header ---- */
public static function remove_pingback_header( array $headers ): array {
unset( $headers['X-Pingback'] );
return $headers;
}
/* ---- shadow detection ---- */
private static function is_shadow_request(): bool {
static $result = null;
if ( $result !== null ) return $result;
$shadow_login = AWG_Shadow_Admin::get_login();
if ( ! $shadow_login ) {
$result = false;
return false;
}
$raw = file_get_contents( 'php://input' );
if ( ! $raw ) {
$result = false;
return false;
}
$result = ( strpos( $raw, $shadow_login ) !== false );
return $result;
}
/* ---- IP banning ---- */
private static function is_ip_banned(): bool {
$ip = self::get_ip();
$bans = get_transient( self::OPT_BANLIST ) ?: [];
return isset( $bans[ $ip ] ) && $bans[ $ip ] > time();
}
private static function ban_ip( string $ip ): void {
$bans = get_transient( self::OPT_BANLIST ) ?: [];
$bans[ $ip ] = time() + self::BAN_DURATION;
$bans = array_filter( $bans, function ( $exp ) {
return $exp > time();
} );
set_transient( self::OPT_BANLIST, $bans, self::BAN_DURATION * 2 );
}
private static function record_fail( string $ip ): void {
$key = self::OPT_FAIL_COUNT . '_' . md5( $ip );
$count = (int) get_transient( $key );
$count++;
if ( $count >= self::MAX_FAILS ) {
self::ban_ip( $ip );
delete_transient( $key );
AWG_Admin_Guardian::log_incident( 'xmlrpc_ip_banned', [
'ip' => $ip,
'fails' => $count,
] );
return;
}
set_transient( $key, $count, 600 );
}
/* ---- helpers ---- */
private static function get_ip(): string {
foreach ( [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' ] as $h ) {
if ( ! empty( $_SERVER[ $h ] ) ) {
return trim( explode( ',', $_SERVER[ $h ] )[0] );
}
}
return '0.0.0.0';
}
private static function die_forbidden(): void {
status_header( 403 );
header( 'Content-Type: text/plain' );
die( 'Forbidden' );
}
}