Extra Systems Ban Software (ESBANS)

ES-RDP

Module rdp-log

The rdp-log module of our original Windows remote desktop protection system consists of three files: two main scripts (rdp-log.ps1 for PowerShell and rdp-log.php for PHP) and an auxiliary batch file (rdp-log.bat). The scripts sequentially perform secondary detection and blocking of repeat offenders and malicious IP subnets. The Windows Task Scheduler calls the batch file every hour to execute the two main scripts.

The rdp-log.bat file in our system looks like this:

powershell.exe -File "C:\Scripts\rdp_log.ps1" 2>nul
"C:\Program Files (x86)\PHP\php.exe" C:\Scripts\rdp_log.php 2>nul

Naturally, you will need to replace C:\Scripts with the path to your installation of our system and, if necessary, adjust the path to the php.exe file. The main file for this module, rdp-log.ps1, looks like this:

# Load shared variables (paths to MySQL, login, password, etc.)
. "$PSScriptRoot\common.ps1"

# 1. CLEANUP EXPIRED RULES
foreach ($Key in $BanTypes.Keys) {
    # 1. Set variables for the current type
    $FirewallPrefix = $FirewallPrefixes[$Key]
    $BanType        = $BanTypes[$Key]
    $BanTime        = $BanTimes[$Key]
    $ExpirationDate = (Get-Date).AddDays(-$BanTime)

    # 2. Search all rules of this type
    $AllRules = Get-NetFirewallRule -DisplayName "$FirewallPrefix*" -ErrorAction SilentlyContinue

    # 3. Filter and remove expired rules
    $OldRules = $AllRules | Where-Object {
        if ($_.DisplayName -match "(\d{4}-\d{2}-\d{2})") {
            [DateTime]$Matches[1] -lt $ExpirationDate
        } else { $false }
    }

    if ($OldRules) {
        $OldRules | Remove-NetFirewallRule
    }

}

# cleaning logs
if ((Get-Date).Hour -lt 3) {
	$db_max_days_size = 7
	$Query = "delete from ban_stats where ban_date < CURDATE() - INTERVAL $db_max_days_size DAY;"
	& $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$Query" 2>$null

	$db_max_days_size = 48
	$Query = "delete from days_data where ban_date < CURDATE() - INTERVAL $db_max_days_size DAY;"
	& $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$Query" 2>$null

	$db_max_days_size = $LongSlice
	if ($NetSlice -gt $db_max_days_size) { $db_max_days_size = $NetSlice }
	$db_max_days_size = $db_max_days_size * 2
	$Query = "delete from ban_log where ban_time < CURDATE() - INTERVAL $db_max_days_size DAY;"
	& $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$Query" 2>$null

	$db_max_days_size = $BotSlice * 2
	$Query = "delete from net_log where ban_time < CURDATE() - INTERVAL $db_max_days_size DAY;"
	& $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$Query" 2>$null
}

# Record active ban counts per category
foreach ($Key in $BanTypes.Keys) {

    # 1. Get the prefix and BanType for current iteration
    $CurrentPrefix = $FirewallPrefixes[$Key]
    $BanType       = $BanTypes[$Key]

    # 2. Count active rules for specific type
    $RulesCount = @(Get-NetFirewallRule -DisplayName "$CurrentPrefix*" -ErrorAction SilentlyContinue).Count

    # 3. Construct SQL query
    $Query = "INSERT INTO ban_stats (ban_count, ban_type) VALUES ($RulesCount, $BanType);"

    # 4. Execute query
    & $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$Query" 2>$null
    
}

# Based on the initial bans, we generate higher-level blocks

$CurDate = Get-Date -Format "yyyy-MM-dd"

