Анализатор атак на сайт

Это новая функция в плагине WatchMan-Site7 (v.3.0.3). Я хочу создать некое интеллектуальное ядро, которое анализировало каждый вид и характер атаки на сайт и принимало решение по противодействию этим атакам. Естественно – атаки со временем меняются, совершенствуются. Следовательно – будет изменятся и анализатор атак (Attack analyzer).

Перед написанием этой статьи я подумал: а стоит ли описывать методы противодействия хакерам, “помогая” тем самым им совершенствовать свои атаки. Подумав не раз, я все-таки решил написать. И вот почему:
1. Противодействие анти-хакеров и хакеров – будет всегда. Каждая сторона совершенствует свои методы. Это похоже на бег по вертикали. Пока кто-то первым не устанет.
2. Каждый хакер, как бы он этого не хотел – оставляет следы. Задача анти-хакера – найти их и как можно больше.
3. Код плагина – открытый (Open source). Поэтому, при желании злоумышленник может изучить методы противодействия ему всегда.
Еще до создания Attack analyzer в плагине WatchMan-Site7 был написан код, считывающий информацию о посетителе сайта:

/**
 * Forms IP data from global variables.
 *
 * @return string data of IP visitor.
 */
private function wms7_get_user_ip() {
	// We get the headers or use the global SERVER.
	if ( function_exists( 'apache_request_headers' ) ) {
		$headers = apache_request_headers();
		$list    = '---Apache request headers-------------<br>';
		foreach ( $headers as $header => $value ) {
			$list = $list . $header . ': ' . $value . '<br>';
		}
	} else {
		$headers = filter_input_array( INPUT_SERVER, FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
		$list    = '---$_SERVER request headers-----------<br>';
		foreach ( $headers as $header => $value ) {
			if ( substr( $header, 0, 5 ) === 'HTTP_' ) {

				$header = substr( $header, 5 );
				$header = str_replace( '_', ' ', $header );
				$header = strtolower( $header );
				$header = ucwords( $header );
				$header = str_replace( ' ', '-', $header );

				$list = $list . $header . ': ' . $value . '<br>';
			}
		}
	}
	$the_ip = '';
	// We get the redirected IP-address, if it exists.
	$_x_forwarded_for = filter_input( INPUT_SERVER, 'X-Forwarded-For', FILTER_SANITIZE_STRING );
	if ( $_x_forwarded_for ) {
		$the_ip .= 'X-Forwarded-For = ' . $_x_forwarded_for . '<br>';
	}
	$_http_x_forwarded_for = filter_input( INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_SANITIZE_STRING );
	if ( $_http_x_forwarded_for ) {
		$the_ip .= 'HTTP_X_FORWARDED_FOR = ' . $_http_x_forwarded_for . '<br>';
	}
	$_http_x_forwarded = filter_input( INPUT_SERVER, 'HTTP_X_FORWARDED', FILTER_SANITIZE_STRING );
	if ( $_http_x_forwarded ) {
		$the_ip .= 'HTTP_X_FORWARDED = ' . $_http_x_forwarded . '<br>';
	}
	$http_x_cluster_client_ip = filter_input( INPUT_SERVER, 'HTTP_X_CLUSTER_CLIENT_IP', FILTER_SANITIZE_STRING );
	if ( $http_x_cluster_client_ip ) {
		$the_ip .= 'HTTP_X_CLUSTER_CLIENT_IP = ' . $http_x_cluster_client_ip . '<br>';
	}
	$_http_forwarded_for = filter_input( INPUT_SERVER, 'HTTP_FORWARDED_FOR', FILTER_SANITIZE_STRING );
	if ( $_http_forwarded_for ) {
		$the_ip .= 'HTTP_FORWARDED_FOR = ' . $_http_forwarded_for . '<br>';
	}
	$_http_forwarded = filter_input( INPUT_SERVER, 'HTTP_FORWARDED', FILTER_SANITIZE_STRING );
	if ( $_http_forwarded ) {
		$the_ip .= 'HTTP_FORWARDED = ' . $_http_forwarded . '<br>';
	}
	$_http_client_ip = filter_input( INPUT_SERVER, 'HTTP_CLIENT_IP', FILTER_SANITIZE_STRING );
	if ( $_http_client_ip ) {
		$the_ip .= 'HTTP_CLIENT_IP = ' . $_http_client_ip . '<br>';
	}
	$_remote_addr = filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_STRING );
	if ( $_remote_addr ) {
		$the_ip .= 'REMOTE_ADDR = ' . $_remote_addr;
	}
	return $list . '---' . $the_ip . '---';
}

