Windows安全基线落地实战:LGPO批量下发

适用场景

Windows 安全基线这件事,在有域控的环境里通常靠组策略统一推送,但现实中大量中小企业是工作组环境:十几台 Windows Server 跑着业务系统,几十台办公终端分散在各部门,安全整改要求下来的时候,只能一台台远程登录点 gpedit,点完还没有留痕,过两个月重装一台又回到默认状态。本文给出一套基于 LGPO.exe 的批量下发方案,把基线做成可版本管理、可 diff、可回滚的产物。

典型需要覆盖的项包括:关闭 SMBv1、限制匿名枚举、收紧账户锁定与密码策略、限制 RDP 空闲断开、强制开启进程创建审核并记录命令行。这些项分别落在注册表策略与安全策略两个不同载体上,处理方式不一样,下面分开讲。

前置条件

  • Windows Server 2016 以上或 Windows 10 1809 以上,PowerShell 5.1 可用;
  • 具备本地管理员权限,且能通过 \主机名\C$ 或 WinRM 访问目标机器;
  • 已下载 Microsoft Security Compliance Toolkit 中的 LGPO.zip,解压后是单个可执行文件,绿色免安装;
  • 至少一台模板机,用于先配置再导出,避免手写策略时格式出错;
  • 一台管理机存放基线产物与分发脚本,最好是文件服务器或 Git 仓库。

原理说明

本地组策略在磁盘上分两块。注册表策略存在 C:\Windows\System32\GroupPolicy\Machine\registry.pol,这是二进制格式,gpedit 里「管理模板」下的设置最终都写进这里;安全策略存在同一目录下的 microsoft\windows nt\SecEdit\GptTmpl.inf,账户策略、用户权限分配、安全选项、审核策略都在这个文本文件里。

这两块载体决定了工具的分工:LGPO.exe /g 导入整个 GPO 备份目录,LGPO.exe /s 导入单个 GptTmpl.inf,两者互补。registry.pol 不适合手写,所以正确做法是在模板机上用 gpedit 点出想要的设置,再用 /b 导出成目录;而 GptTmpl.inf 本身就是文本,可以直接写、直接做版本 diff。

优先级必须先讲清楚,否则做完了发现不生效会白排查。本地 GPO 的优先级低于域 GPO 与站点 GPO,域内机器上同一项设置如果被域策略管理,本地改多少都会被覆盖。所以这套方案的主战场是工作组终端、DMZ 服务器、以及不属于域的测试机。域内机器要改,应该改域 GPO,或者在域内用细粒度密码策略。

操作步骤

第一步:准备 LGPO 与目录结构

把工具和产物分开存放,产物按日期归档,便于回滚时找到上一版。

:: 解压到固定路径,后续脚本都引用这个位置
mkdir C:\Tools\LGPO
expand C:\Temp\LGPO.zip -F:* C:\Tools\LGPO

:: 目录规划
mkdir C:\Baseline
mkdir C:\Baseline\Backup      :: 每台机器的原始策略备份
mkdir C:\Baseline\Baseline    :: 基线产物(GPO 目录 + inf 文件)
mkdir C:\Baseline\Logs

C:\Tools\LGPO\LGPO.exe /?

第二步:在模板机上配置并导出

模板机选一台干净的、与目标机器同版本的系统。按基线要求逐项配置完成后导出,导出结果是一个带 GUID 的目录,整目录拷贝就是可复用的基线包。

:: /b 导出本地 GPO 备份,/n 指定可读名称
C:\Tools\LGPO\LGPO.exe /b C:\Baseline\Baseline\GPO_Backup /n WinSrv-Baseline-2026

:: 产物结构如下(GUID 每次导出都不同)
:: C:\Baseline\Baseline\GPO_Backup\{3F2A1C9E-8B4D-4E21-9C77-0A1B2C3D4E5F}
::   \DomainSysvol\GPO\Machine\registry.pol
::   \DomainSysvol\GPO\Machine\microsoft\windows nt\SecEdit\GptTmpl.inf

