Communitygithub.com

分析憑證透明度以偵測釣魚攻擊

利用 crt.sh 和 Certstream 監控憑證透明度(CT)日誌,以早期發現偽造合法品牌的釣魚網域、可疑的憑證發行,以及與攻擊相關的基礎設施。

分析憑證透明度以偵測釣魚攻擊 是什麼?

此技能旨在協助安全分析師與威脅獵人,透過監控憑證透明度(Certificate Transparency, CT)日誌,來主動發現釣魚攻擊的早期跡象。CT 是一項公開的、僅能附加的 SSL/TLS 憑證日誌系統,根據 2025 年的瀏覽器規範,所有公開信任的憑證都必須記錄在 CT 日誌中,這使得它成為一種極具價值的即時威脅情報來源。 此技能支援兩個主要平台:用於歷史查詢的 crt.sh(可進行萬用字元與品牌關鍵字搜尋),以及用於即時監控的 Certstream(基於 WebSocket 串流)。工作流程包含四個關鍵步驟:利用 crt.sh 查詢歷史憑證以找出可疑的近似網域、使用 Certstream 啟動即時偵測(包括基於編輯距離(Levenshtein distance)的域名搶註(typosquatting)檢測)、枚舉 CT 日誌中與目標網域相關的所有子網域,最後產出一份包含可疑憑證、即時警報以及具體建議(例如 DNS 黑洞、撤銷申請與 CAA 記錄設定)的結構化威脅情報報告。 此技能對於 SOC(安全營運中心)團隊建置自動化警報、紅隊進行攻擊面評估,以及藍隊驗證監控覆蓋範圍特別有價值。所有 Python 程式碼範例皆為可執行狀態,並說明了如何整合 `requests`、`certstream`、`tldextract` 和 `Levenshtein` 函式庫,以建立一個完整的 CT 監控管道。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/mukul975/Anthropic-Cybersecurity-Skills/tree/main/skills/analyzing-certificate-transparency-for-phishing

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

分析憑證透明度以偵測釣魚攻擊 是做什麼的?

概述

憑證透明度(Certificate Transparency, CT)是一項網際網路安全標準,建立了一個公開、僅能附加的 SSL/TLS 憑證發行日誌。監控 CT 日誌能夠早期發現以下威脅:註冊偽造合法品牌的釣魚網域、未經授權的憑證發行(針對自己擁有的網域),以及基於憑證的攻擊基礎設施。本技能涵蓋透過 crt.sh 查詢 CT 日誌、使用 Certstream 進行即時監控、建立針對可疑憑證的自動警報機制,以及將發現結果整合到威脅情報工作流程中。

使用時機

  • 調查需要分析憑證透明度以找出釣魚跡象的安全事件時
  • 針對此領域建置偵測規則或威脅狩獵查詢時
  • SOC 分析師需要此類分析的結構化流程時
  • 驗證安全監控是否涵蓋相關攻擊技術時

