$scan, 'backup_file' => $scan, 'config_file' => $scan, 'xmlrpc_login' => array( __( 'Brute force login', 'rabbitproof' ), __( 'A bot trying to sign in by guessing passwords. Refused before it reached your sign-in.', 'rabbitproof' ) ), 'xmlrpc_off' => array( __( 'Endpoint probing', 'rabbitproof' ), __( 'Sent to XML-RPC, which is switched off on this website. Refused.', 'rabbitproof' ) ), 'user_enum' => array( __( 'Endpoint probing', 'rabbitproof' ), __( 'Looking for your usernames, which bots collect to guess passwords. Refused.', 'rabbitproof' ) ), 'version_file' => $scan, ); } public static function dir() { $u = wp_upload_dir( null, false ); return rtrim( (string) $u['basedir'], '/\\' ) . '/rabbitproof'; } public static function file() { $t = (string) get_option( self::TOKEN, '' ); if ( 1 !== preg_match( '/^[0-9a-f]{16}$/', $t ) ) { $t = bin2hex( random_bytes( 8 ) ); update_option( self::TOKEN, $t, false ); } return self::dir() . '/refused-' . $t . '.log'; } public static function prepare() { $d = self::dir(); if ( ! wp_mkdir_p( $d ) ) { return ''; } if ( ! file_exists( $d . '/index.php' ) ) { file_put_contents( $d . '/index.php', "\nDeny from all\n\n" ); } return self::file(); } public static function add( $kind, $ip, $path ) { $f = self::prepare(); if ( '' === $f || ! isset( self::kinds()[ $kind ] ) ) { return; } if ( is_file( $f ) && filesize( $f ) > 524288 ) { return; } $clean = function ( $v, $len ) { return substr( (string) preg_replace( '/[\x00-\x1f\x7f\t]/', '', (string) $v ), 0, $len ); }; file_put_contents( $f, implode( "\t", array( time(), $kind, $clean( $ip, 45 ), '', '', $clean( $path, 200 ) ) ) . "\n", FILE_APPEND | LOCK_EX ); } public static function entries( $since ) { $since = max( (int) $since, time() - self::DAYS * DAY_IN_SECONDS ); $f = self::file(); $proxy = (bool) Rabbitproof_Options::get( 'proxy' ); $kinds = self::kinds(); $out = array(); foreach ( array( $f . '.1', $f ) as $part ) { if ( ! is_readable( $part ) ) { continue; } foreach ( preg_split( '/\n/', (string) file_get_contents( $part ), -1, PREG_SPLIT_NO_EMPTY ) as $line ) { $c = explode( "\t", $line ); if ( count( $c ) < 6 || (int) $c[0] < $since || ! isset( $kinds[ $c[1] ] ) ) { continue; } $server = array( 'REMOTE_ADDR' => $c[2] ); if ( '' !== $c[3] ) { $server['HTTP_CF_CONNECTING_IP'] = $c[3]; } if ( '' !== $c[4] ) { $server['HTTP_X_FORWARDED_FOR'] = $c[4]; } $out[] = array( (int) $c[0], $c[1], Rabbitproof_Ip::visitor( $server, $proxy ), '' !== $c[5] ? $c[5] : '/' ); } } usort( $out, function ( $a, $b ) { return $a[0] - $b[0]; } ); return $out; } public static function rows( $since = 0 ) { $kinds = self::kinds(); $g = array(); $entries = self::entries( $since ); $runs = array(); $run = array(); foreach ( $entries as $i => $e ) { $scan = 'secret_file' === $e[1] || 'backup_file' === $e[1] || 'config_file' === $e[1]; if ( $scan && $run && $e[0] - $entries[ end( $run ) ][0] <= self::SCAN_GAP ) { $run[] = $i; continue; } $runs[] = $run; $run = $scan ? array( $i ) : array(); } $runs[] = $run; $in = array(); foreach ( $runs as $r ) { $ips = array(); foreach ( $r as $i ) { $ips[ $entries[ $i ][2] ] = true; } if ( count( $ips ) < 2 ) { continue; } $tried = array(); foreach ( array_slice( array_reverse( $r ), 0, 6 ) as $i ) { $tried[] = array( 't' => $entries[ $i ][0], 'path' => $entries[ $i ][3] ); } $last = $entries[ end( $r ) ]; $g[] = array( 't' => $last[0], 'first' => $entries[ $r[0] ][0], 'n' => count( $r ), 'addrs' => count( $ips ), 'tried' => $tried, 'kind' => 'visit', 'why' => $kinds['secret_file'][0], 'about' => $kinds['secret_file'][1], 'form' => '', 'path' => $last[3], 'cc' => '', 'ref' => '', 'outcome' => 'blocked', 'blocked' => true, 'id' => 0, 'let' => false, 'again' => false, 'local' => true ); foreach ( $r as $i ) { $in[ $i ] = true; } } foreach ( $entries as $i => $e ) { if ( isset( $in[ $i ] ) ) { continue; } $k = md5( $e[2] . '|' . $kinds[ $e[1] ][0] . '|' . (int) floor( $e[0] / HOUR_IN_SECONDS ) ); if ( ! isset( $g[ $k ] ) ) { $g[ $k ] = array( 't' => 0, 'n' => 0, 'kind' => 'visit', 'why' => $kinds[ $e[1] ][0], 'about' => $kinds[ $e[1] ][1], 'form' => '', 'path' => '/', 'cc' => '', 'ref' => '', 'outcome' => 'blocked', 'blocked' => true, 'id' => 0, 'let' => false, 'again' => false, 'local' => true ); } $g[ $k ]['t'] = $e[0]; $g[ $k ]['n'] += 1; $g[ $k ]['path'] = $e[3]; } $rows = array_values( $g ); usort( $rows, function ( $a, $b ) { return $b['t'] - $a['t']; } ); return $rows; } public static function counts( $since ) { $kinds = self::kinds(); $seen = array(); $out = array(); foreach ( self::entries( $since ) as $e ) { $label = $kinds[ $e[1] ][0]; $k = gmdate( 'Y-m-d', $e[0] ) . '|' . $e[2] . '|' . $label; if ( isset( $seen[ $k ] ) ) { continue; } $seen[ $k ] = true; $out[ $label ] = ( isset( $out[ $label ] ) ? $out[ $label ] : 0 ) + 1; } return $out; } public static function remove_all() { $d = self::dir(); foreach ( (array) glob( $d . '/*' ) as $f ) { if ( is_string( $f ) && is_file( $f ) ) { wp_delete_file( $f ); } } foreach ( array( '/.htaccess' ) as $hidden ) { if ( is_file( $d . $hidden ) ) { wp_delete_file( $d . $hidden ); } } if ( is_dir( $d ) ) { global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if ( WP_Filesystem() && $wp_filesystem ) { $wp_filesystem->rmdir( $d, true ); } } delete_option( self::TOKEN ); } }