Результат работы этого кода:

---Apache request headers-------------
Host: adminkov.bcr.by
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://adminkov.bcr.by/still/
Cookie: wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_3ccfd98e86faaa1abd5f45846aa6bc93=
user1%7C1541751799%7C6nLDA3iptBYHTekh0n47KYuzjPiEIcrsYxqgh3dmtCO%7Caec6e0f4028811976212e4b7cdc2cb465ebc125e8b50ce1cc8eb05d683cd3e37
Connection: keep-alive
---REMOTE_ADDR = 93.115.86.4---
---Information about the Cookie visitor---
wordpress_test_cookie=WP Cookie check
wordpress_logged_in_3ccfd98e86faaa1abd5f45846aa6bc93=
user1|1541751799|6nLDA3iptBYHTekh0n47KYuzjPiEIcrsYxqgh3dmtCO|aec6e0f4028811976212e4b7cdc2cb465ebc125e8b50ce1cc8eb05d683cd3e37

Помимо этого – плагин WatchMan-Site7 собирает данные о геолокации посетителя сайта и его провайдера.
🙂 Возвращаюсь к описанию Attack analyzer.
1. Замечено, что злоумышленник готовя атаку Brute force, сначала сканирует перечень зарегистрированных пользователей с ролью: author или administrator. Иногда, быает, что это “чудо в штанишках” производит это действие с одного IP. В этом случае, вероятность поймать истинный адрес злоумышленника (а не прокси) вырастает многократно.
2. Насколько мне известно: разработчики WordPress готовы отказаться от использования xmlrpc.php и создают нечто другое в архитектуре этой CMS. Предвидя это я уже ввел в Attack analyzer – блокирование IP посетителей, которые обращаются к этому файлу. Более того, за три года изучения CMS WordPress я не видел где-либо активного использования xmlrpc.php Хлопот от него много, а пользы мало.
3. При первой же попытке залогиниться под именем администратора или автора – произойдет блокировка IP посетителя.
Важно: чтобы не забанить самого себя (администратора, автора) прежде всего пропишите IP адреса, исключенные для регистрации посещения (п.2 Do not register visits for:) в настройках плагина !
Ниже приводится код анализатора атак (Attack analyzer). Он еще мал, но со временем вырастет.

<?php
/**
 * Description: Analyzes attacks targeting a website.
 *
 * PHP version 7.4
 *
 * @category    wms7-attack-analyzer.php
 * @package     WatchMan-Site7
 * @author      Oleg Klenitskiy <klenitskiy.oleg@mail.ru>
 * @version     4.0.0
 * @license     GPLv2 or later
 */

if ( ! defined( "ABSPATH" ) ) {
	exit();
}
/**
 * Block a visitor.
 *
 * @param string $ban_notes Ban notes.
 * @param boolean $ban_user_agent Ban user agent.
 */
function wms7_block_visitor( $ban_notes, $ban_user_agent ) {
	// baned user ip.
	$arr        = array(
		"ban_start_date" => date( "Y-m-d" ),
		"ban_end_date"   => date( "Y-m-d", ( strtotime( "next week", strtotime( date( "Y-m-d" ) ) ) ) ),
		"ban_message"    => "Attack analyzer",
		"ban_notes"      => $ban_notes,
		"ban_login"      => false,
		"ban_user_agent" => $ban_user_agent,
	);
	$black_list = wp_json_encode( $arr );

	return $black_list;
}

/**
 * Analyzes the nature of the site visit.
 *
 * @param string $_log             Login.
 * @param string $page_visit       Page visit.
 * @param string $_http_user_agent User agent.
 * @param string $user_ip          User IP.
 */
