☰
PowerShell安全加固实战:从系统体检到日志审计的Windows防线
2026/10/19 10:18:27 网站建设 项目流程

这几年,找我咨询Windows加固的人越来越多,问题往往不是缺工具,而是工具太多。装了一堆第三方杀软、卫士、扫描器,最后发现系统变卡、互相打架,真正的风险点反而没人说得清。而Windows自带的PowerShell,其实就是一台性能相当能打的安全工具箱——它不需要额外装驱动、不需要代理常驻、不会偷偷占资源,只要你把它用对,基础的入侵排查、基线检查、日志审计、防御策略下发全都能在几分钟内搞定。这篇指南就围绕PowerShell安全工具这条路,把从自身锁定、系统体检、防护接管到日志审计的完整链路拆开讲透。适合系统运维、桌面支持、安全工程师参考,也适合那些厌倦了“全家桶”软件、想回归轻量自控的管理员。

1. 先想清楚:为什么安全加固首选PowerShell而不是装一堆第三方软件

1.1 PowerShell做安全到底强在哪

PowerShell的底层是.NET,管理Windows时天然能拿到最全的系统接口。你要查开机启动项、进程签名、防火墙策略、安全日志,它都能直接读原生的WMI/CIM、注册表、事件通道和各类安全模块,不需要中间商。

更关键的一点是可审计性。第三方安全工具往往把结果存在自己的数据库里,出了问题先看工具脸色。而PowerShell的一切操作都有明确的命令字面记录,配合脚本日志和转录功能,你甚至能还原“某台机器上到底执行过什么”。这对事后溯源和合规检查来说,价值比任何一个“一键体检”按钮都高。

从资源占用看,PowerShell的命令是“用完即走”的。不像常驻客户端,安全扫描时可能吃满CPU,跑完又继续蹲在后台。PowerShell脚本可以在非业务时段通过计划任务调用,CPU、内存、磁盘IO完全可控,这对生产机器尤其友好。

1.2 适合谁用,以及最忌讳什么

这套玩法最适合三类人:一是中小公司的运维,手上几十上百台机器,没预算上昂贵的终端管理平台;二是安全团队里负责事件响应的成员,拿到一台疑似被动的机器,要快速判断是否被植入;三是桌面支持工程师,帮用户处理莫名其妙的卡顿、弹窗、异常自启动时,PowerShell能给你确定性的答案,而不是“重装系统完事”。

但我要先泼盆冷水:PowerShell只是工具,不是银弹。它默认不阻止恶意软件执行,如果你在已经失陷的机器上“体检”,敌人也可能正在用PowerShell做同样的事情。所以最忌讳的做法是——不去理解命令含义,直接照抄网上的“加固全家桶脚本”,还一路Yes到底。先把每个命令是做什么、改了什么配置搞清楚,再上生产,这条原则比任何一条具体命令都重要。

2. 动手前先锁门:PowerShell自身安全配置一次到位

你自己天天用PowerShell查别人,如果PowerShell本身的门户大开,那等于给攻击者送了一把多功能刀。所以第一步,先把PowerShell自己锁住。

2.1 执行策略别一刀切,分清场景再选

执行策略(Execution Policy)是PowerShell的第一道闸门。很多人一上来就Set-ExecutionPolicy Unrestricted,图省事,这属于自己拆门槛。比较合理的是RemoteSigned:本地创建的脚本允许运行,从互联网下载的脚本必须带可信签名才能跑。

先看当前状态:

Get-ExecutionPolicy -List

输出里会看到每个作用域(MachinePolicy、UserPolicy、Process、CurrentUser、LocalMachine)各自的值。注意,组策略(MachinePolicy/UserPolicy)优先级最高,如果它们显示Undefined以外的值,你本地改的CurrentUser和LocalMachine都会被覆盖。这是大家在公司域环境里最容易踩的坑。

设置建议:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

