Windowsパソコンのブルートフォースアタック対策をPowerShell＋VBScriptでやってみる

# 概要

Windowsのイベントビューアーでセキュリティログを確認したところイベントID:4625が多発していました。
たぶんパスワードの総当たりのブルートフォースアタック(Brute-force attack)の痕跡かと思います。
今のところパスワードが解析され、パソコンに侵入された形跡は無いですが、このままではなんか嫌なので対策したいと思いました。

UTMやルーターで海外IPは全部ブロックすれば早いのですが、同じネットワーク内に本ブログの公開用ウェブサーバーがいたり、スマホのアプリが通信出来ないとかになったもダメなので、攻撃を受けているパソコン側で対策をまずはやっていきたいと思い、PowerShell＋VBSでタスクスケジューラーを5分毎に確認して、イベントID:4625の接続元IPをファイヤーウォールでブロックする処理を作りました。

# 処理の詳細
処理の詳細については下記ページに記載しています。

https://tech.yuzuyu.mobi/archives/443

https://tech.yuzuyu.mobi/archives/450

https://tech.yuzuyu.mobi/archives/459

https://tech.yuzuyu.mobi/archives/502

# コード
タスクスケジューラーでPS1ファイルを実行すると、PowerShellウインドウが毎回表示されて邪魔なので、タスクスケジューラーで実行されるトリガーのVBSファイルと、そのVBS内で呼び出されるPS1ファイルに分けています。
## タスクスケジューラーで実行されるトリガーのVBSファイル

```vbs:TrigerForPS1_AutoUpdate_FWBlockList.vbs
'====================================
' ps1の起動用スクリプト TrigerForPS1_AutoUpdate_FWBlockList.vbs
' powershell実行時のウインドウを表示させないため
' 
' TrigerForPS1_AutoUpdate_FWBlockList.vbs
' AutoUpdate_FWBlockList
'====================================
Option Explicit

'FSOオブジェクト、Shellオブジェクト
Dim objFSO
Dim objWshShell

'VBSパス、VBS格納フォルダ、PS1パス
Dim strVBSPath
Dim strVBSFolder
Dim strPS1Path

'オブジェクト参照
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objWshShell = WScript.CreateObject("WScript.Shell")

'VBSパス、VBS格納フォルダ、PS1パス
strVBSPath = Wscript.ScriptFullName
strVBSFolder = objFSO.GetFile(StrVBSPath).ParentFolder
strPS1Path = strVBSFolder & "\AutoUpdate_FWBlockList.ps1"

'PS1起動オプション
Const OPT = "%Systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "

'PS1起動
objWshShell.Run OPT & strPS1Path,0,false

'オブジェクト解放
Set objFSO = Nothing
Set objWshShell = Nothing

'終了
Wscript.Quit
```
## VBS内で呼び出されるPS1ファイル
実行時から30分前までのログイン失敗履歴(EventID:4625)にある攻撃元IPを抽出して、３回以上攻撃してきてたらファイヤーウォールのルールに追加して以降はブロックする様にしています。
ファイヤーウォールのルールは日別で分けて作成し、命名規則は「BlockList日付」にしてあります。
また、ログも残すようにしてありますので、ソースが無駄に長いです。
ログは10世代分だけ残すようにしてあります。