先決條件

  • Python 3.9 或以上版本,並安裝 requests、certstream、tldextract、Levenshtein 函式庫
  • 可存取 crt.sh (https://crt.sh/) 以查詢歷史 CT 日誌
  • 可使用 Certstream (https://certstream.calidog.io/) 進行即時監控
  • 準備要監控的組織網域與品牌關鍵字清單
  • 了解 SSL/TLS 憑證結構及發行流程

關鍵概念

憑證透明度日誌

CT 日誌是經過加密保證、公開可審計、僅能附加的 TLS 憑證發行記錄。主要憑證授權單位(CA)(如 Let's Encrypt、DigiCert、Sectigo、Google Trust Services)會將所有已發行的憑證提交到多個 CT 日誌。截至 2025 年,Chrome 和 Safari 要求所有公開信任的憑證都必須經過 CT 記錄。

透過 CT 進行釣魚偵測

攻擊者註冊近似網域並取得免費憑證(通常來自 Let's Encrypt),以讓釣魚網站看起來具有 HTTPS 的可信外觀。CT 監控之所以能夠早期發現這些行為,是因為憑證會比釣魚活動開始前更早出現在日誌中,從而提供主動封鎖的窗口。

crt.sh 資料庫

crt.sh 是由 Sectigo 營運的免費網頁介面與 PostgreSQL 資料庫,用於索引 CT 日誌。它支援萬用字元搜尋(%.example.com)、直接 SQL 查詢以及 JSON API 回應。crt.sh 會追蹤所有主要 CT 日誌中的憑證發行、到期及撤銷狀態。

工作流程

步驟 1:查詢 crt.sh 以取得憑證歷史

import requests
import json
from datetime import datetime
import tldextract

class CTLogMonitor:
    CRT_SH_URL = "https://crt.sh"

    def __init__(self, monitored_domains, brand_keywords):
        self.monitored_domains = monitored_domains
        self.brand_keywords = [k.lower() for k in brand_keywords]

    def query_crt_sh(self, domain, include_expired=False):
        """查詢 crt.sh 以取得符合網域的憑證。"""
        params = {
            "q": f"%.{domain}",
            "output": "json",
        }
        if not include_expired:
            params["exclude"] = "expired"

        resp = requests.get(self.CRT_SH_URL, params=params, timeout=30)
        if resp.status_code == 200:
            certs = resp.json()
            print(f"[+] crt.sh:*.{domain} 共有 {len(certs)} 張憑證")
            return certs
        return []

    def find_suspicious_certs(self, domain):
        """找出可能是釣魚嘗試的憑證。"""
        certs = self.query_crt_sh(domain)
        suspicious = []

        for cert in certs:
            common_name = cert.get("common_name", "").lower()
            name_value = cert.get("name_value", "").lower()
            issuer = cert.get("issuer_name", "")
            not_before = cert.get("not_before", "")
            not_after = cert.get("not_after", "")

            # 檢查是否為完全網域相符(合法憑證)
            extracted = tldextract.extract(common_name)
            cert_domain = f"{extracted.domain}.{extracted.suffix}"
            if cert_domain == domain:
                continue  # 合法憑證

            # 標記可疑模式
            flags = []
            if domain.replace(".", "") in common_name.replace(".", ""):
                flags.append("包含目標網域字串")
            if any(kw in common_name for kw in self.brand_keywords):
                flags.append("包含品牌關鍵字")
            if "let's encrypt" in issuer.lower():
                flags.append("免費 CA (Let's Encrypt)")

            if flags:
                suspicious.append({
                    "common_name": cert.get("common_name", ""),
                    "name_value": cert.get("name_value", ""),
                    "issuer": issuer,
                    "not_before": not_before,
                    "not_after": not_after,
                    "serial": cert.get("serial_number", ""),
                    "flags": flags,
                    "crt_sh_id": cert.get("id", ""),
                    "crt_sh_url": f"https://crt.sh/?id={cert.get('id', '')}",
                })

        print(f"[+] 發現 {len(suspicious)} 張可疑憑證")
        return suspicious

monitor = CTLogMonitor(
    monitored_domains=["mycompany.com", "mycompany.org"],
    brand_keywords=["mycompany", "mybrand", "myproduct"],
)
suspicious = monitor.find_suspicious_certs("mycompany.com")
for cert in suspicious[:5]:
    print(f"  [{cert['common_name']}] 標記:{cert['flags']}")

步驟 2:使用 Certstream 進行即時監控

import certstream
import Levenshtein
import re
from datetime import datetime

class CertstreamMonitor:
    def __init__(self, watched_domains, brand_keywords, similarity_threshold=0.8):
        self.watched_domains = [d.lower() for d in watched_domains]
        self.brand_keywords = [k.lower() for k in brand_keywords]
        self.threshold = similarity_threshold
        self.alerts = []

    def start_monitoring(self, max_alerts=100):
        """開始即時 CT 日誌監控。"""
        print("[*] 啟動 Certstream 監控...")
        print(f"    監控對象:{self.watched_domains}")
        print(f"    關鍵字:{self.brand_keywords}")

        def callback(message, context):
            if message["message_type"] == "certificate_update":
                data = message["data"]
                leaf = data.get("leaf_cert", {})
                all_domains = leaf.get("all_domains", [])

                for domain in all_domains:
                    domain_lower = domain.lower().strip("*.")
                    if self._is_suspicious(domain_lower):
                        alert = {
                            "domain": domain,
                            "all_domains": all_domains,
                            "issuer": leaf.get("issuer", {}).get("O", ""),
                            "fingerprint": leaf.get("fingerprint", ""),
                            "not_before": leaf.get("not_before", ""),
                            "detected_at": datetime.now().isoformat(),
                            "reason": self._get_reason(domain_lower),
                        }
                        self.alerts.append(alert)
                        print(f"  [警報] {domain} - {alert['reason']}")

                        if len(self.alerts) >= max_alerts:
                            raise KeyboardInterrupt

        try:
            certstream.listen_for_events(callback, url="wss://certstream.calidog.io/")
        except KeyboardInterrupt:
            print(f"\n[+] 監控已停止。共收集 {len(self.alerts)} 個警報。")
        return self.alerts

    def _is_suspicious(self, domain):
        """檢查網域相對於監控對象是否可疑。"""
        for watched in self.watched_domains:
            # 精確關鍵字比對
            watched_base = watched.split(".")[0]
            if watched_base in domain and domain != watched:
                return True

            # Levenshtein 距離(域名搶註偵測)
            domain_base = tldextract.extract(domain).domain
            similarity = Levenshtein.ratio(watched_base, domain_base)
            if similarity >= self.threshold and domain_base != watched_base:
                return True

        # 品牌關鍵字比對
        for keyword in self.brand_keywords:
            if keyword in domain:
                return True

        return False

    def _get_reason(self, domain):
        """判斷網域被標記的原因。"""
        reasons = []
        for watched in self.watched_domains:
            watched_base = watched.split(".")[0]
            if watched_base in domain:
                reasons.append(f"包含 '{watched_base}'")
            domain_base = tldextract.extract(domain).domain
            similarity = Levenshtein.ratio(watched_base, domain_base)
            if similarity >= self.threshold and domain_base != watched_base:
                reasons.append(f"與 '{watched}' 相似 ({similarity:.0%})")
        for kw in self.brand_keywords:
            if kw in domain:
                reasons.append(f"品牌關鍵字 '{kw}'")
        return "; ".join(reasons) if reasons else "未知"

cs_monitor = CertstreamMonitor(
    watched_domains=["mycompany.com"],
    brand_keywords=["mycompany", "mybrand"],
    similarity_threshold=0.75,
)
alerts = cs_monitor.start_monitoring(max_alerts=50)

步驟 3:從 CT 日誌枚舉子網域

def enumerate_subdomains_ct(domain):
    """從憑證透明度日誌中發現所有子網域。"""
    params = {"q": f"%.{domain}", "output": "json"}
    resp = requests.get("https://crt.sh", params=params, timeout=30)

    if resp.status_code != 200:
        return []

    certs = resp.json()
    subdomains = set()
    for cert in certs:
        name_value = cert.get("name_value", "")
        for name in name_value.split("\n"):
            name = name.strip().lower()
            if name.endswith(f".{domain}") or name == domain:
                name = name.lstrip("*.")
                subdomains.add(name)

    sorted_subs = sorted(subdomains)
    print(f"[+] 針對 {domain} 的 CT 子網域枚舉:共 {len(sorted_subs)} 個子網域")
    return sorted_subs

subdomains = enumerate_subdomains_ct("example.com")
for sub in subdomains[:20]:
    print(f"  {sub}")

步驟 4:產生 CT 情報報告

def generate_ct_report(suspicious_certs, certstream_alerts, domain):
    report = f"""# 憑證透明度情報報告
## 目標網域:{domain}
## 產生時間:{datetime.now().isoformat()}

## 摘要
- 發現可疑憑證數量:{len(suspicious_certs)}
- 觸發即時警報數量:{len(certstream_alerts)}

## 可疑憑證(crt.sh)
| 通用名稱 | 發行者 | 標記 | crt.sh 連結 |
|------------|--------|-------|-------------|
"""
    for cert in suspicious_certs[:20]:
        flags = "; ".join(cert.get("flags", []))
        report += (f"| {cert['common_name']} | {cert['issuer'][:30]} "
                   f"| {flags} | [檢視]({cert['crt_sh_url']}) |\n")

    report += f"""
## 即時 Certstream 警報
| 網域 | 發行者 | 原因 | 偵測時間 |
|--------|--------|--------|----------|
"""
    for alert in certstream_alerts[:20]:
        report += (f"| {alert['domain']} | {alert['issuer']} "
                   f"| {alert['reason']} | {alert['detected_at'][:19]} |\n")

    report += """
## 建議
1. 將被標記的網域加入 DNS 黑洞 / 網頁代理封鎖清單
2. 針對確認的釣魚網域提交下架請求
3. 持續監控 CT 日誌以發現新的憑證註冊
4. 部署 CAA DNS 記錄以限制你網域的憑證發行
5. 部署 DMARC 以防止來自近似網域的電子郵件詐騙
"""
    with open(f"ct_report_{domain.replace('.','_')}.md", "w") as f:
        f.write(report)
    print(f"[+] CT 報告已儲存")
    return report

generate_ct_report(suspicious, alerts if 'alerts' in dir() else [], "mycompany.com")

驗證標準

  • crt.sh 查詢能成功回傳目標網域的憑證資料
  • 能根據近似模式識別出可疑憑證
  • Certstream 即時監控能偵測到新的釣魚憑證
  • 子網域枚舉能從 CT 日誌中產生完整的清單
  • 警報需附帶原因分類
  • CT 情報報告包含可執行的建議

參考資料

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

abusing-dpapi-for-credential-access

Extract and decrypt Windows DPAPI-protected secrets (Credential Manager, browser logins/cookies, Wi-Fi credentials, KeePass keys) online or offline using SharpDPAPI, SharpChrome, Mimikatz, or Impacket

abusing-shadow-credentials-for-privesc

Take over Active Directory accounts by writing attacker-controlled public keys to msDS-KeyCredentialLink (Shadow Credentials) with pyWhisker, Whisker, or Certipy, then authenticate via PKINIT to recover the target

achieving-cmmc-level-2-compliance

>-

acquiring-disk-image-with-dd-and-dcfldd

Create forensically sound bit-for-bit disk images with dd or dcfldd on a Linux forensic workstation, preserving evidence integrity through hash verification (MD5/SHA) during acquisition. Use when imaging a suspect drive, USB device, or memory card for investigation, preserving volatile disk evidence during incident response, or producing a verified copy for legal or law-enforcement proceedings before any destructive analysis.

analyzing-active-directory-acl-abuse

Detect dangerous ACL misconfigurations in Active Directory using ldap3

analyzing-android-malware-with-apktool

Perform static analysis of Android APK malware using apktool for resource decompilation, jadx for Java source recovery, and androguard for manifest inspection, dangerous permission-combination detection, and identification of obfuscated code, dynamic code loading, and reflection-based API calls. Use to statically triage a suspicious APK without executing it or to build mobile malware detection rules.

analyzing-api-gateway-access-logs

Parses API Gateway access logs (AWS API Gateway, Kong, Nginx) to detect

analyzing-apt-group-with-mitre-navigator

Query ATT&CK data with attackcti, mitreattack-python, and stix2, then build MITRE ATT&CK Navigator layers and multi-layer heatmap overlays mapping one or more APT groups

analyzing-azure-activity-logs-for-threats

Queries Azure Monitor activity logs and sign-in logs via azure-monitor-query

analyzing-bootkit-and-rootkit-samples

Analyzes bootkit and advanced rootkit malware infecting the Master

analyzing-browser-forensics-with-hindsight

Parse Chromium-based browser databases with Hindsight to extract and correlate browsing history, downloads, cookies, cached content, autofill data, saved passwords, and extensions from Chrome, Edge, Brave, Opera, and Vivaldi into a unified timeline (XLSX, JSON, or SQLite output). Use during incident response, insider-threat investigations, or criminal cases when you need to reconstruct a user

analyzing-campaign-attribution-evidence

Systematically evaluate cyber-campaign evidence to attribute an operation to a threat actor, using the Diamond Model and Analysis of Competing Hypotheses (ACH) to weigh infrastructure overlaps, TTP consistency, malware code similarity, and timing/language artifacts into confidence-weighted attribution assessments. Use when an incident investigation needs a defensible attribution confidence level.

analyzing-cloud-storage-access-patterns

Detect abnormal access in AWS S3, GCS, and Azure Blob Storage by analyzing CloudTrail Data Events, GCS audit logs, and Azure Storage Analytics for after-hours bulk downloads, new-IP access, and API-call spikes (e.g. GetObject) via statistical baselines and time-series anomaly detection. Use when investigating suspected cloud data exfiltration or building related detection rules.

analyzing-cobalt-strike-beacon-configuration

Extract and analyze Cobalt Strike beacon configuration from PE files

analyzing-cobaltstrike-malleable-c2-profiles

Parse and analyze Cobalt Strike Malleable C2 profiles with dissect.cobaltstrike (profiles and beacon-payload configs) and pyMalleableC2 (AST parsing) to extract HTTP/DNS transforms, URIs, headers, sleep/jitter, and injection behavior, then generate network detection signatures. Use when reverse-engineering a captured malleable profile or building detections against Cobalt Strike Beacon traffic.

analyzing-command-and-control-communication

Analyzes malware C2 communication over HTTP, HTTPS, DNS, and custom

analyzing-cyber-kill-chain

Analyzes intrusion activity against the Lockheed Martin Cyber Kill Chain

analyzing-disk-image-with-autopsy

Perform comprehensive forensic analysis of raw (dd), E01, or AFF disk images with Autopsy and The Sleuth Kit, recovering deleted files, examining metadata and embedded artifacts, keyword searching, and building investigation timelines with visual reports. Use for structured analysis of a forensic disk image or when stakeholders need visual reports from evidence.

analyzing-dns-logs-for-exfiltration

Analyzes DNS query logs to detect data exfiltration via DNS tunneling,

analyzing-docker-container-forensics

Investigate compromised Docker containers by analyzing images, layers,

相關技能