聊到WinRM,很多人脑海里蹦出来的第一个词可能是“横向移动”。这个标签在安全圈里很常见,但实际在日常运维中,WinRM(Windows Remote Management)是Windows内置的远程管理协议,它的价值远远不止于“攻击面”——它是我批量管理Windows服务器时的头号工具。今天这篇就围绕WinRM聊透:怎么配、怎么用、怎么避开那些坑,以及最关键的一环:怎么防止它被别有用心的人拿去做横向移动。
这篇文章适合三类人:一是每天要跟几十台Windows服务器打交道的运维,二是需要批量执行脚本、收集状态的自动化工程师,三是安全岗的同仁——理解了WinRM的工作机制,才能在攻防演练中做好检测和加固。我尽量用实操中的真实经验来讲,不绕弯子。
1. WinRM是什么?先搞清楚它在干什么
1.1 WinRM的底层逻辑
WinRM是微软基于WS-Management协议实现的远程管理服务,简单说就是让一台Windows机器可以通过网络对另一台Windows机器执行管理命令。它走的是HTTP或者HTTPS,默认端口分别是5985和5986,数据传输格式是SOAP XML。
你可以把它理解成Windows版的SSH。SSH让你能在远程Linux机器上敲命令,WinRM则让你能在远程Windows机器上跑PowerShell、获取系统信息、修改配置、部署软件,甚至可以建立交互式会话,就像坐在那台机器面前一样。
这里得强调一下,WinRM并不是什么偏门功能,它从Windows Server 2012开始就是服务器操作系统的标配服务,Windows 10/11桌面系统也内置了它。只是在桌面系统上默认没有启用,服务器系统上很多企业镜像会预开启,这就给运维和攻防都留下了操作空间。
1.2 为什么它会被盯上
从攻击者的视角看,WinRM是一个“自带遥控器”的通道。一旦拿到一台机器的管理员凭据,并且目标机器开了WinRM,那就可以横向操作内网其他机器——不需要再上传乱七八糟的工具,系统原生功能就够用了。
但从运维的视角看,WinRM的效率同样惊人。你不需要给每台服务器装Agent,不需要额外开共享端口,只用PowerShell一条命令就能同时操作一百台机器。这也是为什么我坚持要在所有生产环境里做WinRM安全加固——它太好用了,好用到让人容易忽略该上的锁。
2. 配置WinRM的完整实操
2.1 快速启用:一条命令的事
在目标机器上用管理员权限打开PowerShell,执行:
Enable-PSRemoting -Force这个命令会做以下几件事:
- 启动WinRM服务,并设置为自动启动;
- 把本机所有IP地址都注册为WinRM监听地址;
- 放行防火墙规则(5985/5986端口);
- 设置LocalAccountTokenFilterPolicy,保证本地管理员能通过远程会话获得完整令牌。
如果只是想快速测试,也可以执行winrm quickconfig,效果类似。但注意,Enable-PSRemoting通常只对手动配置的机器生效。如果机器在域环境里,组策略可能会统一关掉它,这时候需要检查策略。
2.2 自定义监听地址和端口
默认配置下,WinRM监听本机所有IP的5985端口。但这在生产环境里太粗放了,我习惯把它绑到指定内网网卡上,避免暴露到不该暴露的网段。
先把默认监听器删掉:
winrm delete winrm/config/Listener?Address=*+Transport=HTTP再添加一个指定IP的监听器:
winrm set winrm/config/service @{AllowUnencrypted="false"} winrm create winrm/config/Listener?Address=10.0.0.10+Transport=HTTP如果要走HTTPS,得先给主机准备证书。这里有个常用命令:
New-SelfSignedCertificate -DnsName "winrm01.example.local" -CertStoreLocation "Cert:\LocalMachine\My"拿到证书指纹后,创建一个HTTPS监听器:
winrm create winrm/config/Listener?Address=10.0.0.10+Transport=HTTPS @{Hostname="winrm01.example.local";CertificateThumbprint="THUMBPRINT_HERE"}注意证书的主机名必须和客户端连接时用的主机名一致,否则客户端会报信任错误。
2.3 认证方式与权限策略
WinRM支持多种认证方式,包括Kerberos、Negotiate(NTLM)、Basic和Certificate。运维中最常用的是Kerberos和Negotiate,因为它们能传递当前用户的凭据,不需要明文密码。
但在工作组环境下,经常遇到双跳问题(从机器A连到B,再在B上连到C)。这时候需要配置CredSSP:
Enable-WSManCredSSP -Role Client -DelegateComputer "*.example.local" Enable-WSManCredSSP -Role Server -Force不过CredSSP有安全隐患,因为它会把凭据转发给目标机器。我用它只限于受控的内网测试环境,生产环境一律走Kerberos。
权限策略方面,普通用户连接到远程机器后默认只是标准用户权限,要执行管理员操作需要目标机器上配置LocalAccountTokenFilterPolicy为1。这个值默认是0,也就是说即使你本地是管理员,远程连接后也会被降权。很多新手在这里踩坑。可以通过:
New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -Name LocalAccountTokenFilterPolicy -PropertyType DWord -Value 1来放开,但放开意味着更强的风险,建议只在确实需要远程管理的管理员机器上设置,普通服务器不要开。
3. 基于WinRM的批量管理与自动化
3.1 一条命令应对一百台机器
WinRM最爽的地方在Invoke-Command。我可以把服务器列表放在一个文本文件里,再用一条命令并行执行任务。比如查看所有机器的系统启动时间:
$servers = Get-Content "serverlist.txt" Invoke-Command -ComputerName $servers -ScriptBlock { (Get-CimInstance Win32_OperatingSystem).LastBootUpTime } -ThrottleLimit 20-ThrottleLimit是并发数。我实测下来,批量执行时并发调到20左右比较稳,太高容易把目标机器资源吃满,太低效率不明显。
如果需要在远程机器上运行一段较长的脚本,可以先把脚本文件保存为.ps1,然后:
Invoke-Command -ComputerName $servers -FilePath "C:\scripts\deploy.ps1" -ArgumentList "param1","param2"3.2 交互式会话:像坐在这台机器面前
单台机器调试时,Enter-PSSession能直接进入一个交互式PowerShell会话,相当于给你“坐”到远程机器跟前。
Enter-PSSession -ComputerName "server-01" -Credential (Get-Credential)进入后,当前提示符会变成[server-01]: PS C:\>,此时你敲的所有命令都在远程机器上执行。这个功能在排查问题时特别好用,省掉了远程桌面的开销,哪怕带宽再小也能顺畅操作。
但要注意,Enter-PSSession默认不加载远程机器上的模块配置,有些自定义函数和模块需要先Import-Module。所以我一般会在进入会话后先加一行:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "server-01" -Concatenate这是针对工作组环境的,域环境用Kerberos就不会有这个问题。
3.3 和自动化配置管理工具结合
如果你在运维中用了Ansible,那WinRM几乎是Windows节点认证的必备通道。Ansible通过ansible_connection: winrm连接目标机器,然后在上面执行Ansible模块。
一段典型的Ansible主机清单配置:
[windows] win-web01 ansible_host=10.0.0.21 ansible_user=admin@example.local ansible_password=xxxx ansible_connection=winrm ansible_winrm_server_cert_validation=ignore这里的ansible_winrm_server_cert_validation=ignore表示跳过证书校验,只适合测试环境。生产环境一定要配好证书,并设置传输协议为HTTPS。
实际项目中,我用WinRM配合Ansible批量完成了上千台Windows安全补丁的推送。整个过程不需要在服务器上安装额外Agent,只要WinRM服务正常,Ansible就能像SSH操作Linux一样操作Windows,效率非常高。
3.4 一个完整的批量部署案例
假设我要给50台Web服务器更新一个IIS配置节的参数。传统做法是远程桌面一台台改,而用WinRM只需要以下几行:
$servers = Get-Content "webservers.txt" Invoke-Command -ComputerName $servers -ScriptBlock { param($siteName) Set-WebConfigurationProperty -Filter "//system.webServer/security/authentication/anonymousAuthentication" ` -Name enabled -Value $true -PSPath "IIS:\Sites\$siteName" } -ArgumentList "defaultweb"这样在几分钟内就完成了全量更新,而且每台机器的执行结果都会返回到本机控制台,方便记录和审计。
注意Set-WebConfigurationProperty是IIS自带的PowerShell模块,远程机器需要安装有IIS管理模块,否则会报命令缺失错误。因此在批量执行前,最好先在一台机器上测试脚本兼容性。
4. 安全风险与防线:别让招牌服务变成突破口
4.1 为什么WinRM会成为“横向移动”代名词
所谓横向移动,指攻击者拿到一台内网机器权限后,试图借助各类远程管理通道访问其他机器。WinRM之所以经常被提及,原因有三:
- 默认端口开放:很多企业服务器为了运维方便,直接开启了5985端口但没有限制来源IP,等于把门敞开;
- 凭据复用:管理员往往在多台机器上用同一套账号密码,攻下一台就等于拿到整个内网的管理员密码;
- 原生态原生:WinRM是基于系统的合法协议,流量和行为看起来非常正常,很多安全设备默认不拦截。
我在一次内部红队演练中就见过,攻击者用一套窃取到的本地管理员账号,通过WinRM在两分钟内拿下了整个网段的服务器。所以安全防御的核心不是禁用WinRM,而是把它锁到合理的范围内。
4.2 攻击者常走的三条路
描述攻击方式不是为了教人攻击,而是为了帮防守方建立正确的检测点。
第一,用Invoke-Command执行PowerShell命令。这不会在目标机器上落盘文件,纯内存操作。检测端要重点关注5985端口上出现过哪些不寻常的PowerShell连接。
第二,利用WinRM配合工具获取凭据。比如使用Mimikatz获取内存里的明文密码,然后通过WinRM继续连下一台机器。这时候事件日志里会出现多处登录记录。
第三,滥用Kerberos委派。如果域环境配置不当,攻击者可以借用WinRM请求携带着高权限票据访问其他服务。
4.3 检测方案:我能看到什么
Windows的WinRM服务会记录日志,位置在“应用程序和服务日志/Windows/Windows Remote Management”。重点关注以下事件ID:
事件ID 6:WinRM服务初始化失败; 事件ID 101:客户端连接建立; 事件ID 142:客户端认证成功; 事件ID 150:客户端命令执行。
另外,PowerShell日志中,Event ID 4104(脚本块日志)和4103(模块日志)能记录远程执行的PowerShell命令。若看到来源为WSMan的PowerShell连接记录,就需要留意。
我在日常巡检时,会定期用Sysmon或者安全设备抓5985端口的连接元数据。如果发现非工作时间、非运维人员IP段大规模连接,基本可以判定异常。
4.4 加固清单:照着做就能消掉大部分风险
这里是我在生产环境中实际用过的加固方案,每一条都亲测有效:
| 加固项 | 操作建议 | 备注 |
|---|---|---|
| 启用HTTPS | 禁用HTTP监听,使用证书配置5986 | 避免明文凭据传输 |
| 限制来源IP | 防火墙仅允许运维IP或者跳板机IP访问5985/5986 | 最小化暴露面 |
| 关闭不必要的认证方式 | 禁用Basic认证,保留Kerberos | Basic会把用户名密码明文发送 |
| 配置JEA (Just Enough Administration) | 为远程管理用户配置受限的PowerShell端点 | 避免管理员权限过大 |
| 设置MaxShells和MaxProcesses | WinRM服务级并发限制 | 防止资源耗尽和暴力枚举 |
| 启用审计日志 | 开启PowerShell脚本块日志和转录 | 方便事后回溯 |
| 对异常登录做告警 | 结合SIEM监控4624、4625事件 | 勒索攻击前往往有大量登录尝试 |
还有一个容易被忽视的坑:不要把WinRM端口映射到外网。有人图方便在防火墙上做端口转发,结果直接把内网管理端口暴露到公网。一旦被扫描到,暴力破解几乎是迟早的事。
5. 常见问题与排查技巧实录
5.1 连接超时
我早期配置WinRM时遇到最多的问题就是“连接超时”。排查思路很固定:
先确认目标机器WinRM服务是否启动:
Get-Service WinRM再确认防火墙规则:
Get-NetFirewallRule -Name "WINRM*" | Select-Object Name,Enabled,Direction最后测试端口连通性:
Test-NetConnection -ComputerName "server-01" -Port 5985如果结果是TcpTestSucceeded : False,仔细查看防火墙是否放行了局域网配置文件对应的端口。记住:Windows防火墙默认有“域、专用、公用”三种配置文件,WinRM默认只在“域”配置中放行。如果你的机器放在“专用”网络里,记得手动放行。
5.2 认证失败,用户名密码都是对的
这个坑十有八九是TrustedHosts没配置。在工作组环境,或者目标机器没有做域名接入时,WinRM客户端默认只允许来自受信任主机的连接。查看当前受信任主机:
Get-Item WSMan:\localhost\Client\TrustedHosts如果为空,添加:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "server-01" -Concatenate注意-Concatenate表示追加,如果不加会把原有内容覆盖掉。更宽泛的做法是设置星号*,表示信任所有主机,但这样等于丧失了来源限制,强烈不建议。
5.3 连接成功但提示拒绝访问
这通常是因为目标机器上的WinRM服务运行正常,但用户权限不足。在域环境里需要确保当前登录用户是目标机的本地管理员组成员,或者域管理员组里有权限。在纯工作组环境,则需要检查LocalAccountTokenFilterPolicy是否设置,前面已经讲过。
5.4 PowerShell模块无法加载
远程执行时如果要用到特定模块,比如IIS管理、AD管理,需要目标机器上安装对应的RSAT工具或者角色模块。很多时候不是代码的问题,是目标机器缺少模块。解决方案是把模块路径加入远程会话的环境变量:
Invoke-Command -ComputerName server-01 -ScriptBlock { Import-Module ActiveDirectory -ErrorAction Stop Get-ADUser -Filter * }如果目标机器确实没有AD模块,就会报Import-Module : Could not load file or assembly,这种情况建议先在目标机器预装模块再执行。
5.5 双跳问题
所谓双跳,就是你从A机器连到B机器,在B机器上执行命令时,命令里需要再连接C机器(比如B上执行Get-ADUser要连接域控)。默认情况下,B上的第二次连接不会带上你的身份认证,这就导致失败。
解决方案有三个:
- 使用CredSSP,前面已经提过;
- 把需要远程访问的脚本文件放在目标机器本地,在B上使用计划任务执行;
- 在B机器上配置Kerberos约束委派。
第三种最安全,但配置相对复杂。我的建议是:能不用CredSSP就不用,毕竟它会把凭据明文暴露给目标机器。
6. 最后提醒几个值得养成的习惯
用WinRM时间长了,我总结出三个必须刻进肌肉记忆的要点。
第一,每次连接前先确认目标机器是否在运维白名单里。不是技术问题,而是习惯问题。你永远不知道哪台机器因为某个历史原因开着WinRM,如果恰好是DMZ区的机器,连上去可能就是一次安全事故。
第二,所有生产环境的WinRM一律走HTTPS。即便内网有防火墙,明文传输这种风险也不值得冒。证书可以用内部CA统一签发,别用自签名证书糊弄,不然客户端报错排查起来更烦。
第三,批量执行时永远先跑一小批测试。我习惯先对一台机器跑通脚本,确认无异常再扩展到全部。批量操作最怕的不是脚本本身有问题,而是脚本在100台机器上产生了意料之外的副作用,等反应过来已经晚了。
最后再分享一个小技巧:如果觉得Invoke-Command的返回值解析起来麻烦,可以在命令里直接指定输出格式为ConvertTo-Json,这样每条结果都是结构化数据,方便存到日志系统里:
Invoke-Command -ComputerName $servers -ScriptBlock { Get-Process | Select-Object Name,Id,CPU } | ConvertTo-Json -Depth 3 | Out-File "process_report.json"WinRM是一把双刃剑。用得好,它是大规模Windows运维的利器;用不好,它就是内网中一把敞开的门。这篇就是把这把剑的剑柄和剑刃都掰开给你看,怎么握住它,就看你自己的选择了。