```ps1:AutoUpdate_FWBlockList.ps1
# ----------------------------
# 定義
# ----------------------------
$Date    = Get-Date -Format yyyyMMdd
$FW_RuleName = "BlockList"+$Date

# EventLogの検索範囲(分単位)
$RangeMinutes = 30
$SearchEndTime   = $(Get-Date -Format "yyyy-MM-dd HH:mm")
$SearchStartTime = $(Get-Date (Get-Date).AddMinutes(-$RangeMinutes) -Format "yyyy-MM-dd HH:mm")

# 実行中Scriptのフルパス
$CurrentFileFullName = $MyInvocation.MyCommand.Path
# 実行中Scriptの絶対パス
$CurrentDirPath = Split-Path $MyInvocation.MyCommand.Path
# 実行中Scriptのファイル名
$CurrentFileName = $MyInvocation.MyCommand.Name
# ファイル名から拡張子を除去
$FileName_NoExt = [IO.Path]::GetFileNameWithoutExtension($CurrentFileName);
# ログファイル
$LogFile = ($CurrentDirPath+"\PSLog_"+$FileName_NoExt+"_"+$(Get-Date -Format "yyyyMMdd")+".log")

# ----------------------------
# ファンクション
# ----------------------------
# ログ出力ファンクション
Function LogOutput($LogFile, $LogData){
	$NowDate = $(Get-Date -Format "yyyy/MM/dd HH:mm:ss")
#	Write-Output ( $NowDate + " " + $LogData + "`r`n" ) >> $LogFile
	Write-Output ( $NowDate + " " + $LogData ) >> $LogFile
}

# ----------------------------
# 本処理開始
# ----------------------------
LogOutput $LogFile $($CurrentFileName+" : Start => "+$CurrentFileFullName)

# ----------------------------
# 同じ日に追加したルールがあるか確認
# ----------------------------
# 既存ルールにRemoteIPだけを追加する事が出来ないので
# 既存ルールのRemoteIPを取得しておく

$CurrentRule = netsh advfirewall firewall show rule name="$FW_RuleName"
if( $? ){
	LogOutput $LogFile $($CurrentFileName+" : 同日FWルール("+$FW_RuleName+")の有無を確認 => 有り")
	# リモートIPの8行目を抽出
	$CurrentRemoteIP = $CurrentRule[8] -replace "リモート IP:",""
	$CurrentRemoteIP = $CurrentRemoteIP -replace " ",""
	$CurrentRemoteIP_Arr = $CurrentRemoteIP.split(",")
	LogOutput $LogFile $($CurrentFileName+" : 同日FWルール("+$FW_RuleName+")のRemoteIP抽出 => "+$CurrentRemoteIP)
}else{
	LogOutput $LogFile $($CurrentFileName+" : 同日FWルール("+$FW_RuleName+")の有無を確認 => 無し")
}

# ----------------------------
# EventLogからIP抽出
# ----------------------------
# XML形式でEventLogを検索
LogOutput $LogFile $($CurrentFileName+" : EventLog検索 => LogName:Security,Id:4625,StartTime:"+$SearchStartTime+",EndTime:"+$SearchEndTime)
$Obj_Logs = Get-WinEvent -FilterHashtable @{LogName="Security";Id=4625;StartTime="$SearchStartTime";EndTime="$SearchEndTime"}

# 検索結果をループでXMLからIPのみを抽出
$Arr_IP = @()
foreach($Log in $Obj_Logs){
	# 配列からLogXMLを取り出す
	$xml = [XML]($Log.ToXml())
	# XML内のEventDataから項目IpAddressの値(#text)を取り出す
	$IP = ($xml.Event.EventData.Data | where Name -eq "IpAddress")."#text"
	# 配列に入れ直し
	$Arr_IP = $Arr_IP + $IP
}
# 重複削除と並び替え
#$Arr_IP_Unique = $Arr_IP | Sort-Object | Get-Unique
# IPの出現回数をカウント
$Arr_IP_Count = $Arr_IP | Group-Object | Select-Object Name,Count
# 出現回数が３回以上のIPを$Arr_BlockIPListに入れ直し
$Arr_BlockIPList = @()
foreach($Val in $Arr_IP_Count){
	$Count = [int]$Val.Count
	if( $Count -gt 3 ){
		$Arr_BlockIPList = $Arr_BlockIPList + $Val.Name
	}
}
# $Arr_BlockIPListにWhiteIPListのIP(自社IP)が含まれていた場合は除去
$Arr_WhiteIPList = @("127.0.0.1", "111.111.111.111", "222.222.222.222")
foreach($WhiteIP in $Arr_WhiteIPList){
	$Arr_BlockIPList = $Arr_BlockIPList -ne $WhiteIP
}
LogOutput $LogFile $( $CurrentFileName+" : EventLogのIP抽出 => "+$($Arr_BlockIPList -join ",") )

# ----------------------------
# IPレンジを調べる
# ----------------------------
# この部分はまた今度作成

# ----------------------------
# IPレンジをFireWallに変更or追加
# ----------------------------
# $Arr_BlockIPListが空だったら
$Arr_BlockIPList
if($Arr_BlockIPList.Count -eq 0){
	# Write-Host "直近のBlockIPが空なので何もしない"
	LogOutput $LogFile $($CurrentFileName+" : 新規Block対象IP => 無し")
	LogOutput $LogFile $($CurrentFileName+" : End => ")
	exit
}else{
	LogOutput $LogFile $($CurrentFileName+" : 新規Block対象IP => 有り")
}

# 既存ルールが存在したら
if( [bool]$CurrentRemoteIP_Arr ){
	# RemoteIPを追加
	$Arr_BlockIPList = $Arr_BlockIPList + $CurrentRemoteIP_Arr
	# 重複削除と並び替え
	$Arr_BlockIPList = $Arr_BlockIPList | Sort-Object | Get-Unique
	# 配列IPをカンマ区切り文字列に変換
	$Str_RemoteIP = $Arr_BlockIPList -join ","
	LogOutput $LogFile $($CurrentFileName+" : 新規と既存のBlock対象IPを結合 => "+$Str_RemoteIP)
	# ルール更新
	LogOutput $LogFile $($CurrentFileName+" : FWルール("+$FW_RuleName+")を更新 => dir=in,action=block,protocol=any,profile=any,remoteip="+$Str_RemoteIP)
	netsh advfirewall firewall set rule name="$FW_RuleName" new dir=in action=block protocol=any profile=any remoteip=$Str_RemoteIP
}else{
	# 配列IPをカンマ区切り文字列に変換
	$Str_RemoteIP = $Arr_BlockIPList -join ","
	# ルール作成
	LogOutput $LogFile $($CurrentFileName+" : FWルール("+$FW_RuleName+")を作成 => dir=in,action=block,protocol=any,profile=any,remoteip="+$Str_RemoteIP)
	netsh advfirewall firewall add rule name="$FW_RuleName" dir=in action=block protocol=any profile=any remoteip=$Str_RemoteIP
}
#$Str_RemoteIP

# ----------------------------
# 過去ログ削除
# ----------------------------
$LogSaveDate = 10
$RemoveDate = $(Get-Date (Get-Date).AddDays(-$LogSaveDate) -Format "yyyyMMdd")
$RemoveLog_FullName = ($CurrentDirPath+"\PSLog_"+$FileName_NoExt+"_"+$RemoveDate+".log")

# 削除対象ログファイルの有無を確認
if(Test-Path $RemoveLog_FullName){
	LogOutput $LogFile $($CurrentFileName+" : 過去ログ存在確認 => "+$RemoveLog_FullName+" => 有り")
	# 古いログを削除
	LogOutput $LogFile $($CurrentFileName+" : 過去ログ削除 => "+$RemoveLog_FullName)
	Remove-item -Force $RemoveLog_FullName
}else{
	LogOutput $LogFile $($CurrentFileName+" : 過去ログ存在確認 => "+$RemoveLog_FullName+" => 無し")
}

LogOutput $LogFile $($CurrentFileName+" : End => ")
exit

```

# スクリプトで自動追加されたファイヤーウォール
下記画像の様に日別でファイヤーウォールが追加されていきます。
なのでいつか整理削除しないといっぱいになりそうな気がします。
ルールは月別でもいいかもしれませんが、1ルールあたりスコープに登録できるIP数は何個までなのかが分からないので、その点も不安要素です。

![スクリーンショット 2024-02-17 11.21.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82956/07ef70ed-efb3-e0e0-8549-ca03472d1c5c.png)