还有一点容易被忽略:PowerShell 7+和Windows PowerShell 5.1的执行策略是独立的。你在5.1里改完,pwsh里可能还是Restricted。两侧都要检查。

2.2 只给签名脚本放行:脚本签名实操

RemoteSigned策略面对“自己写、内网传”的脚本,其实默认放行。但真正规整的做法是:团队内部要求所有分发脚本必须签名,这样既能防篡改,也能在出现恶意脚本时快速定位责任人。

演示环境里可以先用自签名证书体验全流程:

# 生成一个测试用的代码签名证书 New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=内部安全脚本测试证书" -CertStoreLocation Cert:\CurrentUser\My # 对脚本签名 Set-AuthenticodeSignature -FilePath C:\Scripts\security-check.ps1 -Certificate (Get-ChildItem Cert:\CurrentUser\My\代码签名证书指纹)

签名后查看状态:

Get-AuthenticodeSignature C:\Scripts\security-check.ps1 | Select Status, StatusMessage, SignerCertificate

需要说明的是,自签名证书只适合测试和练习。生产环境请走企业内部的代码签名证书颁发流程,或者至少把测试证书加入“受信任的发布者”存储,否则其他机器依然会拒绝。签名体系建立后,你的策略可以进一步收紧为AllSigned,执行时PowerShell会自动校验签名链。

2.3 受限语言模式与审计日志是最后一道保险

比执行策略更硬的是WDAC或AppLocker控制的“受限语言模式”(Constrained Language Mode)。简单理解,正常模式允许PowerShell调用.NET类型和Win32 API做任何事,而受限模式下大部分高级能力被禁用,只允许基本命令和类型。配置了AppLocker/WDAC规则后,PowerShell会自动进入受限模式,恶意脚本就算落地,能玩的花样也会被大幅压缩。

自检当前模式:

$ExecutionContext.SessionState.LanguageMode

输出的如果不是FullLanguage,说明本机已经被某种策略限制,这时候很多管理命令会报错,属正常现象,别急着去关AppLocker。

最后要开审计。把PowerShell脚本块日志和转录打开,后续你的一切操作、以及攻击者利用PowerShell的行为,都会留下记录。命令如下:

$sbPath = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' New-Item -Path $sbPath -Force | Out-Null Set-ItemProperty -Path $sbPath -Name EnableScriptBlockLogging -Value 1 -Type DWord Set-ItemProperty -Path $sbPath -Name EnableScriptBlockInvocationLogging -Value 1 -Type DWord $trPath = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\Transcription' New-Item -Path $trPath -Force | Out-Null Set-ItemProperty -Path $trPath -Name EnableTranscripting -Value 1 -Type DWord Set-ItemProperty -Path $trPath -Name OutputDirectory -Value 'C:\PSLogs' -Type String

打开后,日志会出现在“应用程序和服务日志 / Microsoft / Windows / PowerShell / Operational”里,事件ID 4104记录脚本块内容,4100记录命令调用。这个配置最好通过组策略统一下发,别在每台机器上手工敲。后面第5部分我还会讲怎么实时分析这些日志,现在先把门锁好。

3. Windows系统体检:用PowerShell揪出可疑自启动和异常进程

系统是否被植入,最直接的表现就是各种“莫名其妙的东西开机就跑”。手动打开任务管理器慢慢翻也行,但机器一多就累死人。用PowerShell做体检,核心就是三个字:结构化。拿到对象、筛选属性、输出报表,一气呵成。

3.1 五分钟查清所有开机自启动项

常见的自启动位置无非几类:注册表Run键、启动文件夹、计划任务、服务、WMI事件订阅。一条命令先覆盖最常见的两类:

Get-CimInstance Win32_StartupCommand | Select-Object Name, Command, Location, User | Format-Table -AutoSize

再手工兜底查注册表:

$runPaths = @( 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run', 'HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce', 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run', 'HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce' ) foreach ($p in $runPaths) { if (Test-Path $p) { Write-Host "== $p ==" -ForegroundColor Cyan Get-ItemProperty -Path $p | Select-Object * -ExcludeProperty PS* | Format-List } }

排查要点:看路径是否在C:\Users\下的临时目录、看是否用了可疑的程序名伪装(比如svchost.exe被放在非System32目录)、看启动参数里是否带下载指令。任何藏在用户Profile目录里的“启动项”都值得记下来重点查。

3.2 进程、服务、计划任务的异常识别

进程体检我一般分成两步。第一步看所有带路径的进程签名是否有效,无效签名的优先怀疑:

Get-Process | Where-Object { $_.Path } | ForEach-Object { [pscustomobject]@{ 进程名 = $_.ProcessName PID = $_.Id 路径 = $_.Path 签名状态 = (Get-AuthenticodeSignature $_.Path).Status 启动时间 = $_.StartTime } } | Where-Object 签名状态 -ne 'Valid' | Format-Table -AutoSize

第二步重点看那些“长得很像系统进程”的东西。系统进程路径必须在C:\Windows\System32或C:\Windows\SysWOW64,如果有svchost.exe跑在别的目录,十有八九有问题。顺手查服务里非Microsoft启动的服务:

Get-CimInstance Win32_Service | Where-Object { $_.PathName -notlike 'C:\Windows\*' -and $_.State -eq 'Running' } | Select-Object Name, DisplayName, PathName, StartMode | Format-Table -AutoSize

计划任务同样是被忽略的重灾区。按经验,只要排掉系统自带的\Microsoft\目录,剩下的任务基本都值得过一眼:

Get-ScheduledTask | Where-Object { $_.State -ne 'Disabled' -and $_.TaskPath -notlike '\Microsoft\*' } | Select-Object TaskName, TaskPath, State | Format-Table -AutoSize

看到不认识的TaskName,用Get-ScheduledTask -TaskName xxx | Select -ExpandProperty Actions查看它到底执行什么命令。很多蛰伏后门就藏在看似无害的计划任务里,一周跑一次,跑完就消失。

3.3 网络连接的审查:谁在偷偷监听

本地植入木马,绝大多数要开端口通信。内网环境重点看监听端口和对应的进程:

Get-NetTCPConnection -State Listen | ForEach-Object { $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue [pscustomobject]@{ 监听端口 = $_.LocalPort 进程名 = $proc.ProcessName PID = $_.OwningProcess 路径 = $proc.Path } } | Sort-Object 监听端口 | Format-Table -AutoSize

这条命令的价值在于,它能直接把端口和进程路径对齐。正常业务再乱,也就是那几十个懂行的端口;如果看到445以外的SMB、3389的意外监听、或者某个普通软件进程突然监听一个高位端口,就要警惕。

对外连接的审查更有门槛,因为纯PowerShell原生cmdlet拿不到太全的“发起连接进程”映射,但可以借助Get-NetTCPConnection的OwningProcess反查:

Get-NetTCPConnection -State Established | Where-Object { $_.RemotePort -match '^(80|443)$' -or $_.RemotePort -gt 1024 } | ForEach-Object { $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue [pscustomobject]@{ 本地端口 = $_.LocalPort 远程地址 = $_.RemoteAddress 远程端口 = $_.RemotePort 进程名 = $proc.ProcessName 路径 = $proc.Path } } | Sort-Object 进程名 | Format-Table -AutoSize

看输出里有没有哪个进程在持续连接无关域名IP。遇到可疑连接,先在企业防火墙侧确认目的地址的历史信誉,再决定隔离。

4. 让Windows自带防护火力全开

查问题只是一半,另一半是把Windows自带的防御能力调到能打的水平。这里的底气在于,Defender和防火墙的控制接口对PowerShell是完全开放的,且调整粒度远超图形界面。

4.1 Defender的管理和深度配置

