Certificate Transparency (CT) คืออะไรและทำงานอย่างไร
Certificate Transparency (CT) เป็นระบบที่ออกแบบมาเพื่อตรวจสอบและเฝ้าระวังการออก SSL/TLS Certificate โดย Certificate Authority (CA) ทุกครั้งที่ CA ออก certificate ใหม่จะต้องส่ง certificate นั้นไปบันทึกใน CT Log ที่เป็น public append-only log ทำให้ทุกู้คืนสามารถตรวจสอบได้ว่ามี certificate อะไรถูกออกสำหรับ domain ใดบ้าง
CT ถูกพัฒนาโดย Google เพื่อแก้ปัญหาที่ CA อาจออก certificate ปลอมหรือผิดพลาดโดยที่เจ้าของ domain ไม่รู้ ตัวอย่างเช่นในปี 2011 DigiNotar CA ถูกแฮ็กแล้วออก certificate ปลอมสำหรับ google.com ทำให้ผู้โจมตีสามารถดักฟัง traffic ของผู้ใช้ Google ได้ CT ช่วยป้องกันปัญหาเหล่านี้ด้วยการทำให้การออก certificate ทุกครั้งเป็นสาธารณะ
ระบบ CT ประกอบด้วย 3 ส่วนหลักคือ CT Log Server ที่เก็บ certificate ทั้งหมดใน Merkle Tree, Monitor ที่เฝ้าดู CT Log เพื่อตรวจจับ certificate ที่น่าสงสัย และ Auditor ที่ตรวจสอบว่า CT Log ทำงานถูกต้องไม่มีการแก้ไขข้อมูล
ตั้งแต่เดือนเมษายน 2018 Google Chrome บังคับให้ certificate ทุกใบต้องมี Signed Certificate Timestamp (SCT) จาก CT Log อย่างน้อย 2 แห่ง ถ้าไม่มีจะแสดง warning ให้ผู้ใช้เห็น ทำให้ CT กลายเป็นส่วนสำคัญของ web security infrastructure
โครงสร้างของ CT Log และ Merkle Tree
CT Log ใช้ Merkle Hash Tree สำหรับเก็บข้อมูล certificate ทำให้สามารถตรวจสอบความถูกต้องของข้อมูลได้อย่างมีประสิทธิภาพ
# โครงสร้างของ Merkle Tree ใน CT Log
#
# Root Hash
# / \
# Hash(A+B) Hash(C+D)
# / \ / \
# Hash(A) Hash(B) Hash(C) Hash(D)
# | | | |
# Cert1 Cert2 Cert3 Cert4
#
# แต่ละ leaf node คือ hash ของ certificate
# internal nodes คือ hash ของ child nodes รวมกัน
# Root Hash เปลี่ยนทุกครั้งที่เพิ่ม certificate ใหม่
# ตรวจสอบ CT Log ที่ Google ดูแล
curl -s "https://ct.googleapis.com/logs/us1/argon2025h1/ct/v1/get-sth" | python3 -m json.tool
# {
# "tree_size": 1234567890,
# "timestamp": 1709136000000,
# "sha256_root_hash": "abc123...",
# "tree_head_signature": "def456..."
# }
# ดูรายการ CT Log ที่ browsers เชื่อถือ
curl -s "https://www.gstatic.com/ct/log_list/v3/log_list.json" | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
for op in data.get('operators', []):
print(f\"Operator: {op['name']}\")
for log in op.get('logs', []):
print(f\" - {log['description']} ({log.get('state', {}).get('usable', {}).get('timestamp', 'N/A')})\")
"
SCT (Signed Certificate Timestamp) เป็นหลักฐานว่า certificate ถูกบันทึกใน CT Log แล้ว
# ตรวจสอบ SCT ของ certificate ด้วย OpenSSL
openssl s_client -connect google.com:443 -ct 2>/dev/null | \
openssl x509 -noout -text | grep -A 20 "CT Precertificate SCTs"
# Output:
# CT Precertificate SCTs:
# Signed Certificate Timestamp:
# Version : v1 (0x0)
# Log ID : 7s3QZN...
# Timestamp : Feb 28 10:00:00.000 2026 GMT
# Extensions: none
# Signature : ecdsa-with-SHA256 30:45:02:21:00...
# ตรวจสอบ SCT ด้วย Python
pip install cryptography pyOpenSSL
python3 -c "
import ssl, socket, json
from cryptography import x509
from cryptography.hazmat.backends import default_backend
hostname = 'google.com'
ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.socket(), server_hostname=hostname) as s:
s.connect((hostname, 443))
cert_bin = s.getpeercert(binary_form=True)
cert = x509.load_der_x509_certificate(cert_bin, default_backend())
print(f'Subject: {cert.subject}')
print(f'Issuer: {cert.issuer}')
print(f'Not After: {cert.not_valid_after_utc}')
for ext in cert.extensions:
if 'SCT' in ext.oid.dotted_string or 'precert' in str(ext.oid):
print(f'SCT Extension: {ext.oid}')
"
ตรวจสอบ CT Log ด้วย Command Line Tools
ใช้เครื่องมือ command line สำหรับค้นหาและตรวจสอบ certificate ใน CT Log
# ติดตั้ง certspotter (CT monitoring tool)
go install software.sslmate.com/src/certspotter/cmd/certspotter@latest
# หรือดาวน์โหลด binary
wget https://github.com/SSLMate/certspotter/releases/download/v0.18.0/certspotter_0.18.0_linux_amd64.tar.gz
tar xzf certspotter_0.18.0_linux_amd64.tar.gz
sudo mv certspotter /usr/local/bin/
# ค้นหา certificate ที่ออกให้ domain
certspotter -domain example.com -follow=false
# ใช้ crt.sh API (ฐานข้อมูล CT ที่ใหญ่ที่สุด)
# ค้นหา certificate ทั้งหมดของ domain
curl -s "https://crt.sh/?q=%.example.com&output=json" | \
python3 -c "
import sys, json
certs = json.load(sys.stdin)
print(f'Found {len(certs)} certificates')
for c in certs[:10]:
print(f\" ID: {c['id']} | Name: {c['common_name']} | Issuer: {c['issuer_name'][:50]} | Not After: {c['not_after']}\")
"
# ค้นหา subdomain จาก CT Log
curl -s "https://crt.sh/?q=%.example.com&output=json" | \
python3 -c "
import sys, json
certs = json.load(sys.stdin)
domains = set()
for c in certs:
name = c.get('common_name', '')
if name:
domains.add(name)
for san in c.get('name_value', '').split('\n'):
if san.strip():
domains.add(san.strip())
for d in sorted(domains):
print(d)
"
# ใช้ subfinder ค้นหา subdomain ผ่าน CT Log
# go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
# subfinder -d example.com -sources crtsh
ตั้งค่า CT Monitoring สำหรับ Domain ของคุณ
สร้างระบบ monitoring ที่แจ้งเตือนเมื่อมี certificate ใหม่ถูกออกให้ domain ของคุณ
#!/usr/bin/env python3
# ct_monitor.py — CT Log Monitor สำหรับตรวจจับ certificate ใหม่
import requests
import json
import time
import sqlite3
import os
from datetime import datetime
DOMAINS_TO_WATCH = [
"example.com",
"%.example.com", # รวม subdomain
]
SLACK_WEBHOOK = os.getenv("SLACK_WEBHOOK", "")
DB_PATH = "ct_monitor.db"
CHECK_INTERVAL = 300 # 5 นาที
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("""
CREATE TABLE IF NOT EXISTS seen_certs (
cert_id INTEGER PRIMARY KEY,
common_name TEXT,
issuer TEXT,
not_before TEXT,
not_after TEXT,
first_seen TEXT
)
""")
conn.commit()
return conn
def check_crt_sh(domain):
url = f"https://crt.sh/?q={domain}&output=json"
try:
r = requests.get(url, timeout=30)
if r.status_code == 200:
return r.json()
except Exception as e:
print(f"Error querying crt.sh: {e}")
return []
def notify(message):
print(f"[ALERT] {message}")
if SLACK_WEBHOOK:
requests.post(SLACK_WEBHOOK, json={"text": message})
def process_certs(conn, certs):
new_certs = []
for cert in certs:
cert_id = cert.get("id")
cursor = conn.execute(
"SELECT cert_id FROM seen_certs WHERE cert_id = ?", (cert_id,)
)
if cursor.fetchone() is None:
conn.execute(
"INSERT INTO seen_certs VALUES (?, ?, ?, ?, ?, ?)",
(
cert_id,
cert.get("common_name", ""),
cert.get("issuer_name", ""),
cert.get("not_before", ""),
cert.get("not_after", ""),
datetime.now().isoformat(),
),
)
new_certs.append(cert)
conn.commit()
return new_certs
def main():
conn = init_db()
print(f"CT Monitor started for: {', '.join(DOMAINS_TO_WATCH)}")
while True:
for domain in DOMAINS_TO_WATCH:
certs = check_crt_sh(domain)
new_certs = process_certs(conn, certs)
if new_certs:
for cert in new_certs:
msg = (
f"NEW CERT: {cert.get('common_name')} | "
f"Issuer: {cert.get('issuer_name', '')[:60]} | "
f"Valid: {cert.get('not_before')} - {cert.get('not_after')}"
)
notify(msg)
# ตรวจสอบว่า issuer เป็น CA ที่เชื่อถือ
issuer = cert.get("issuer_name", "").lower()
trusted_cas = ["let's encrypt", "digicert", "sectigo", "google trust"]
if not any(ca in issuer for ca in trusted_cas):
notify(f"WARNING: Unknown CA issued cert for {cert.get('common_name')}: {issuer}")
time.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
main()
ใช้ CT Log API สำหรับ Security Research
CT Log API สามารถใช้สำหรับ subdomain enumeration, phishing detection และ certificate analysis
#!/usr/bin/env python3
# ct_security_tools.py — เครื่องมือ Security ที่ใช้ CT Log
import requests
import json
from collections import Counter
from datetime import datetime
def enumerate_subdomains(domain):
"""ค้นหา subdomain จาก CT Log"""
url = f"https://crt.sh/?q=%.{domain}&output=json"
r = requests.get(url, timeout=60)
certs = r.json()
subdomains = set()
for cert in certs:
cn = cert.get("common_name", "")
if cn and domain in cn:
subdomains.add(cn.lower().strip())
for san in cert.get("name_value", "").split("\n"):
san = san.strip().lower()
if san and domain in san:
subdomains.add(san)
return sorted(subdomains)
def detect_phishing_domains(target_domain):
"""ตรวจจับ domain ที่อาจเป็น phishing"""
variations = [
target_domain.replace(".", "-"),
target_domain.replace("o", "0"),
target_domain.replace("l", "1"),
f"login-{target_domain}",
f"secure-{target_domain}",
f"{target_domain}-verify",
]
phishing_certs = []
for variation in variations:
try:
url = f"https://crt.sh/?q=%{variation}%&output=json"
r = requests.get(url, timeout=30)
if r.status_code == 200:
certs = r.json()
for cert in certs:
if cert.get("common_name") != target_domain:
phishing_certs.append({
"domain": cert.get("common_name"),
"issuer": cert.get("issuer_name", "")[:60],
"not_after": cert.get("not_after"),
})
except Exception:
continue
return phishing_certs
def analyze_ca_distribution(domain):
"""วิเคราะห์ CA ที่ออก certificate ให้ domain"""
url = f"https://crt.sh/?q=%.{domain}&output=json"
r = requests.get(url, timeout=60)
certs = r.json()
ca_counter = Counter()
for cert in certs:
issuer = cert.get("issuer_name", "Unknown")
# Extract CA organization
for part in issuer.split(","):
if "O=" in part:
ca_org = part.split("O=")[1].strip()
ca_counter[ca_org] += 1
break
print(f"\n=== CA Distribution for {domain} ===")
for ca, count in ca_counter.most_common(10):
print(f" {ca}: {count} certificates")
if __name__ == "__main__":
import sys
domain = sys.argv[1] if len(sys.argv) > 1 else "example.com"
print(f"=== Subdomain Enumeration: {domain} ===")
subs = enumerate_subdomains(domain)
for s in subs[:20]:
print(f" {s}")
print(f" Total: {len(subs)} subdomains")
analyze_ca_distribution(domain)
ตั้งค่า Expect-CT Header และ CT Policy
ตั้งค่า Expect-CT HTTP header เพื่อบังคับให้ browser ตรวจสอบ CT สำหรับ domain ของคุณ
# Nginx Configuration — Expect-CT Header
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Expect-CT Header
# max-age: ระยะเวลาที่ browser จำ policy (86400 = 1 วัน)
# enforce: บังคับให้ปฏิเสธ certificate ที่ไม่มี SCT
# report-uri: URL สำหรับส่ง violation report
add_header Expect-CT 'max-age=86400, enforce, report-uri="https://example.com/ct-report"' always;
# Security Headers อื่นๆ
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
# Apache Configuration
#
# Header always set Expect-CT "max-age=86400, enforce, report-uri=\"https://example.com/ct-report\""
#
# Caddy Configuration
# example.com {
# header Expect-CT "max-age=86400, enforce"
# }
# ตรวจสอบ header
curl -sI https://example.com | grep -i expect-ct
# ตรวจสอบ certificate มี SCT หรือไม่
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -text 2>/dev/null | \
grep -c "Signed Certificate Timestamp"
# Output: 2 (ต้องมีอย่างน้อย 2 SCTs)
สร้าง CT Report Endpoint สำหรับรับ violation reports
#!/usr/bin/env python3
# ct_report_server.py — รับ CT Violation Reports
from flask import Flask, request, jsonify
import json
import logging
from datetime import datetime
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
@app.route("/ct-report", methods=["POST"])
def ct_report():
report = request.json
logging.warning(f"CT VIOLATION: {json.dumps(report, indent=2)}")
# บันทึก report
with open("ct_violations.log", "a") as f:
f.write(f"{datetime.now().isoformat()} | {json.dumps(report)}\n")
# ส่ง alert (Slack, email ฯลฯ)
return jsonify({"status": "received"}), 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
FAQ คำถามที่พบบ่อย
Q: CT Log ปลอดภัยจากการถูกแก้ไขข้อมูลอย่างไร?
A: CT Log ใช้ Merkle Hash Tree ที่เป็น append-only structure ทุกการเปลี่ยนแปลงจะส่งผลต่อ root hash ทำให้ตรวจจับได้ทันที นอกจากนี้ CT Log Server ต้อง sign ทุก Signed Tree Head (STH) ด้วย private key ทำให้ไม่สามารถปลอมแปลงได้ Monitor และ Auditor จะตรวจสอบ consistency ของ log อย่างสม่ำเสมอ
Q: ถ้าพบ certificate ปลอมใน CT Log ต้องทำอย่างไร?
A: ติดต่อ CA ที่ออก certificate นั้นเพื่อขอ revoke ทันที แจ้ง browser vendors (Google, Mozilla, Apple, Microsoft) เพื่อ block certificate และรายงานไปยัง CA/Browser Forum ถ้า CA ออก certificate ปลอมซ้ำอาจถูก distrust จาก browser ทั้งหมด
Q: CT ช่วยป้องกัน phishing ได้อย่างไร?
A: CT ไม่ได้ป้องกัน phishing โดยตรง แต่ช่วยให้ตรวจจับ phishing domain ที่ใช้ certificate จริงได้เร็วขึ้น โดย monitor CT Log แล้วค้นหา domain ที่คล้ายกับ domain เป้าหมาย เช่น g00gle.com หรือ examp1e.com องค์กรหลายแห่งใช้ CT monitoring เป็นส่วนหนึ่งของ brand protection strategy
Q: Certificate ที่ออกก่อนมี CT บังคับยังใช้งานได้ไหม?
A: Certificate ที่ออกก่อนเมษายน 2018 (ช่วงที่ Chrome เริ่มบังคับ CT) ยังใช้งานได้จนกว่าจะหมดอายุ แต่ certificate ที่ออกหลังจากนั้นต้องมี SCT จาก CT Log อย่างน้อย 2 แห่ง ถ้าไม่มี Chrome จะแสดง warning และ certificate ที่มีอายุยาวกว่า 180 วันต้องมี SCT จาก CT Log อย่างน้อย 3 แห่ง
