feat: monitor admin share access via Security 5140 C$/ADMIN$ (2.0.19-SAC)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+222
-10
@@ -89,7 +89,7 @@ $script:SkipLogDetailLimit = 15
|
||||
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
|
||||
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
|
||||
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
|
||||
$ScriptVersion = "2.0.18-SAC"
|
||||
$ScriptVersion = "2.0.19-SAC"
|
||||
|
||||
# Логи (все под InstallRoot)
|
||||
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
||||
@@ -147,6 +147,14 @@ $WinRm4624CorrelationWindowSeconds = 15
|
||||
$WinRmIgnoreLocalSource = 1
|
||||
$WinRmIgnoreMachineAccounts = 1
|
||||
|
||||
# Admin share (Security 5140): C$, ADMIN$ — рабочие станции и серверы; нужен audit File Share.
|
||||
$EnableAdminShareMonitoring = 1
|
||||
$AdminShareMonitorSuffixes = @('C$', 'ADMIN$')
|
||||
$AdminShareEventIds = @(5140)
|
||||
$AdminShareIgnoreMachineAccounts = 1
|
||||
$AdminShareIgnoreLocalSource = 1
|
||||
$AdminShareNotifyDedupSeconds = 120
|
||||
|
||||
$ExcludedProcesses = @(
|
||||
"HTTP", "HTTP/*", "W3WP.EXE", "MSExchange", "SYSTEM", "LOCAL SERVICE",
|
||||
"NETWORK SERVICE", "OUTLOOK.EXE", "EXCHANGE", "EDGETRANSPORT", "STORE.EXE",
|
||||
@@ -1266,27 +1274,79 @@ function Enable-SecurityAudit {
|
||||
}
|
||||
|
||||
$preferRu = Test-RussianUiPreferred
|
||||
$logonConfigured = $false
|
||||
|
||||
if ($preferRu) {
|
||||
if (Ensure-RuLogonLogoffSubcategories) {
|
||||
Write-Log "Audit policy (RU): enabled for Russian subcategory names (Logon/Logoff / Russian UI output)."
|
||||
return
|
||||
$logonConfigured = $true
|
||||
} else {
|
||||
Write-Log "Russian category names not accepted or failed; trying English Logon/Logoff (auditpol language can differ from OS UI)."
|
||||
}
|
||||
Write-Log "Russian category names not accepted or failed; trying English Logon/Logoff (auditpol language can differ from OS UI)."
|
||||
}
|
||||
|
||||
if (Ensure-EnLogonLogoffSubcategories) {
|
||||
if (-not $logonConfigured -and (Ensure-EnLogonLogoffSubcategories)) {
|
||||
Write-Log "Audit policy (EN): OK for Logon/Logoff (English auditpol output)."
|
||||
$logonConfigured = $true
|
||||
}
|
||||
|
||||
if (-not $logonConfigured) {
|
||||
Write-Log "Trying Logon/Logoff subcategories via known GUIDs (locale-neutral fallback)..."
|
||||
if (Ensure-LogonLogoffSubcategoriesByGuid) {
|
||||
Write-Log "Audit policy (GUID): OK for Logon/Logoff subcategories."
|
||||
$logonConfigured = $true
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $logonConfigured) {
|
||||
Write-Log "WARNING: could not configure logon/logoff auditing via auditpol automatically. The script will continue; check audit policy in local/domain GPO."
|
||||
}
|
||||
|
||||
if (-not (Test-MonitorFeatureEnabled -Value $EnableAdminShareMonitoring)) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Log "Trying Logon/Logoff subcategories via known GUIDs (locale-neutral fallback)..."
|
||||
if (Ensure-LogonLogoffSubcategoriesByGuid) {
|
||||
Write-Log "Audit policy (GUID): OK for Logon/Logoff subcategories."
|
||||
return
|
||||
function Ensure-FileShareAuditSubcategory {
|
||||
$guid = '0CCE9220-69AE-11D9-BED3-505054503030'
|
||||
$ruCategory = Uc @(0x0414,0x043E,0x0441,0x0442,0x0443,0x043F,0x0020,0x043A,0x0020,0x043E,0x0431,0x044A,0x0435,0x043A,0x0442,0x0430,0x043C)
|
||||
$ruSub = Uc @(0x041E,0x0431,0x0449,0x0438,0x0439,0x0020,0x0434,0x043E,0x0441,0x0442,0x0443,0x043F,0x0020,0x043A,0x0020,0x0444,0x0430,0x0439,0x043B,0x0430,0x043C)
|
||||
$attempts = @(
|
||||
@{ Category = $ruCategory; Sub = $ruSub; Label = 'RU File Share' },
|
||||
@{ Category = 'Object Access'; Sub = 'File Share'; Label = 'EN File Share' },
|
||||
@{ Category = $null; Sub = $guid; Label = 'GUID File Share'; UseGuid = $true }
|
||||
)
|
||||
foreach ($a in $attempts) {
|
||||
if ($a.UseGuid) {
|
||||
$setArgs = ('/set /subcategory:{{{0}}} /success:enable /failure:enable' -f $a.Sub)
|
||||
$set = Invoke-AuditPol -Arguments $setArgs
|
||||
if ($set.ExitCode -eq 0) {
|
||||
Write-Log "Audit policy ($($a.Label)): enabled (5140 admin share monitoring)."
|
||||
return $true
|
||||
}
|
||||
Write-Log ("auditpol File Share GUID SET FAIL (code {0}): {1}" -f $set.ExitCode, $set.Text)
|
||||
continue
|
||||
}
|
||||
$cur = Get-CategorySettingLine -CategoryName $a.Category -SubcategoryLabel $a.Sub
|
||||
if (-not $cur.Ok) { continue }
|
||||
if ($null -eq $cur.Line) { continue }
|
||||
if (Test-SuccessAndFailureText -Line $cur.Line) {
|
||||
Write-Log "Audit policy ($($a.Label)): already Success+Failure."
|
||||
return $true
|
||||
}
|
||||
$setArgs = ('/set /subcategory:"{0}" /success:enable /failure:enable' -f $a.Sub)
|
||||
$set = Invoke-AuditPol -Arguments $setArgs
|
||||
if ($set.ExitCode -ne 0) {
|
||||
Write-Log ("auditpol File Share SET FAIL ($($a.Label), code {0}): {1}" -f $set.ExitCode, $set.Text)
|
||||
continue
|
||||
}
|
||||
Write-Log "Audit policy ($($a.Label)): enabled for admin share monitoring (5140)."
|
||||
return $true
|
||||
}
|
||||
Write-Log "WARNING: could not enable File Share audit (5140). Admin share alerts need Object Access — File Share in GPO/auditpol."
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Log "WARNING: could not configure logon/logoff auditing via auditpol automatically. The script will continue; check audit policy in local/domain GPO."
|
||||
[void](Ensure-FileShareAuditSubcategory)
|
||||
}
|
||||
|
||||
function Test-RDSDeploymentPresent {
|
||||
@@ -1734,6 +1794,102 @@ function Should-IgnoreWinRmSession {
|
||||
return -not [string]::IsNullOrWhiteSpace((Get-WinRmIgnoreReason -Username $Username -SourceIP $SourceIP))
|
||||
}
|
||||
|
||||
function Test-IsMonitoredAdminShareName {
|
||||
param([string]$ShareName)
|
||||
if ([string]::IsNullOrWhiteSpace($ShareName)) { return $false }
|
||||
$s = $ShareName.Trim().TrimEnd('\')
|
||||
foreach ($suffix in @($AdminShareMonitorSuffixes)) {
|
||||
if ([string]::IsNullOrWhiteSpace($suffix)) { continue }
|
||||
$sfx = [string]$suffix
|
||||
if ($s -like ('*{0}' -f $sfx)) { return $true }
|
||||
if ($s -ieq $sfx) { return $true }
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-AdminShare5140EventInfo {
|
||||
param($Event)
|
||||
$info = [pscustomobject]@{
|
||||
TimeCreated = $Event.TimeCreated
|
||||
EventId = [int]$Event.Id
|
||||
Username = '-'
|
||||
Domain = '-'
|
||||
SourceIP = '-'
|
||||
ShareName = '-'
|
||||
SharePath = '-'
|
||||
RelativeTarget = '-'
|
||||
AccessMask = '-'
|
||||
}
|
||||
try {
|
||||
$map = Get-EventDataMap -Event $Event
|
||||
$user = Get-FirstNonEmptyMapValue -DataMap $map -Keys @('SubjectUserName', 'Subject User Name')
|
||||
$domain = Get-FirstNonEmptyMapValue -DataMap $map -Keys @('SubjectDomainName', 'Subject Domain Name')
|
||||
if (-not [string]::IsNullOrWhiteSpace($user) -and -not [string]::IsNullOrWhiteSpace($domain)) {
|
||||
$info.Username = ('{0}\{1}' -f $domain, $user)
|
||||
} elseif (-not [string]::IsNullOrWhiteSpace($user)) {
|
||||
$info.Username = $user
|
||||
}
|
||||
$info.SourceIP = Get-FirstNonEmptyMapValue -DataMap $map -Keys @('IpAddress', 'Ip Address', 'ClientAddress', 'Client Address')
|
||||
$info.ShareName = Get-FirstNonEmptyMapValue -DataMap $map -Keys @('ShareName', 'Share Name')
|
||||
$info.SharePath = Get-FirstNonEmptyMapValue -DataMap $map -Keys @('ShareLocalPath', 'Share Local Path')
|
||||
$info.RelativeTarget = Get-FirstNonEmptyMapValue -DataMap $map -Keys @('RelativeTargetName', 'Relative Target Name')
|
||||
$info.AccessMask = Get-FirstNonEmptyMapValue -DataMap $map -Keys @('AccessMask', 'Access Mask')
|
||||
} catch {
|
||||
Write-Log "Ошибка разбора 5140: $($_.Exception.Message)"
|
||||
}
|
||||
foreach ($prop in @('Username', 'Domain', 'SourceIP', 'ShareName', 'SharePath', 'RelativeTarget', 'AccessMask')) {
|
||||
if ([string]::IsNullOrWhiteSpace($info.$prop)) { $info.$prop = '-' }
|
||||
}
|
||||
return $info
|
||||
}
|
||||
|
||||
function Format-AdminShare5140TelegramMessage {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]$Info,
|
||||
[string]$SecurityLogComputerName
|
||||
)
|
||||
$hHost = (ConvertTo-TelegramHtml (Get-MonitorServerLabelWithIp))
|
||||
$hUser = (ConvertTo-TelegramHtml $Info.Username)
|
||||
$hIp = (ConvertTo-TelegramHtml $Info.SourceIP)
|
||||
$hShare = (ConvertTo-TelegramHtml $Info.ShareName)
|
||||
$hPath = (ConvertTo-TelegramHtml $Info.SharePath)
|
||||
$hTarget = (ConvertTo-TelegramHtml $Info.RelativeTarget)
|
||||
$hTime = (ConvertTo-TelegramHtml ($Info.TimeCreated.ToString('dd.MM.yyyy HH:mm:ss')))
|
||||
$message = "<b>📁 Доступ к админ-шару (5140)</b>`r`n"
|
||||
$message += "🏢 Сервер: $hHost`r`n"
|
||||
$message += "👤 Пользователь: $hUser`r`n"
|
||||
$message += "🌐 IP клиента: $hIp`r`n"
|
||||
$message += "📂 Share: $hShare`r`n"
|
||||
if ($Info.SharePath -ne '-') {
|
||||
$message += "📍 Путь: $hPath`r`n"
|
||||
}
|
||||
if ($Info.RelativeTarget -ne '-') {
|
||||
$message += "📄 Объект: $hTarget`r`n"
|
||||
}
|
||||
$message += "🕐 Время: $hTime`r`n"
|
||||
$message += "🔢 Event ID: 5140 (File Share)"
|
||||
return $message
|
||||
}
|
||||
|
||||
function Get-AdminShare5140IgnoreReason {
|
||||
param(
|
||||
[string]$Username,
|
||||
[string]$SourceIP
|
||||
)
|
||||
if ([string]::IsNullOrWhiteSpace($Username) -or $Username -eq '-') { return 'empty-user' }
|
||||
if (Test-RdpMonitorUsernameExcludedByPattern -Username $Username) { return 'excluded-user-pattern' }
|
||||
if (Test-MonitorFeatureEnabled -Value $AdminShareIgnoreMachineAccounts) {
|
||||
if (Test-RdpMonitorIsMachineAccountName -Username $Username) { return 'machine-account' }
|
||||
}
|
||||
if (Test-MonitorFeatureEnabled -Value $AdminShareIgnoreLocalSource) {
|
||||
if (Test-RdpMonitorIsLoopbackOrLinkLocalSourceIp -Ip $SourceIP) { return 'local-or-linklocal-ip' }
|
||||
}
|
||||
if (Test-RdpMonitorIgnoreListMatch -EventId '5140' -Username $Username -SourceIP $SourceIP) {
|
||||
return 'ignore-list-match'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function Get-MonitorServerLabel {
|
||||
$label = $null
|
||||
if (Get-Variable -Name ServerDisplayName -ErrorAction SilentlyContinue) {
|
||||
@@ -2390,8 +2546,11 @@ function Parse-RdpMonitorIgnoreListLine {
|
||||
} elseif ($line -match '^(?i)(winrm|pssession|91)\s*:\s*(.+)$') {
|
||||
$scopes = @('winrm')
|
||||
$line = $Matches[2].Trim()
|
||||
} elseif ($line -match '^(?i)(smb|5140|adminshare)\s*:\s*(.+)$') {
|
||||
$scopes = @('5140')
|
||||
$line = $Matches[2].Trim()
|
||||
} elseif ($line -match '^(?i)(all|\*)\s*:\s*(.+)$') {
|
||||
$scopes = @('4624', '4625', '4740', '20506', '20507', '20510', 'winrm')
|
||||
$scopes = @('4624', '4625', '4740', '20506', '20507', '20510', 'winrm', '5140')
|
||||
$line = $Matches[2].Trim()
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($line)) { return $null }
|
||||
@@ -3634,6 +3793,9 @@ function Start-LoginMonitor {
|
||||
} else {
|
||||
Write-Log "Режим сервера: Security — LogonType 2, 3, 10"
|
||||
}
|
||||
if (Test-MonitorFeatureEnabled -Value $EnableAdminShareMonitoring) {
|
||||
Write-Log "Admin share: Security 5140 для $($AdminShareMonitorSuffixes -join ', ') (audit File Share; workstation + server)."
|
||||
}
|
||||
Write-Log "========================================"
|
||||
Write-Log "Каналы уведомлений: $(Get-NotifyChainHuman)"
|
||||
Write-Log "Фильтр Exchange 4624 LT3 EmptyIP: ${Ignore4624-LT3-EmptyIP-Event}"
|
||||
@@ -3691,6 +3853,7 @@ function Start-LoginMonitor {
|
||||
|
||||
$rcmShadowMonitoringEnabled = (Test-MonitorFeatureEnabled -Value $EnableRcmShadowControlMonitoring) -and (Test-RcmLogAvailable)
|
||||
$winRmMonitoringEnabled = (Test-MonitorFeatureEnabled -Value $EnableWinRmInboundMonitoring) -and (Test-WinRmLogAvailable)
|
||||
$adminShareMonitoringEnabled = Test-MonitorFeatureEnabled -Value $EnableAdminShareMonitoring
|
||||
|
||||
$nextHeartbeatTime = (Get-Date).AddSeconds($HeartbeatInterval)
|
||||
$nextRotationCheck = Check-AndRotateLog
|
||||
@@ -3708,6 +3871,7 @@ function Start-LoginMonitor {
|
||||
$lastRcmCheckTime = (Get-Date).AddSeconds(-10)
|
||||
$lastRcmShadowCheckTime = (Get-Date).AddSeconds(-10)
|
||||
$lastWinRmCheckTime = (Get-Date).AddSeconds(-10)
|
||||
$lastAdminShare5140CheckTime = (Get-Date).AddSeconds(-10)
|
||||
$lastLockout4740CheckTime = (Get-Date).AddSeconds(-10)
|
||||
$monitorEvents = @(4624, 4625, 4648)
|
||||
|
||||
@@ -4097,6 +4261,54 @@ function Start-LoginMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
if ($adminShareMonitoringEnabled) {
|
||||
Set-MonitorLoopPhase -Phase 'poll_admin_share'
|
||||
$adminShareEvents = Get-WinEvent -FilterHashtable @{
|
||||
LogName = 'Security'
|
||||
ID = $AdminShareEventIds
|
||||
StartTime = $lastAdminShare5140CheckTime
|
||||
} -ErrorAction SilentlyContinue
|
||||
|
||||
if ($adminShareEvents) {
|
||||
foreach ($event in @($adminShareEvents | Sort-Object TimeCreated, RecordId)) {
|
||||
if ($event.TimeCreated -le $lastAdminShare5140CheckTime) { continue }
|
||||
$sh = Get-AdminShare5140EventInfo -Event $event
|
||||
if (-not (Test-IsMonitoredAdminShareName -ShareName $sh.ShareName)) {
|
||||
continue
|
||||
}
|
||||
$ignoreReason = Get-AdminShare5140IgnoreReason -Username $sh.Username -SourceIP $sh.SourceIP
|
||||
if (-not [string]::IsNullOrWhiteSpace($ignoreReason)) {
|
||||
Write-Log "Skip 5140: User=$($sh.Username) IP=$($sh.SourceIP) Share=$($sh.ShareName) — $ignoreReason"
|
||||
continue
|
||||
}
|
||||
$dedupKey = "5140|$($sh.Username)|$($sh.SourceIP)|$($sh.ShareName)|$($event.RecordId)"
|
||||
if (Test-RdpMonitorNotifyDedup -Key $dedupKey -WindowSeconds $AdminShareNotifyDedupSeconds) {
|
||||
Write-Log "Notify dedup 5140: $dedupKey"
|
||||
continue
|
||||
}
|
||||
$msg = Format-AdminShare5140TelegramMessage -Info $sh -SecurityLogComputerName $event.MachineName
|
||||
Write-Log "Notify 5140: User=$($sh.Username) IP=$($sh.SourceIP) Share=$($sh.ShareName)"
|
||||
Send-MonitorNotification -Message $msg `
|
||||
-EmailSubject "RDP Login Monitor: admin share $($sh.ShareName)" `
|
||||
-SacEventType 'smb.admin_share.access' -SacSeverity 'warning' `
|
||||
-SacTitle "Admin share access ($($sh.ShareName))" `
|
||||
-SacSummary "5140 $($sh.Username) from $($sh.SourceIP) share=$($sh.ShareName)" `
|
||||
-SacOccurredAt $sh.TimeCreated `
|
||||
-SacDetails @{
|
||||
event_id_windows = 5140
|
||||
user = $sh.Username
|
||||
source_ip = $sh.SourceIP
|
||||
ip_address = $sh.SourceIP
|
||||
share_name = $sh.ShareName
|
||||
share_path = $sh.SharePath
|
||||
relative_target = $sh.RelativeTarget
|
||||
access_mask = $sh.AccessMask
|
||||
} | Out-Null
|
||||
}
|
||||
$lastAdminShare5140CheckTime = Update-MonitorPollCursor -CurrentCursor $lastAdminShare5140CheckTime -Events @($adminShareEvents)
|
||||
}
|
||||
}
|
||||
|
||||
if ($lockout4740Enabled) {
|
||||
$lockoutEvents = Get-WinEvent -FilterHashtable @{
|
||||
LogName = 'Security'
|
||||
|
||||
+6
-1
@@ -8,7 +8,8 @@
|
||||
# 4740: — только блокировка учётной записи (4740); для IP — любой IP из IIS
|
||||
# shadow: — RDS Shadow Control (RCM 20506/20507/20510)
|
||||
# winrm: / pssession: — WinRM inbound / Enter-PSSession (Operational 91)
|
||||
# all: — 4624/4625, 4740, shadow, winrm
|
||||
# smb: / 5140: / adminshare: — доступ к админ-шару C$/ADMIN$ (Security 5140)
|
||||
# all: — 4624/4625, 4740, shadow, winrm, 5140
|
||||
#
|
||||
# Форматы правила (после префикса области):
|
||||
# user:domain\user
|
||||
@@ -32,6 +33,10 @@
|
||||
# winrm:user:DOMAIN\jump-admin
|
||||
# winrm:ip:192.168.160.50
|
||||
|
||||
# --- только admin share 5140 (C$, ADMIN$) ---
|
||||
# smb:user:DOMAIN\backup-svc
|
||||
# 5140:ip:192.168.160.50
|
||||
|
||||
# --- только RDS Shadow Control ---
|
||||
# shadow:user:DOMAIN\helpdesk
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ $DailyReportEnabled = 1
|
||||
# --- RDS Shadow Control + WinRM inbound (Enter-PSSession), severity warning ---
|
||||
# $EnableRcmShadowControlMonitoring = 1 # RCM Operational 20506/20507/20510
|
||||
# $EnableWinRmInboundMonitoring = 1 # WinRM Operational 91 (+ correlate Security 4624)
|
||||
# $EnableAdminShareMonitoring = 1 # Security 5140 C$/ADMIN$ (audit File Share)
|
||||
# $WinRmIgnoreLocalSource = 1 # ::1, 127.0.0.1, fe80 (шум Exchange/локальный WinRM)
|
||||
# $WinRmIgnoreMachineAccounts = 1 # учётки, оканчивающиеся на $
|
||||
# HealthMailbox* уже в ExcludedUserPatterns скрипта
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.0.18-SAC
|
||||
2.0.19-SAC
|
||||
|
||||
Reference in New Issue
Block a user