先看当前防护状态:

Get-MpComputerStatus | Select AMServiceEnabled, AntispywareEnabled, AntivirusEnabled, RealTimeProtectionEnabled, NISEnabled, QuickScanEndTime, AntivirusSignatureVersion

现实中见到最多的状态是“实时保护被某软件关了还关不回来”。恢复实时保护和云端防护:

Set-MpPreference -DisableRealtimeMonitoring $false Set-MpPreference -MAPSReporting 1 Set-MpPreference -SubmitSamplesConsent 1

我补充几个图形界面不容易找到、但应对新型威胁很管用的选项:

  • Set-MpPreference -DisableArchiveScanning $false:扫描压缩包内部文件,压缩包是木马最常见的运输包装。
  • Set-MpPreference -DisableScriptScanning $false:对PowerShell脚本做内容扫描,结合前文开启的脚本日志,能拦住相当大一部分无文件攻击。
  • Set-MpPreference -PUAProtection 1:启用PUA(潜在不需要的程序)拦截,捆绑下载器基本会在落地阶段被拦下。
  • Set-MpPreference -HighThreatDefaultAction 6 -LowThreatDefaultAction 2:高危威胁自动隔离,低危先询问。数值映射不同操作,实际用之前先看官方文档。

更新和全盘扫描可以串起来:

Update-MpSignature Start-MpScan -ScanType FullScan -AsJob

-AsJob会在后台运行,你可以后面用Receive-Job收结果,不用傻傻卡在终端前。实测大面积扫描时,建议避开业务高峰,因为全盘扫描I/O压力不小。

4.2 防火墙规则批量下发与策略落地

Windows防火墙日常没人管,要么全开但规则乱成一团,要么干脆被第三方软件接管后自带一条全放行。我建议用PowerShell把所有配置文件统一强制启用,再按业务需求批量放行:

Set-NetFirewallProfile -Profile Domain, Private, Public -Enabled True -DefaultInboundAction Block -DefaultOutboundAction Allow

注意,这里-DefaultInboundAction Block是关键。默认入站拦截,再对必要端口单独放行,比“默认全放、坏了自己背”靠谱得多。需要放行某个端口时,显式建规则:

New-NetFirewallRule -DisplayName "允许内部应用端口8080" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8080 -Profile Domain,Private

批量下发时,先导出当前规则备份:

Export-NetFirewallRule -PolicyStore ActiveStore -FileName C:\FirewallBackup\fw_policy_backup.wfw

我踩过最狠的坑就是把远程桌面端口写进规则后忘了限制配置文件,结果公网配置都放行了。教训:新建放行规则一定带-Profile限定,宁可多拆几条规则,也别一条规则管三个配置。

4.3 写一个简易安全基线自查脚本

与其到处找“一键加固工具”,不如自己攒一个贴合本单位环境的小脚本。常用的自查项就够初筛了:SMBv1是否禁用、Guest账户是否启用、自动登录是否残留、Defender和防火墙状态、系统补丁更新时间。

$report = [System.Collections.Generic.List[object]]::new() # SMBv1 协议是否开放 $smb = Get-SmbServerConfiguration $report.Add([pscustomobject]@{检查项='SMBv1'; 当前值=$smb.EnableSMB1Protocol; 期望值='False'}) # Guest 账户是否启用 $guest = Get-LocalUser -Name Guest -ErrorAction SilentlyContinue $report.Add([pscustomobject]@{检查项='Guest账户'; 当前值=$guest.Enabled; 期望值='False'}) # 自动登录注册表残留 $autoLogon = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -ErrorAction SilentlyContinue).AutoAdminLogon $report.Add([pscustomobject]@{检查项='自动登录'; 当前值=$autoLogon; 期望值='0或空'}) # 防火墙状态 $fw = Get-NetFirewallProfile $report.Add([pscustomobject]@{检查项='防火墙Public'; 当前值=($fw | Where-Object Name -eq 'Public').Enabled; 期望值='True'}) # Defender 实时保护 $mp = Get-MpComputerStatus $report.Add([pscustomobject]@{检查项='Defender实时防护'; 当前值=$mp.RealTimeProtectionEnabled; 期望值='True'}) # 最后一次补丁安装时间 $lastHotfix = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 1 $report.Add([pscustomobject]@{检查项='最近补丁'; 当前值=$lastHotfix.InstalledOn; 期望值='近30天内'}) $report | Format-Table 检查项, 当前值, 期望值 -AutoSize