:: 把 registry.pol 解析成文本,用于版本管理与人眼 review
C:\Tools\LGPO\LGPO.exe /parse /m ^
  "C:\Baseline\Baseline\GPO_Backup\{3F2A1C9E-8B4D-4E21-9C77-0A1B2C3D4E5F}\DomainSysvol\GPO\Machine\registry.pol" ^
  /out C:\Baseline\Baseline\registry-expected.txt

第三步:用 GptTmpl.inf 写安全策略

账户锁定、密码策略、匿名枚举、SMBv1 这些项写在 inf 里比在界面上点更可控,也方便进 Git。下面是一份可直接使用的片段,按需裁剪:

[Unicode]
Unicode=yes
[Version]
signature="$CHICAGO$"
Revision=1

[System Access]
MinimumPasswordLength = 14
PasswordComplexity = 1
MaximumPasswordAge = 60
MinimumPasswordAge = 1
LockoutBadCount = 5
ResetLockoutCount = 15
LockoutDuration = 15

[Event Audit]
AuditSystemEvents = 3
AuditLogonEvents = 3
AuditObjectAccess = 2
AuditPolicyChange = 3
AuditAccountManage = 3

[Registry Values]
; 关闭 SMBv1 服务端,4 表示 REG_DWORD
MACHINE\System\CurrentControlSet\Services\LanmanServer\Parameters\SMB1=4,0
; 限制匿名枚举 SAM 与共享
MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymous=4,1
MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymousSAM=4,1
MACHINE\System\CurrentControlSet\Control\Lsa\EveryoneIncludesAnonymous=4,0
; 进程创建审核时记录完整命令行
MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit\ProcessCreationIncludeCmdLine_Enabled=4,1
; 关闭自动播放
MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\NoDriveTypeAutoRun=4,255

格式要留意:[Registry Values] 里每一行是「键路径=类型,数据」,类型 4 是 REG_DWORD,1 是 REG_SZ,7 是 REG_MULTI_SZ。路径里不能带 HKEY_LOCAL_MACHINE\ 前缀,必须换成 MACHINE\。文件必须存成 UTF-16 LE 带 BOM,用记事本另存时选「Unicode」,存成 UTF-8 会导致中文项名乱码、导入部分失败。

第四步:批量下发

下发顺序是先备份、再导注册表策略、再导安全策略、最后刷新生效。备份这一步不能省,它就是回滚包。

$hosts = Get-Content C:\Baseline\hosts.txt
$src   = "C:\Baseline\Baseline"
$gpo   = "C:\Baseline\Baseline\GPO_Backup\{3F2A1C9E-8B4D-4E21-9C77-0A1B2C3D4E5F}"

foreach ($h in $hosts) {
    if (-not (Test-Connection -ComputerName $h -Count 1 -Quiet)) {
        "$h offline" | Out-File C:\Baseline\Logs\deploy.log -Append
        continue
    }
    Copy-Item "$src\*" -Destination "\\$h\C$\Baseline\" -Recurse -Force
    Copy-Item "C:\Tools\LGPO\LGPO.exe" -Destination "\\$h\C$\Baseline\" -Force

    Invoke-Command -ComputerName $h -ScriptBlock {
        param($gpo)
        & C:\Baseline\LGPO.exe /b C:\Baseline\Backup /n "Before-$(Get-Date -f yyyyMMdd)" | Out-Null
        & C:\Baseline\LGPO.exe /g $gpo
        & C:\Baseline\LGPO.exe /s C:\Baseline\security-baseline.inf
        gpupdate /force | Out-Null
        "baseline applied: $env:COMPUTERNAME"
    } -ArgumentList $gpo | Out-File C:\Baseline\Logs\deploy.log -Append
}

第五步:补审核子类别策略

[Event Audit] 段是老式审核策略,粒度粗。要精确控制子类别,比如只记录进程创建成功事件、不记对象访问,用 auditpol 更合适,而且命令行参数可以直接进脚本:

auditpol /set /subcategory:"Process Creation" /success:enable /failure:disable
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
auditpol /set /subcategory:"Special Logon" /success:enable
auditpol /set /subcategory:"Security Group Management" /success:enable
auditpol /set /subcategory:"Audit Policy Change" /success:enable /failure:enable
auditpol /set /subcategory:"Filtering Platform Connection" /success:disable

