导语:毫于疑问,许多攻击者目前已经掌握了多种破解密码的专业知识,其中破解流行密码的字典攻击变得越来越有攻击力。因此,美国国家标准与技术研究院建议为Active Directory配置密码黑名单,以阻止此类攻击。
毫于疑问,许多攻击者目前已经掌握了多种破解密码的专业知识,其中破解流行密码的字典攻击变得越来越有攻击力。因此,美国国家标准与技术研究院建议为Active Directory配置密码黑名单,以阻止此类攻击。不过,为Active Directory配置密码黑名单目前仍然是一项相对较新的创新,企业尚未实现广泛的应用。
然而,对于像Yelp这样注重安全的人气网站来说,他们已经及时采用了此类防御机制。由于Yelp使用Active Directory(AD)对所有员工进行身份验证和管理,因此可以实现他们自己的自定义密码过滤器动态链接库(Password Filter DLL)。Yelp是美国著名商户点评网站,创立于2004年,类似于中国的大众点评网。
在这篇文章中,我将详细描述一下Yelp是如何利用现有的开源DLL来构建密码黑名单服务,以满足安全策略和需求。
解决方案
经过大量的研究和测试,Yelp的安全管理人员发现OpenPasswordFilter(OPF)最符合他们的需求。他们选择了最原始的版本,并给它增加了连接SQL数据库的能力,而不是比较纯文本中的哈希值。使用自定义密码过滤器动态链接库的最大问题之一是本地安全机构(LSA)运行的任何错误都可能导致正在运行的DC服务器崩溃。另一个主要的问题是确保在不影响用户体验的情况下快速有效地添加新密码。所选择的解决方案通过结合面向服务的体系结构解决了这两个问题,该体系结构将基本的LSA线程代码与过滤功能分离,同时还集成了可由服务直接查询的基于SQL的数据库。此外,在过滤过程中,该设计还进行了安全保护,任何触发的异常或错误都将导致系统打开失败,从而最大程度的限制了令人担忧的DC关闭的可能性。鉴于LSA对DLL错误的脆弱性,这是必要的权衡,特别是密码重置被破坏后所造成的记录错误。
系统架构
密码过滤服务由三部分组成:
· OPF Service:用于密码验证的模块(通过 loopback与DLL通信);
· OPF DLL:原始密码过滤器DLL,它与LSA连接以用于凭证验证;
· OPF DB:数据库中包含所有易受攻击密码的SHA-1哈希值;
每当客户端请求更改密码时,此请求将通过其指定的与LSA联系的域控制器进行路由。如果未满足默认密码策略(最小密码长度和某些字符标准的组合),则不会调用密码筛选器DLL,并自动拒绝密码。但是,如果满足默认策略要求,则将使用密码调用DLL。该过程的示意图如下所示:
Active Directory密码过滤器身份验证流程
具体步骤如下,总共7步:
1.客户端通过DC向LSA发起密码请求更改,从DC到LSA的联系是通过配置LSA通知包完成的。如果满足默认密码策略,则将调用已注册的DLL,否则将拒绝密码。
2.PasswordFilter函数是Microsoft的PasswordFilter DLL接口的三种核心方法之一。此函数根据是否应进行重置返回一个布尔响应。OPF版本尝试连接到loopback接口上的特定端口,以调用已注册的服务。如果连接失败,则由于其故障安全性,该函数返回true。
3.如果可以连接建立起来后,则DLL会尝试将凭据发送到OPF服务。首先发送preamble码,即随机接入前导码,然后发送凭证。
4.然后检查哈希凭证(SHA-1)是否存在于容易受损密码的数据库中,对密码哈希值的索引使得这一过程明显更快。
5.根据是否找到哈希,将代表成功或失败的消息返回给OPFService,OPFService以初始DLL的形式,将该消息作为布尔值返回给LSA。
6.对所有已注册的DLL重复上述过程,假设一切都成功,密码将正式提交给安全帐户管理器(SAM)。
7. 然后调用PasswordChangeNotify函数为每个注册DLL进行同步。
安全性测试
最后的安全性测试尤为重要,因为一旦安全性得不到实际保证,则任何实际中的错误都会产生严重影响,比如导致域控制器崩溃。此外,为了确保系统不会因测试而占用大量的系统资源。因此,在测试之前,测试人员先在一个独立的域控制器上测试了该服务,然后再在实验室环境中进行了测试。方法如下:
1.在独立域或实验室域中设置一个DC;
2.根据Yelp的密码策略配置默认的域密码策略设置;
3.下载一些最常被攻击的密码列表,从中进行抽样;
4.将抽样的密码随机分成四个子集,每个大小为5000:
4.1符合默认政策但属于黑名单的子集命名为A;
4.2 符合默认政策但不属于黑名单的子集命名为B;
4.3 不符合默认策略且属于黑名单的子集命名为C;
4.4不符合默认策略且不属于黑名单的子集命名为D;
对于上述四个子集,安全人员只期望子集B可以重置成功。
5.为子集B和子集C生成SHA-1哈希值;
6.在要测试的总密码数量中创建启用的LDAP用户;
7.创建一个CSV文件,每行包含用户、密码和与密码分类相关的类别(A,B,C,D);
8.运行他们定制的PowerShell脚本(下面会提到),以检查已验证的安全行为;
Powershell脚本的详细信息
1.每个启用的用户都尝试重置各自的密码;
2.使用秒表记录所需的总时间;
3.根据退出代码验证预期的重置成功;
4.然后执行AD绑定以验证是否成功登录;
5.预期的AD绑定行为也基于退出代码并要进行验证;
6.结果按密码类别汇总并输出,具体而言,就是每个密码重置的平均时间和发现的错误数量;下面的脚本就是执行上述测试的:
param([string]$file, [string]$dc, [string]$admin_usr, [string]$admin_pwd)# Default error usage message Set-Variable errUsg -option Constant -Value "$($MyInvocation.MyCommand.Name) [CSV_FILE] [DC] [ADMIN_USR] [ADMIN_PWD]"# Ensure proper parameters are given $CommandName = $MyInvocation.InvocationName $ParameterList = (Get-Command -Name $CommandName).Parameters foreach ( $key in $ParameterList.Keys ) { $value = (Get-Variable $key -ErrorAction SilentlyContinue).Value if ([string]::IsNullOrEmpty($value)) { Write-Host "Required parameter not found." Write-Host "$errUsg" exit 1 } }# Check existence of csv fileif (!(test-path $file)) { Write-Host "File $file not found." exit 1}# Set password types and associated expectations (0 = failure, 1 = success)$csv = Import-CSV -Path $file$pwdTypes = @("NO_POLICY_NO_DUMP", "NO_POLICY_YES_DUMP", "YES_POLICY_NO_DUMP", "YES_POLICY_YES_DUMP")$expected = @(0, 0, 1, 0)$times = @(0) * 4$errors = @(0) * 4$counts = @(0) * 4# Check provided DC is valid and that admin login credentials validdsquery user -u $admin_usr -p $admin_pwd -s $dc -q > $null 2>&1if ($LastExitCode -ne 0) { Write-Host "Invalid remote credentials or DC server specified." exit 1}# Start loggingstart-transcript -path C:\nail\syslog\DLL_LOG_$(get-date -format 'MM-dd-yyyy-HH-mm-ss').logForeach ($el in $csv) { # Extracted relevant fields $ex = $expected[$el.Id] $type = $pwdTypes[$el.Id] $usr = dsquery user -samid $el.User -s $dc -u $admin_usr -p $admin_pwd $pwd = $el.Password # Record time in milleseconds for password reset $sw = [system.diagnostics.stopwatch]::startNew() dsmod user $usr -pwd "$pwd" -mustchpwd no -d $dc -u $admin_usr -p "$admin_pwd" > $null 2>&1 $sw.Stop() # Check exit code with expectations to validate reset success $r_s = If ($lastexitcode -ne 0) {0} Else {1} $t = $sw.get_ElapsedMilliseconds() $times[$el.Id] += $t if ($r_s -ne $ex) { $errors[$el.Id]++ } # Perform AD bind and check with expectations to verify successful login (new-object directoryservices.directoryentry "", $usr, $pwd).psbase.name -ne $null > $null 2>&1 $b_s = If ($lastexitcode -ne 0) {0} Else {1} if ($b_s -ne $ex) { $errors[$el.Id]++ } # Log appropriately in the format of (EXPECTED, ACTUAL) for both the reset and bind $logline = "($usr) : [ $type ] : RESET ($ex,$r_s) : BIND ($ex,$b_s) : TIME ($t)" if (($b_s -ne $ex) -or ($r_s -ne $ex)) { $logline = "[ERROR] [PASSWORD = $pwd] $logline" } $counts[$el.Id]++ Write-Output $logline}# Aggregate final resultsWrite-Output ("*" * 22)Write-Output "RESULTS:"for ($i=0; $i -lt $pwdTypes.Length; $i++) { $type = $pwdTypes[$i] if ($counts[$i] -ne 0) { $t = $times[$i] / $counts[$i] } else { $t = 0 } $es = $errors[$i] Write-Output "($type) ERRORS: $es AVG TIME: $t"} stop-transcript
安全测试结果
通过测试,安全管理人员不仅可以验证密码过滤服务的正确性,还可以查看重置时间间隔等数据。
AD密码重置时间间隔(带有DLL))
更重要的是,DLL还进行了边际时间的计算:
AD密码重置时间间隔(带有DLL)
AD密码重置时间间隔(带有DLL)
在对安全人员找出的100万个密码样本进行检测后,他们有了以下发现:
平均剩余时间(在独立的域控制器测试环境下)
平均剩余时间(在试验环境下)
在此,我强烈建议在公司环境中应该采用类似的黑名单措施。至少,它可以确保强不会出现密码被攻击的危险。一句话,黑名单提供了一种更有效的方法来缓解字典攻击,同时又不会产生让密码的保护变得极其复杂。