这个脚本的价值不在“跑一次”,而在持续跑。把脚本放到共享目录,配好计划任务,每周生成报告,阈值异常直接告警。基线检查只要形成周期性动作,才算真正落地。

5. 日志与审计:从“事后补救”到“主动告警”

体检和加固都是防御动作,但安全最怕“不知道自己被打了”。Windows安全日志是富矿,PowerShell是挖掘机。把日志管好,你就能在入侵后几分钟内发现异常,而不是等业务出问题才倒查。

5.1 打开PowerShell日志记录:4104和4100事件

第2.3节我已经写过开启命令。开启后,所有脚本块的执行内容都会进“Microsoft-Windows-PowerShell/Operational”日志。平时怎么用?直接查最近有哪些脚本被执行:

Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' -MaxEvents 200 | Where-Object { $_.Id -in 4103, 4104 } | Select-Object TimeCreated, Id, @{n='脚本内容'; e={$_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))}}

很多攻击者会刻意用-EncodedCommand参数把命令编码后再执行,为的就是避开日志里的明文记录。但脚本块日志开启后,解码后的内容也会留在4104事件里。所以这套配置的优先级,我始终放在最前面。开启之后建议做一次验证:本机随便跑一条Get-Process,然后去日志里搜4104是否记录了对应内容,没记录就是策略没生效,先排查再做其他事。

5.2 登录审计:快速定位暴力破解和异常登录

安全日志里最该盯的三个ID:4624(登录成功)、4625(登录失败)、4720(创建新账户)。暴破最典型的特征是4625短时间爆炸式增长。查询近24小时失败次数最高的来源:

$failed = Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625; StartTime=(Get-Date).AddDays(-1)} -ErrorAction SilentlyContinue $failed | Group-Object { $_.Properties[18].Value } | Select-Object Name, Count | Sort-Object Count -Descending | Format-Table -AutoSize

注意,4625事件的属性索引在不同Windows版本有差异,如果上面拿不到源IP,就退回用Group-Object { $_.Message }按消息整体聚合,一样能看出热点。查出来源后,去防火墙侧直接封禁对应IP段,同时在账户策略里加上锁定阈值,别干等暴破结束。

再看成功登录里的异常:凌晨三点登录成功、从未见过的用户名、登录类型是3(网络)但来源IP却是内网管理网段外的地址。这些单看一条日志不明显,但把它筛出来人工复核就有价值了,命令就是把4624按TimeCreated、LogonType、IpAddress分组导出成CSV,交给每周值班的人扫一遍。

5.3 定时巡检报告:PowerShell+计划任务

日志分析现查现做太被动,我的习惯是写一个巡检脚本,定时生成摘要发到邮箱或导出到共享目录。核心骨架长这样:

$reportPath = 'C:\SecurityReports' if (!(Test-Path $reportPath)) { New-Item -ItemType Directory -Path $reportPath -Force } # 汇总核心安全日志数量 $since = (Get-Date).AddDays(-1) $failedLogon = (Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625; StartTime=$since} -ErrorAction SilentlyContinue).Count $successLogon = (Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4624; StartTime=$since} -ErrorAction SilentlyContinue).Count $newAccount = (Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4720; StartTime=$since} -ErrorAction SilentlyContinue).Count $psExec = (Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; ID=4104; StartTime=$since} -ErrorAction SilentlyContinue).Count $summary = [pscustomobject]@{ 日期 = Get-Date 失败登录 = $failedLogon 成功登录 = $successLogon 新建账户 = $newAccount PowerShell脚本执行 = $psExec } $summary | Export-Csv (Join-Path $reportPath "security-report-$((Get-Date).ToString('yyyyMMdd')).csv") -NoTypeInformation -Encoding UTF8

