mirror of
https://gitlab.com/ArkHost/WP-Security-Pack.git
synced 2026-09-19 17:37:30 +02:00
v1.0
This commit is contained in:
@@ -1,326 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* IP access control for WP Security Pack.
|
||||
*
|
||||
* @package WP_Security_Pack
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* IP control class for whitelist/blacklist management.
|
||||
*/
|
||||
class WPSP_IP_Control {
|
||||
|
||||
/**
|
||||
* Cached whitelist IPs.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $whitelist_cache = null;
|
||||
|
||||
/**
|
||||
* Cached blacklist IPs.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $blacklist_cache = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Run IP check immediately (constructor runs during init).
|
||||
// This blocks blacklisted IPs from accessing the entire website.
|
||||
$this->check_ip_access();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check IP access on every request.
|
||||
*/
|
||||
public function check_ip_access() {
|
||||
$ip = WPSP_Helper::get_client_ip();
|
||||
|
||||
if ( ! $ip ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always allow whitelisted IPs.
|
||||
if ( $this->is_whitelisted( $ip ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Block blacklisted IPs.
|
||||
if ( $this->is_blacklisted( $ip ) ) {
|
||||
WPSP_Activity_Log::log( WPSP_Activity_Log::EVENT_IP_BLOCKED, $ip, null, __( 'IP blacklisted', 'wp-security-pack' ) );
|
||||
$this->block_access( __( 'Your IP address has been blocked.', 'wp-security-pack' ) );
|
||||
}
|
||||
|
||||
// Check auto-blocked IPs.
|
||||
if ( $this->is_auto_blocked( $ip ) ) {
|
||||
WPSP_Activity_Log::log( WPSP_Activity_Log::EVENT_IP_BLOCKED, $ip, null, __( 'IP auto-blocked', 'wp-security-pack' ) );
|
||||
$this->block_access( __( 'Your IP address has been temporarily blocked due to suspicious activity.', 'wp-security-pack' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if IP is whitelisted.
|
||||
*
|
||||
* @param string $ip IP address to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_whitelisted( $ip ) {
|
||||
$whitelist = $this->get_whitelist();
|
||||
return WPSP_Helper::ip_matches_rules( $ip, $whitelist );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if IP is blacklisted.
|
||||
*
|
||||
* @param string $ip IP address to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_blacklisted( $ip ) {
|
||||
$blacklist = $this->get_blacklist();
|
||||
return WPSP_Helper::ip_matches_rules( $ip, $blacklist );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if IP is auto-blocked (temporary block from failed logins).
|
||||
*
|
||||
* @param string $ip IP address to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_auto_blocked( $ip ) {
|
||||
global $wpdb;
|
||||
|
||||
$table = WPSP_DB::get_lockout_table();
|
||||
$max_attempts = (int) WP_Security_Pack::get_setting( 'login_max_attempts', 5 );
|
||||
$lockout_minutes = (int) WP_Security_Pack::get_setting( 'login_lockout_duration', 15 );
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$lockout = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT * FROM {$table} WHERE ip_address = %s",
|
||||
$ip
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $lockout ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if IP has reached max attempts and is still within lockout window.
|
||||
if ( (int) $lockout->failed_attempts >= $max_attempts ) {
|
||||
// Calculate if lockout is still active based on updated_at + duration.
|
||||
$updated_time = strtotime( $lockout->updated_at );
|
||||
$lockout_expires = $updated_time + ( $lockout_minutes * 60 );
|
||||
$current_time = time();
|
||||
|
||||
if ( $current_time < $lockout_expires ) {
|
||||
return true; // Still locked out.
|
||||
}
|
||||
}
|
||||
|
||||
// Also check lockout_until for honeypot/manual blocks (stored as Unix timestamp).
|
||||
if ( ! empty( $lockout->lockout_until ) && is_numeric( $lockout->lockout_until ) ) {
|
||||
if ( time() < (int) $lockout->lockout_until ) {
|
||||
return true; // Still locked out via lockout_until.
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whitelist IPs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_whitelist() {
|
||||
if ( null === $this->whitelist_cache ) {
|
||||
$whitelist_text = WP_Security_Pack::get_setting( 'ip_whitelist', '' );
|
||||
$this->whitelist_cache = WPSP_Helper::parse_ip_list( $whitelist_text );
|
||||
}
|
||||
return $this->whitelist_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blacklist IPs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_blacklist() {
|
||||
if ( null === $this->blacklist_cache ) {
|
||||
$blacklist_text = WP_Security_Pack::get_setting( 'ip_blacklist', '' );
|
||||
$this->blacklist_cache = WPSP_Helper::parse_ip_list( $blacklist_text );
|
||||
}
|
||||
return $this->blacklist_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear internal caches (call after modifying whitelist/blacklist).
|
||||
*/
|
||||
public function clear_cache() {
|
||||
$this->whitelist_cache = null;
|
||||
$this->blacklist_cache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add IP to auto-block list (temporary lockout).
|
||||
*
|
||||
* @param string $ip IP address.
|
||||
* @param int $duration Duration in minutes.
|
||||
* @param string $reason Reason for blocking.
|
||||
* @return bool
|
||||
*/
|
||||
public function auto_block_ip( $ip, $duration = 15, $reason = '' ) {
|
||||
global $wpdb;
|
||||
|
||||
$table = WPSP_DB::get_lockout_table();
|
||||
// Store as Unix timestamp for consistency with lockout_ip().
|
||||
$lockout_until = time() + ( $duration * 60 );
|
||||
|
||||
// Check if already exists.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$existing = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT id FROM {$table} WHERE ip_address = %s",
|
||||
$ip
|
||||
)
|
||||
);
|
||||
|
||||
if ( $existing ) {
|
||||
// Update existing record.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
return false !== $wpdb->update(
|
||||
$table,
|
||||
array(
|
||||
'lockout_until' => $lockout_until,
|
||||
'updated_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( 'ip_address' => $ip ),
|
||||
array( '%d', '%s' ),
|
||||
array( '%s' )
|
||||
);
|
||||
}
|
||||
|
||||
// Insert new record.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
return false !== $wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'ip_address' => $ip,
|
||||
'failed_attempts' => 0,
|
||||
'lockout_until' => $lockout_until,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
'updated_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%s', '%d', '%d', '%s', '%s' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove IP from auto-block list.
|
||||
*
|
||||
* @param string $ip IP address.
|
||||
* @return bool
|
||||
*/
|
||||
public function unblock_ip( $ip ) {
|
||||
global $wpdb;
|
||||
|
||||
$table = WPSP_DB::get_lockout_table();
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
return false !== $wpdb->delete(
|
||||
$table,
|
||||
array( 'ip_address' => $ip ),
|
||||
array( '%s' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all currently blocked IPs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_blocked_ips() {
|
||||
global $wpdb;
|
||||
|
||||
$table = WPSP_DB::get_lockout_table();
|
||||
$max_attempts = (int) WP_Security_Pack::get_setting( 'login_max_attempts', 5 );
|
||||
$lockout_minutes = (int) WP_Security_Pack::get_setting( 'login_lockout_duration', 15 );
|
||||
$current_time = time();
|
||||
|
||||
// Get all lockout records.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$lockouts = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT * FROM {$table} ORDER BY updated_at DESC"
|
||||
);
|
||||
|
||||
$blocked = array();
|
||||
foreach ( $lockouts as $lockout ) {
|
||||
$is_blocked = false;
|
||||
$lockout_expires = 0;
|
||||
|
||||
// Check login-based lockout (failed_attempts >= max and within time window).
|
||||
if ( (int) $lockout->failed_attempts >= $max_attempts ) {
|
||||
$updated_time = strtotime( $lockout->updated_at );
|
||||
$lockout_expires = $updated_time + ( $lockout_minutes * 60 );
|
||||
if ( $current_time < $lockout_expires ) {
|
||||
$is_blocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check honeypot/manual lockout (lockout_until as Unix timestamp).
|
||||
if ( ! empty( $lockout->lockout_until ) && is_numeric( $lockout->lockout_until ) ) {
|
||||
$lockout_until_ts = (int) $lockout->lockout_until;
|
||||
if ( $current_time < $lockout_until_ts ) {
|
||||
$is_blocked = true;
|
||||
// Use the later expiry time.
|
||||
if ( $lockout_until_ts > $lockout_expires ) {
|
||||
$lockout_expires = $lockout_until_ts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $is_blocked ) {
|
||||
// Add computed lockout_until field for display.
|
||||
$lockout->lockout_until = gmdate( 'Y-m-d H:i:s', $lockout_expires );
|
||||
$blocked[] = $lockout;
|
||||
}
|
||||
}
|
||||
|
||||
return $blocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block access and exit.
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*/
|
||||
private function block_access( $message ) {
|
||||
status_header( 403 );
|
||||
nocache_headers();
|
||||
|
||||
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
|
||||
wp_send_json_error( array( 'message' => $message ), 403 );
|
||||
}
|
||||
|
||||
// Simple blocked page.
|
||||
wp_die(
|
||||
esc_html( $message ),
|
||||
esc_html__( 'Access Denied', 'wp-security-pack' ),
|
||||
array(
|
||||
'response' => 403,
|
||||
'back_link' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user