:: 导出当前审核配置,纳入基线归档
auditpol /backup /file:C:\Baseline\Baseline\auditpol-backup.csv
auditpol /get /category:* /r

审核策略改完要确认日志容量,否则事件很快把安全日志冲掉。建议安全日志调到 256MB 以上并开启覆盖时归档:

wevtutil sl Security /ms:268435456
wevtutil gl Security | findstr /i "maxSize retention"

配置验证

三项验证分别对应「策略写进去了没」「系统行为变了没」「审核出得来数据没」。第一条最关键,能区分「没下发成功」和「下发成功但被覆盖」这两类完全不同的故障。

:: 1. 策略是否真的写进了本地 GPO
C:\Tools\LGPO\LGPO.exe /parse /m C:\Windows\System32\GroupPolicy\Machine\registry.pol ^
  /out C:\Baseline\applied.txt
fc /n C:\Baseline\Baseline\registry-expected.txt C:\Baseline\applied.txt

:: 2. 系统行为是否已改变
Get-SmbServerConfiguration | Select-Object EnableSMB1Protocol
reg query "HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" /v SMB1
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v RestrictAnonymous

:: 3. 账户策略与审核策略
net accounts
auditpol /get /subcategory:"Process Creation"

:: 4. 进程创建审核是否带命令行
Get-WinEvent -LogName Security -MaxEvents 20 |
  Where-Object { $_.Id -eq 4688 } |
  Select-Object TimeCreated, @{n='cmd';e={$_.Properties[8].Value}} | Format-Table -Auto

常见问题

Q1:registry.pol 和 GptTmpl.inf 有什么区别,为什么两个都要下发?

前者管注册表策略,也就是 gpedit 里「计算机配置 – 管理模板」下的项,格式是二进制;后者管安全策略,包括账户策略、用户权限分配、安全选项与审核策略,格式是文本 inf。两套东西存放位置不同、作用机制不同,LGPO 也对应两个开关:/g 导入整个备份目录,/s 导入单个 inf。只做其中一个,基线一定是不完整的。

Q2:下发显示成功了,但设置没生效,怎么定位?

按三步排查。先看 LGPO.exe /parse 输出的 applied.txt 与基线文本是否一致,不一致说明根本没写进去,通常是路径权限或 pol 文件版本不匹配;一致但行为没变,说明被更高优先级的策略覆盖,域内机器查 gpresult /h 的获胜 GPO;再排除是否需要重启,SMBv1、LSA 保护、审核策略中的部分项必须重启相关服务甚至整机才生效,只跑 gpupdate /force 不够。

Q3:出错后怎么回滚?

回滚包就是第一步 /b 导出的 Before 目录,指回去即可:LGPO.exe /g "C:\Baseline\Backup\{原GUID}",然后 gpupdate /force。要注意账户策略类改动不可完全回滚:把最小密码长度从 14 改回 8,已存在的短密码不会失效,只是下次改密时允许更短;锁定阈值回滚也不会解锁已被锁定的账号。所以账户策略调整要放在变更窗口内做,并提前通知。

Q4:域内机器和工作组机器能不能用同一套基线?

可以用同一套产物,但预期不同。域内机器的本地 GPO 优先级最低,凡是被域策略管理的项都会被覆盖,表现是「明明导入了却没变化」。正确做法是域内改域 GPO,本地 LGPO 只覆盖域里没管的项;工作组终端和 DMZ 服务器才是这套方案的主场,效果最直接。

总结

LGPO 的价值不在于它能改设置,而在于它把「安全基线」变成了文件:可以进 Git、可以 review、可以 diff、可以回滚。落地时记住四件事:模板机上点配置、/b 导出成基线包;安全选项用 GptTmpl.inf 手写更可控,注意存成 UTF-16;下发前必须 /b 备份原始策略;验证时用 /parse 对比期望与实际,先把「没写进去」和「被覆盖」区分开。把这套流程跑顺之后,新装一台服务器的加固可以压缩到一条命令,这才是基线该有的样子。