注册计划任务每天执行:

$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\daily-security-report.ps1' $trigger = New-ScheduledTaskTrigger -Daily -At 07:00 Register-ScheduledTask -TaskName '每日安全摘要生成' -Action $action -Trigger $trigger -RunLevel Highest

这里用了-ExecutionPolicy Bypass只针对这个计划任务,如果第2部分的脚本签名体系已经做好,建议改成-ExecutionPolicy AllSigned,并给巡检脚本签上名,别为了省事整个大后门。

6. 实际踩坑记录与高频问题排查

PowerShell安全这条路,我前前后后见过不少朋友在同几个地方栽跟头。把这些典型问题集中列出来,你遇到时可以直接对号入座。

现象常见原因解决思路
脚本一运行就报“禁止运行脚本”ExecutionPolicy是Restricted,或组策略覆盖了本地设置Get-ExecutionPolicy -List看所有作用域,优先通过组策略下发RemoteSigned
Set-MpPreference提示拒绝访问当前Shell未提权管理员身份重新打开PowerShell,域名环境检查是否有安全策略锁定Defender
安全日志一查就只有几百条日志大小限制太小,被滚动覆盖wevtutil sl Security /ms:1073741824把日志上限调大到1GB,并开启自动备份
4104事件查不到任何记录脚本块日志未生效,或重启后策略被刷掉确认注册表路径在HKLM\Software\Policies下且策略刷新正常,测试执行一条简单命令后立即查询
防火墙规则建了但不起作用建规则时没限Profile,或默认入站策略被第三方改了用Get-NetFirewallProfile检查DefaultInboundAction,规则按Profile单独建并定期导出校验
全盘扫描跑不完,机器卡死多个扫描线程叠加,或设备IO本来紧张Start-MpScan -ScanType FullScan -AsJob并安排在计划任务空闲时段;排除虚拟机快照目录等确属安全的高IO路径
Defender被第三方杀软禁用后恢复不回来第三方卸载不干净,注册表残留接管项彻底卸载第三方软件,再用官方清理工具清理残留;必要时用Defender自身修复选项刷新状态

另外分享两个很现实的调参经验。第一,排除路径不要随便加。很多人嫌Defender误报烦,直接把整个项目目录或磁盘排除掉,结果等于给恶意软件发通行证。真要排除,只精确排除到单个文件或确有性能瓶颈的目录,并且限定给只读缓存类目录,例如-ExclusionPath 'D:\Cache',别排业务可执行文件目录。第二,可疑进程排查时优先看“签名无效 + 路径不在系统目录 + 开机自启动”三个条件同时成立的对象,单独中一个未必有问题,三个都中,后面基本不用浪费时间了。

7. 一点实操心得

写了这么多,最后想说几句实在话。我见过不少管理员的电脑上装了三四个“安全加强”软件,结果它们互相抢权限,系统防御能力反而更弱。用PowerShell做加固的整个过程,本质上是在做减法:减去不必要的第三方驻留程序,减去乱七八糟的自启动,减去多余的开放端口,然后把Windows自身可信的执行链、日志链、策略链一条条理顺。你要在真实环境动手之前,强烈建议先开一台虚拟机完整过一遍这里的所有命令,特别是执行策略、防火墙默认策略和Defender配置,生产机器上改差了不是开玩笑的。还有个小习惯值得坚持:每次批量操作前,先记下改动了哪些项目,导出当前配置备份,再动手。安全加固这件事,慢就是快,稳就是赢。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询