function wms7_attack_analyzer( $_log, $page_visit, $_http_user_agent, $user_ip ) {
	$_http_user_agent = filter_input( INPUT_SERVER, "HTTP_USER_AGENT", FILTER_SANITIZE_STRING );
	$black_list       = "";
	$ban_user_agent   = false;

	if ( "" == get_option( "user_agent_attack" ) ) {
		update_option( "user_agent_attack", $_http_user_agent );
		update_option( "user_ip_attack", $user_ip );
	} else {
		if ( get_option( "user_agent_attack" ) == $_http_user_agent &&
			get_option( "user_ip_attack" ) !== $user_ip ) {
			$ban_user_agent = true;
			// Delete item from options.
			update_option( "user_agent_attack", "" );
			update_option( "user_ip_attack", "" );
		} else {
			$ban_user_agent = false;
			update_option( "user_agent_attack", $_http_user_agent );
			update_option( "user_ip_attack", $user_ip );
		}
	}
	// Если пустой User Agent - баним на 1 неделю.
	if ( "" == trim( $_http_user_agent ) ) {
		// Insert to black_list.
		$black_list = wms7_block_visitor( "Empty User Agent", $ban_user_agent );
		// Insert to htaccess.
		wms7_agent_ins_to_file( "" );
		wms7_ip_ins_to_file( $user_ip );
		update_option( "user_agent_attack", "" );

		return $black_list;
	}
	// Если доступ через xmlrpc - баним на 1 неделю.
	if ( strpos( $page_visit, "xmlrpc" ) ) {
		// Insert to black_list.
		$black_list = wms7_block_visitor( "Access via xmlrpc", $ban_user_agent );
		// Insert to htaccess.
		wms7_ip_ins_to_file( $user_ip );
		// Insert user_agent into .htaccess.
		if ( $ban_user_agent && "" !== trim( $_http_user_agent ) ) {
			wms7_agent_ins_to_file( $_http_user_agent );
		}
		return $black_list;
	}
	// Если Brute force - баним IP.
	if ( $_log ) {
		$user_info = get_user_by( "login", $_log );
		if ( $user_info ) {
			$user_role = array_shift( $user_info->roles );
			if ( ( "administrator" == $user_role ) || ( "author" == $user_role ) ) {
				// Insert to black_list.
				$black_list = wms7_block_visitor( "Brute force: " . $user_role, $ban_user_agent );
				// Insert to htaccess.
				wms7_ip_ins_to_file( $user_ip );
				// Insert user_agent into .htaccess.
				if ( $ban_user_agent && "" !== trim( $_http_user_agent ) ) {
					wms7_agent_ins_to_file( $_http_user_agent );
				}
				return $black_list;
			}
		}
	}
	// Если Сканирование структуры сайта - баним IP.
	if ( strpos( $page_visit, "wp-content" ) || strpos( $page_visit, "wp-includes" ) ) {
		// Insert to black_list.
		$black_list = wms7_block_visitor( "Scan the site structure", $ban_user_agent );
		// Insert to htaccess.
		wms7_ip_ins_to_file( $user_ip );
		// Insert user_agent into .htaccess.
		if ( $ban_user_agent && "" !== trim( $_http_user_agent ) ) {
			wms7_agent_ins_to_file( $_http_user_agent );
		}
		return $black_list;
	}
	// Если Сканирование авторов сайта - баним IP.
	if ( strpos( $page_visit, "author" ) || strpos( $page_visit, "/wp-json/wp/v2/users" ) ) {
		// Insert to black_list.
		$black_list = wms7_block_visitor( "Scan authors site", $ban_user_agent );
		// Insert to htaccess.
		wms7_ip_ins_to_file( $user_ip );
		// Insert user_agent into .htaccess.
		if ( $ban_user_agent && "" !== trim( $_http_user_agent ) ) {
			wms7_agent_ins_to_file( $_http_user_agent );
		}
		return $black_list;
	}

	return $black_list;
}

p.s.: Даже, если Вы забанили самого себя (любимого), то можете зайдя на свой хостинг по FTP, убрать блокирующую запись из htaccess. Но нужно это сделать достаточно быстро, т.к. работает крон-событие: wms7_htaccess. Это событие срабатывает 1 раз в 1 час. За этот промежуток времени нужно почистить поле Black List в нужной строке таблицы посещений основного экрана плагина WatchMan-Site7.
p.p.s: Крон-событие wms7_htaccess занимается тем, что приводит в соответствие записи о блокировках IP с параметрами блокировок (начало и окончание блокировки IP). В данном случае: если строка блокировки будет удалена из htaccess в ручную, то в течении 1 часа она будет восстановлена, если дата окончания блокировки еще не наступила.