Communitygithub.com

피싱 탐지를 위한 인증서 투명성 분석

crt.sh와 Certstream을 사용하여 인증서 투명성 로그를 모니터링하고 피싱 도메인을 조기에 탐지합니다.

피싱 탐지를 위한 인증서 투명성 분석란 무엇인가요?

이 스킬은 인증서 투명성(CT) 로그를 분석하여 정식 브랜드를 사칭하는 피싱 도메인, 무단 인증서 발급, 인증서 기반 공격 인프라를 조기에 탐지하는 방법을 제공합니다. 주요 기능으로는 crt.sh를 통한 과거 CT 로그 질의, Certstream을 활용한 실시간 모니터링, 자동 경고 시스템 구축, 그리고 위협 인텔리전스 워크플로우 통합이 포함됩니다. Python 기반 코드 예제를 통해 CTLogMonitor 클래스로 의심스러운 인증서를 식별하고, CertstreamMonitor 클래스로 유사 도메인(타이포스쿼팅)을 실시간 탐지하며, 최종적으로 CT 인텔리전스 보고서를 생성합니다. 이 스킬은 Python 3.9+ 환경에서 작동하며 requests, certstream, tldextract, Levenshtein 라이브러리가 필요합니다. SOC 분석가, 보안 엔지니어, 위협 헌터가 피싱 캠페인 발사 전 인증서 로그에 나타난 악성 도메인을 사전 차단하는 데 유용합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/mukul975/Anthropic-Cybersecurity-Skills/tree/main/skills/analyzing-certificate-transparency-for-phishing

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

피싱 탐지를 위한 인증서 투명성 분석은(는) 무엇을 하나요?

개요

인증서 투명성(CT)은 발급된 모든 SSL/TLS 인증서의 공개적이고 추가 전용 로그를 생성하는 인터넷 보안 표준입니다. CT 로그를 모니터링하면 정식 브랜드를 사칭하는 피싱 도메인, 소유 도메인에 대한 무단 인증서 발급, 인증서 기반 공격 인프라를 조기에 탐지할 수 있습니다. 이 스킬은 crt.sh를 통한 CT 로그 질의, Certstream을 사용한 실시간 모니터링, 의심스러운 인증서에 대한 자동 경고 구축, 그리고 위협 인텔리전스 워크플로우로 결과 통합을 다룹니다.

사용 시기

  • 보안 사고 조사 시 인증서 투명성 분석이 필요한 경우
  • 이 도메인에 대한 탐지 규칙 또는 위협 헌팅 쿼리를 구축할 때
  • SOC 분석가가 이 분석 유형에 대한 구조화된 절차가 필요할 때
  • 관련 공격 기술에 대한 보안 모니터링 범위를 검증할 때

전제 조건

  • Python 3.9+ 및 requests, certstream, tldextract, Levenshtein 라이브러리
  • 과거 CT 로그 질의를 위한 crt.sh 접근 권한 (https://crt.sh/)
  • 실시간 모니터링을 위한 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 응답을 지원합니다. 주요 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,

관련 스킬