在Windows管理指令(WMI)中,权限管理是一个重要的环节,它决定了脚本能访问哪些资源和执行哪些操作。本文将介绍如何通过WMI脚本管理权限,包括使用WMI脚本API和.NET Framework进行权限管理。
WMI提供了一系列的权限,这些权限可以在找到。权限可以通过两种方式分配:
通常情况下,WMI脚本不需要分配任何权限即可正常工作。例如,以下是一个简单的VBScript脚本,它连接到本地WMI服务并输出应用程序事件日志中的所有事件消息,而不需要分配任何权限:
Set objSWbemServices = GetObject("winmgmts:")
Set colEvents = objSWbemServices.ExecQuery("Select * From Win32_NTLogEvent Where LogFile = 'Application'")
For Each objEvent In colEvents
WScript.Echo objEvent.Message
Next
这个脚本使用了最基本的标识符连接到WMI服务,并且没有分配任何权限,但是是否隐式地分配了权限呢?以下是一个测试脚本:
Set objSWbemServices = GetObject("winmgmts:")
Set objSWbemPrivileges = objSWbemServices.Security_.Privileges
WScript.Echo "WMI privilege count: " & objSWbemPrivileges.Count
输出结果为:
WMI privilege count: 0
因此,无论是显式还是隐式,都没有分配任何权限。这对于应用程序事件日志来说是可以的,但如果尝试枚举安全事件日志中的事件:
Set objSWbemServices = GetObject("winmgmts:")
Set colEvents = objSWbemServices.ExecQuery("Select * From Win32_NTLogEvent Where LogFile = 'Security'")
WScript.Echo colEvents.Count
For Each objEvent In colEvents
WScript.Echo objEvent.Message
Next
如果没有分配权限,即使安全事件日志中充满了条目,也不会枚举出任何事件,SWbemObjectSet.Count的值为0。要使这个脚本正常工作,需要为SWbemServices分配wbemPrivilegeSecurity(7)权限,以下是使用WMI标识符分配权限的方法:
Set objSWbemServices = GetObject("winmgmts:{(Security)}")
Set colEvents = objSWbemServices.ExecQuery("Select * From Win32_NTLogEvent Where LogFile = 'Security'")
WScript.Echo colEvents.Count
For Each objEvent In colEvents
WScript.Echo objEvent.Message
Next
当使用标识符添加权限时,权限名称实际上是‘Security’,而不是wbemPrivilegeSecurity。可以在找到其他适合与标识符一起使用的权限名称(在‘Scripting short name’下)。
如何知道获取安全事件日志条目需要特殊权限?这是有文档记录的,因此在编写WMI脚本时阅读WMI类文档是个好主意。
有趣的是,如果不分配权限,脚本将不会工作,但不会有任何错误——SWbemServices.ExecQuery将只返回一个空集合。
以下是另一个没有适当权限就无法工作的示例脚本:
Set objSWbemServices = GetObject("winmgmts:")
Set colProcesses = objSWbemServices.ExecQuery("Select * From Win32_Process Where Name = 'sqlservr.exe'")
For Each objProcess In colProcesses
WScript.Echo objProcess.Terminate
Next
SQL Server在SYSTEM账户下运行,因此Win32_Process.Terminate()将返回错误代码2——‘访问被拒绝’,因为没有以SYSTEM账户连接到WMI(这是有文档记录的)。在这种情况下,需要使用wbemPrivilegeDebug(19)。以下是工作的脚本:
Set objSWbemServices = GetObject("winmgmts:{(Debug)}")
Set colProcesses = objSWbemServices.ExecQuery("Select * From Win32_Process Where Name = 'sqlservr.exe'")
For Each objProcess In colProcesses
WScript.Echo objProcess.Terminate
Next
备份事件日志文件是另一个需要特殊权限的操作——wbemPrivilegeBackup(16):
Set objSWbemServices = GetObject("winmgmts:")
Set colEventLogFiles = objSWbemServices.ExecQuery("Select * From Win32_NTEventLogFile Where LogFileName = 'Application'")
For Each objEventLogFile In colEventLogFiles
WScript.Echo objEventLogFile.BackupEventLog("C:\\scripts\\Application.evt")
Next
如果运行这个脚本,将得到以下错误:
SWbemObjectEx: Access denied
当使用wbemPrivilegeBackup时,脚本将工作:
Set objSWbemServices = GetObject("winmgmts:{(backup)}")
Set colEventLogFiles = objSWbemServices.ExecQuery("Select * From Win32_NTEventLogFile Where LogFileName = 'Application'")
For Each objEventLogFile In colEventLogFiles
WScript.Echo objEventLogFile.BackupEventLog("C:\\scripts\\Application.evt")
Next
值得注意的是,WMI在报告缺少权限时并不一致:在第一种情况下根本没有错误,在第二个例子中Win32_Process.Terminate返回了表示缺少权限的错误代码并继续,在第三个脚本中Win32_NTEventLogFile.BackupEventLog导致脚本崩溃——这在调试WMI脚本时并不是很有帮助。
这里还有一些更令人困惑的信息:要备份安全日志文件,需要同时拥有Security和Backup权限。如果只使用Security权限:
Set objSWbemServices = GetObject("winmgmts:{(Security)}")
Set colEventLogFiles = objSWbemServices.ExecQuery("Select * From Win32_NTEventLogFile Where LogFileName = 'Security'")
For Each objEventLogFile In colEventLogFiles
WScript.Echo objEventLogFile.BackupEventLog("C:\\scripts\\Security.evt")
Next
将得到‘访问被拒绝’的错误,但如果只使用Backup权限:
Set objSWbemServices = GetObject("winmgmts:{(Backup)}")
Set colEventLogFiles = objSWbemServices.ExecQuery("Select * From Win32_NTEventLogFile Where LogFileName = 'Security'")
For Each objEventLogFile In colEventLogFiles
WScript.Echo objEventLogFile.BackupEventLog("C:\\scripts\\Security.evt")
Next
Win32_NTEventLogFile.BackupeventLog将返回5,这是一个在文档中没有列出的错误代码。要使其工作,需要使用两个权限:
Set objSWbemServices = GetObject("winmgmts:{(Security, Backup)}")
Set colEventLogFiles = objSWbemServices.ExecQuery("Select * From Win32_NTEventLogFile Where LogFileName = 'Security'")
For Each objEventLogFile In colEventLogFiles
WScript.Echo objEventLogFile.BackupEventLog("C:\\scripts\\Security.evt")
Next
另一种分配WMI权限的方法是使用SWbemPrivilege和SWbemPrivilegeSet对象:
Set objSWbemServices = GetObject("winmgmts:")
objSWbemServices.Security_.Privileges.AddAsString "SeSecurityPrivilege"
objSWbemServices.Security_.Privileges.AddAsString "SeBackupPrivilege"
Set colEventLogFiles = objSWbemServices.ExecQuery("Select * From Win32_NTEventLogFile Where LogFileName = 'Security'")
For Each objEventLogFile In colEventLogFiles
WScript.Echo objEventLogFile.BackupEventLog("C:\\scripts\\Security.evt")
Next
SWbemService有一个名为Security_的属性,它实际上是一个SWbemSecurity对象,该对象又有一个Privileges属性。这是一个SWbemPrivilegeSet对象,可以使用它的AddAsString和Add方法为WMI分配权限。当使用AddAsString时,使用定义的权限名称(所有名称都以Se***开头,如SeDebugPrivilege),当使用Add方法时,使用代表特定权限的整数(例如,对于SeDebugPrivilege,将使用19)。
可以使用SWbemPrivilege对象列出所有可用的权限。上述链接列出了27个可用的权限,可以使用For循环将它们全部添加:
Set objSWbemServices = GetObject("winmgmts:")
Set colPrivileges = objSWbemServices.Security_.Privileges
For i = 1 To 27
colPrivileges.Add i
Next
For Each objPrivilege In colPrivileges
WScript.Echo objPrivilege.Identifier & vbTab & objPrivilege.Name & vbTab & objPrivilege.DisplayName
Next
并使用For..Each列出每个权限的信息。
.NET Framework的System.Management命名空间简化了所有这些:有一个名为ConnectionOptions.EnablePrivileges的属性,当设置为true时,启用所有可能的权限。以下是一个C#示例:
using System;
using System.Management;
class Program
{
static void Main()
{
ManagementScope scope = new ManagementScope();
scope.Options.EnablePrivileges = true;
ObjectQuery query = new ObjectQuery(@"
Select * From Win32_NtLogEvent "
+ @"
Where LogFile = 'Security'");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
foreach (ManagementObject logEvent in searcher.Get())
{
Console.WriteLine(logEvent["Message"]);
}
}
}
对于PowerShell,有一个名为–EnableAllPrivileges的开关用于V2,它也启用所有权限(全部在一行中):
Get-WmiObject Win32_NTLogEvent -filter "LogFile='Security'" -EnableAllPrivileges
$Searcher = New-Object System.Management.ManagementObjectSearcher
$Searcher.Scope.Options.EnablePrivileges = $true
$Searcher.Query = "Select * From Win32_NTLogEvent Where LogFile = 'Security'"
foreach ($Event in $Searcher.Get())
{
$Event.EventCode
$Event.TimeGenerated
}