# Query for repeat offenders
$LongQuery = "SELECT INET_NTOA(ban_addr), COUNT(*) as cnt FROM ban_log WHERE ban_time > NOW() - INTERVAL $LongSlice DAY GROUP BY ban_addr HAVING cnt >= $LongLimit;"
$Recidivists = @(& $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$LongQuery" --skip-column-names)
$FirewallPrefix = $FirewallPrefixes['long']
foreach ($Row in $Recidivists) {
	$Columns = $Row -split "\t"
	$IP = $Columns[0].Trim()
	$TechnicalName = $FirewallPrefix + $IP.Replace('.', '_')
        if (-not (Get-NetFirewallRule -Name $TechnicalName -ErrorAction SilentlyContinue)) {
		$Description = "Type: recidive. Created: $(Get-Date)"
		New-NetFirewallRule -Name $TechnicalName -DisplayName "$FirewallPrefix$CurDate`_$IP" -Direction Inbound -Action Block -RemoteAddress $IP -Description $Description | Out-Null
	}
}

# Query for hostile networks
$NetQuery = "SELECT INET_NTOA(ban_subnet), COUNT(DISTINCT ban_addr) as unique_ips FROM ban_log WHERE ban_time > NOW() - INTERVAL $NetSlice DAY GROUP BY ban_subnet HAVING unique_ips >= $NetLimit;"
$HostileNets = @(& $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$NetQuery" --skip-column-names)
$FirewallPrefix = $FirewallPrefixes['net']
foreach ($Row in $HostileNets) {
	$Subnet = ($Row -split "\t")[0].Trim()
	$TechnicalName = $FirewallPrefix + $Subnet.Replace('.', '_')
        if (-not (Get-NetFirewallRule -Name $TechnicalName -ErrorAction SilentlyContinue)) {
		$Description = "Type: botnet. Created: $(Get-Date)"
		$FullSubnet = "$Subnet/24"
		New-NetFirewallRule -Name $TechnicalName -DisplayName "$FirewallPrefix$CurDate`_$Subnet" -Direction Inbound -Action Block -RemoteAddress $FullSubnet -Description $Description | Out-Null

		# Log event for recurrence tracking
		$LogQuery = "INSERT INTO net_log (net_addr) VALUES (INET_ATON('$Subnet'));"
		& $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$LogQuery" 2>$null
	}
}

# Query for repeat offender networks (those that constantly end up in the net_log)
$BotQuery = "SELECT INET_NTOA(net_addr), COUNT(*) as cnt FROM net_log WHERE ban_time > NOW() - INTERVAL $BotSlice DAY GROUP BY net_addr HAVING cnt >= $BotLimit;"
$BotNets = @(& $mysql_path --user=$mysql_user --password=$mysql_password --database=$mysql_dbName --execute="$BotQuery" --skip-column-names)
$FirewallPrefix = $FirewallPrefixes['bot']
foreach ($Row in $BotNets) {
    $Columns = $Row -split "\t"
    $Subnet = $Columns[0].Trim()
    $TechnicalName = $FirewallPrefix + $Subnet.Replace('.', '_')
    if (-not (Get-NetFirewallRule -Name $TechnicalName -ErrorAction SilentlyContinue)) {
        $Description = "Type: hell. Created: $(Get-Date)"
        $FullSubnet = "$Subnet/24"
        New-NetFirewallRule -Name $TechnicalName -DisplayName "$FirewallPrefix$CurDate`_$Subnet" -Direction Inbound -Action Block -RemoteAddress $FullSubnet -Description $Description | Out-Null
    }
}

As this code shows, the script uses the general system settings from the common.ps1 file and logs its activity in the ban_log table. This logging is necessary for other modules that perform secondary banning (e.g., repeat offenders, IP blocks, etc.).

The rdp-log.php file in our system looks like this:

<?php
	$dbUser     = "*******";
	$dbPassword = "*******";
	$dbName     = "rdp_ban";
	$conn = mysqli_connect("localhost","$dbUser","$dbPassword", "$dbName");
	$mysql_datetime = date("Y-m-d");
	$ban_types['short'] = 1;
	$ban_types['long']  = 2;
	$ban_types['net']   = 3;
	$ban_types['bot']   = 4;
	foreach ($ban_types as $ban_type)
	{
		$step_result = mysqli_query($conn, "select AVG(ban_count) as ban_avg from ban_stats where ban_type = $ban_type and ban_date > '$mysql_datetime'");
		$step_row = mysqli_fetch_assoc($step_result);
		$ban_avg = intval($step_row['ban_avg']);
		$step_result = mysqli_query($conn, "select count(*) as data_count from days_data where ban_type = $ban_type and ban_date='$mysql_datetime'");
		$step_row = mysqli_fetch_assoc($step_result);
		$data_count = $step_row['data_count'];
		if ($data_count == 0)
		{
			mysqli_query($conn, "insert into days_data(ban_date, ban_count, ban_type) values('$mysql_datetime', $ban_avg, $ban_type)");
		} else {
			mysqli_query($conn, "update days_data set ban_count=$ban_avg where ban_type = $ban_type and ban_date='$mysql_datetime'");
		}
	}
?>

Naturally, you must replace the asterisks at the beginning of this file with your actual database login and password.

The content of this page is also available in Russian.


© Extra Systems, 2026 Extra Web Top