get_route(); if ( 0 !== strpos( $route, '/wp/v2/users' ) ) { return $result; } $ip = Argus_Request_Inputs::client_ip(); if ( Argus_Ban_Engine::is_banned( $ip ) ) { Argus_Policy_Engine::deny_already_banned( $ip ); return $result; } $recent = self::recent_user_enumeration_hits( $ip ); if ( $recent >= 10 ) { $decision = Argus_Policy_Engine::evaluate( $ip, 'rest_abuse', array( 'route' => $route, 'recent_hits' => $recent ) ); Argus_Findings::record( 'account', 'high', array( 'what_happened' => sprintf( 'Unauthenticated user-enumeration probing from %s targeting %s', $ip, $route ), 'why_it_matters' => 'Repeated unauthenticated requests to the users endpoint are a well-known reconnaissance step -- enumerating valid usernames to fuel a subsequent brute-force run.', 'what_argus_found' => sprintf( '%d unauthenticated requests to %s from %s. Action taken: %s (%s).', $recent, $route, $ip, $decision['action'], $decision['observation_only'] ? 'observed only, MONITOR mode' : 'enforced' ), 'when_it_happened' => current_time( 'mysql' ), 'why_suspicious' => 'A real client has no reason to request this endpoint repeatedly without authenticating -- this pattern is consistent with an automated recon/scanning tool.', 'what_could_be_affected' => 'Enumerated usernames are commonly fed into a follow-up brute-force or credential-stuffing attack against wp-login.php.', 'what_should_you_do' => $decision['observation_only'] ? 'ARGUS is in MONITOR mode and did not block this. Review recent activity and switch to BLOCK mode once you are confident legitimate traffic is not being flagged.' : 'No action needed -- ARGUS already blocked this IP.', ), array( 'ip' => $ip, 'route' => $route, 'recent_hits' => $recent, 'decision' => $decision ) ); Argus_Policy_Engine::enforce_decision( $ip, 'rest_abuse', $decision, array( 'route' => $route, 'recent_hits' => $recent ) ); } else { Argus_Events::record( 'rest_user_enum_probe', 'low', 'Unauthenticated request to ' . $route, array( 'route' => $route ), $ip ); } return $result; }, 10, 3 ); } protected static function recent_user_enumeration_hits( $ip ) { global $wpdb; $table = Argus_DB::table( 'events' ); $since = gmdate( 'Y-m-d H:i:s', time() - 300 ); return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE event_type = %s AND ip = %s AND created_at >= %s", // phpcs:ignore 'rest_user_enum_probe', $ip, $since ) ); } }