🤔

[Threat Thinker] LLMを用いた脅威モデリングを試してみる

に公開

はじめに

こんにちは、セキュリティエンジニアのJJ(yuasa)です。

本記事では、LLMを用いた自動脅威モデリングツールであるThreat Thinkerを用いて様々なシステムの脅威モデリングを試してみます。AWSベースシステムからスマートホームまで、LLMがどのように脅威を洗い出すのか、実際の出力を交えながらLLM脅威モデリングの雰囲気を体験していただければ幸いです。

https://github.com/melonattacker/threat-thinker

Threat Thinkerとは?

Threat Thinker は、LLMを用いてシステム構成図から自動で脅威モデリングを行うツールです。Mermaiddraw.io、構成図のスクリーンショット、OWASP Threat Dragonなど、さまざまな形式の図を入力として解析し、コンポーネント間の関係から脅威を推論します。

従来の脅威モデリングでは、構成図を用意したあとに、開発者やセキュリティ担当者が手作業で脅威を一つひとつ検討する必要がありました。その中には、どのシステムでも発生し得る「基本的な脅威」と、仕様や実装の詳細を理解しないと気づけない「システム固有の脅威」があるように思います。

Threat Thinkerを使うことで、まずは「基本的な脅威」の洗い出しを自動化でき、人間は「システム固有の脅威」の深掘りや対策検討といった部分に注力できるようになります。CLIとWeb UIの両方に対応しており、セキュリティ専門家でなくても気軽に利用できる点も特徴です。

diagram-to-threat
システム構成図から基本的な脅威を自動で洗い出し

Threat Thinkerを使ってみる

Threat Thinkerを用いて、AWSベースシステム、企業ネットワーク、スマートホームの脅威を洗い出してみます。ここでは、それぞれの構成図をThreat Thinkerに入力し、どのような脅威が抽出されるのかを確認していきます。

AWSベースシステム

システム構成図

対象となるAWSベースシステムの構成図はこちらです。Mermaidで作成しています。Webアプリケーションでよく利用される CloudFront → ALB → ECS → RDS/S3 という比較的シンプルな構成を用いて、Threat Thinker がどのように脅威を抽出するのかを確認します。

Threat Thinkerの実行

ここではCLIでの実行例を示します。--diagramでMermaid形式の構成図へのファイルパスを指定します。--infer-hintsを付けることで、LLMが図から読み取れない補助的な情報も推論した上で脅威を推論してくれます。LLMのモデルはOpenAIのgpt-4.1を使用し、最大5件の脅威を出力するように指定しています。

threat-thinker think \
    --diagram path/to/diagram/system.mmd \
    --infer-hints \
    --topn 5 \
    --llm-api openai \
    --llm-model gpt-4.1 \
    --out-dir path/to/report/dir

洗い出された脅威

Markdownレポートで示される上位5件の脅威は以下の通りです。いずれもAWSを用いてWebアプリケーションを構成する際に典型的に発生し得るリスクで、認証・認可の不備、データ暗号化の不備、ロギング・監視の不足、S3の設定ミスといった基本的な観点が指摘されています。

Markdownレポート

Threat Analysis Report

Threat Summary

ID Threat Severity Score
T001 Potential Lack of Authentication/Authorization on ALB to ECS Path High 8.0
T002 Unencrypted Traffic Between ALB and ECS Allows Tampering and Disclosure High 8.0
T003 Exposure of PII in RDS Without Explicit Encryption at Rest High 7.0
T004 Insufficient Logging and Monitoring for Sensitive Operations Medium 6.0
T005 Potential S3 Bucket Misconfiguration Exposing Internal Data Medium 6.0

Threat Details

T001: Potential Lack of Authentication/Authorization on ALB to ECS Path

Severity: High

Score: 8.0

STRIDE: Elevation of Privilege, Spoofing

Affected Components: ALB, ECS Service

Why: No explicit authentication or authorization is described between ALB and ECS, risking unauthorized access to internal APIs.

References: ASVS V2.1, ASVS V4.2, CWE-285

Recommended Actions:

Require strong authentication (e.g., JWT, OAuth 2.0) and enforce authorization checks on ECS endpoints.


T002: Unencrypted Traffic Between ALB and ECS Allows Tampering and Disclosure

Severity: High

Score: 8.0

STRIDE: Tampering, Information Disclosure

Affected Components: ALB, ECS Service

Why: HTTP is used between ALB and ECS, exposing internal traffic to interception or modification if the subnet is compromised.

References: ASVS V9.1.1, ASVS V9.2.1, CWE-319

Recommended Actions:

Enforce HTTPS/TLS 1.2+ for all traffic between ALB and ECS to ensure confidentiality and integrity.


T003: Exposure of PII in RDS Without Explicit Encryption at Rest

Severity: High

Score: 7.0

STRIDE: Information Disclosure

Affected Components: ECS Service, Customer RDS
PII

Why: RDS contains PII but there is no mention of encryption at rest, risking data exposure if storage is compromised.

References: ASVS V9.4.1, ASVS V9.4.2, CWE-311

Recommended Actions:

Enable RDS encryption at rest and enforce encrypted backups for all PII-containing databases.


T004: Insufficient Logging and Monitoring for Sensitive Operations

Severity: Medium

Score: 6.0

STRIDE: Repudiation

Affected Components: ECS Service, Customer RDS
PII, S3 Bucket
Logs/Uploads

Why: No evidence of audit logging for access to PII or uploads, making it hard to detect or investigate misuse.

References: ASVS V10.1, ASVS V10.2, CWE-778

Recommended Actions:

Implement detailed audit logging for all access to RDS and S3, and monitor logs for suspicious activity.


T005: Potential S3 Bucket Misconfiguration Exposing Internal Data

Severity: Medium

Score: 6.0

STRIDE: Information Disclosure

Affected Components: S3 Bucket
Logs/Uploads

Why: S3 bucket is used for internal data, but no mention of bucket policy or access controls, risking accidental public exposure.

References: ASVS V9.1.1, ASVS V9.4.3, CWE-200

Recommended Actions:

Restrict S3 bucket access to only necessary IAM roles and enable bucket policy to deny public access.


ID Threat Severity Score
T001 Potential Lack of Authentication/Authorization on ALB to ECS Path High 8.0
T002 Unencrypted Traffic Between ALB and ECS Allows Tampering and Disclosure High 8.0
T003 Exposure of PII in RDS Without Explicit Encryption at Rest High 7.0
T004 Insufficient Logging and Monitoring for Sensitive Operations Medium 6.0
T005 Potential S3 Bucket Misconfiguration Exposing Internal Data Medium 6.0

HTMLレポート上では、それぞれの脅威がシステム構成図のどこにあるのかを視覚的に把握することができます。

HTMLレポート
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Threat Analysis Report</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 32px; color: #0f172a; }
    h1 { font-size: 28px; margin-bottom: 8px; }
    h2 { margin-top: 32px; border-bottom: 2px solid #e2e8f0; padding-bottom: 4px; }
    h3 { margin-top: 24px; color: #0f172a; }
    table { border-collapse: collapse; width: 100%; margin-top: 12px; }
    th, td { border: 1px solid #e2e8f0; padding: 8px 10px; text-align: left; }
    th { background: #f8fafc; }
    .severity { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 600; }
    .sev-High { background: #fee2e2; color: #b91c1c; }
    .sev-Medium { background: #fef9c3; color: #b45309; }
    .sev-Low { background: #dcfce7; color: #166534; }
    .meta { color: #475569; font-size: 14px; }
    .section { margin-top: 24px; }
    .mapping-list { list-style: disc; margin-left: 20px; }
    .chip { background: #e2e8f0; padding: 2px 6px; border-radius: 8px; margin-right: 4px; display: inline-block; cursor: pointer; }
    #graph-container { margin-top: 24px; }
    #graph { width: 100%; height: 520px; border: 1px solid #e2e8f0; border-radius: 8px; }
    .dom-highlight { box-shadow: 0 0 0 2px #f97316; }
    .cy-highlight { }
    .cy-dim { }
    .zone { background-color: #f8fafc; border: 1px dashed #cbd5e1; padding: 8px; }
  </style>
</head>
<body>
  <h1>Threat Analysis Report</h1>
  <h2>Threat Summary</h2>
  <table>
    <tr><th>ID</th><th>Threat</th><th>Severity</th><th>Score</th></tr>
    <tr class="threat-row" data-threat-id="T001"><td>T001</td><td>Potential Lack of Authentication/Authorization on ALB to ECS Path</td><td><span class="severity sev-High">High</span></td><td>8.0</td></tr>
    <tr class="threat-row" data-threat-id="T002"><td>T002</td><td>Unencrypted Traffic Between ALB and ECS Allows Tampering and Disclosure</td><td><span class="severity sev-High">High</span></td><td>8.0</td></tr>
    <tr class="threat-row" data-threat-id="T003"><td>T003</td><td>Exposure of PII in RDS Without Explicit Encryption at Rest</td><td><span class="severity sev-High">High</span></td><td>7.0</td></tr>
    <tr class="threat-row" data-threat-id="T004"><td>T004</td><td>Insufficient Logging and Monitoring for Sensitive Operations</td><td><span class="severity sev-Medium">Medium</span></td><td>6.0</td></tr>
    <tr class="threat-row" data-threat-id="T005"><td>T005</td><td>Potential S3 Bucket Misconfiguration Exposing Internal Data</td><td><span class="severity sev-Medium">Medium</span></td><td>6.0</td></tr>
  </table>
  <div id="graph-container">
    <h2>Architecture Graph</h2>
    <div id="graph"></div>
  </div>
  <h2>Architecture Mapping</h2>
  <h3>Nodes to Threats</h3>
  <table>
    <tr><th>Node</th><th>Zone</th><th>Type</th><th>Threats</th></tr>
    <tr><td>User [user]</td><td>Internet</td><td>actor</td><td>&mdash;</td></tr>
    <tr><td>CloudFront [cf]</td><td>Edge</td><td>service</td><td>&mdash;</td></tr>
    <tr><td>ALB [alb]</td><td>VPC &gt; Public subnet</td><td>elb</td><td><span class="chip" data-threat-id="T001">T001</span> <span class="chip" data-threat-id="T002">T002</span></td></tr>
    <tr><td>ECS Service [ecs]</td><td>VPC &gt; Private subnet</td><td>service</td><td><span class="chip" data-threat-id="T001">T001</span> <span class="chip" data-threat-id="T002">T002</span> <span class="chip" data-threat-id="T004">T004</span></td></tr>
    <tr><td>Customer RDS&lt;br&gt;PII [rds]</td><td>VPC &gt; Private subnet</td><td>database</td><td><span class="chip" data-threat-id="T003">T003</span> <span class="chip" data-threat-id="T004">T004</span></td></tr>
    <tr><td>S3 Bucket&lt;br&gt;Logs/Uploads [s3]</td><td>VPC &gt; Private subnet</td><td>s3</td><td><span class="chip" data-threat-id="T004">T004</span> <span class="chip" data-threat-id="T005">T005</span></td></tr>
  </table>
  <h3>Edges to Threats</h3>
  <table>
    <tr><th>Edge</th><th>Protocol</th><th>Threats</th></tr>
    <tr><td>User [user] -> CloudFront [cf] : sends HTTPS request</td><td>HTTPS</td><td>&mdash;</td></tr>
    <tr><td>CloudFront [cf] -> ALB [alb] : forwards HTTPS request</td><td>HTTPS</td><td>&mdash;</td></tr>
    <tr><td>ALB [alb] -> ECS Service [ecs] : routes HTTP request</td><td>HTTP</td><td><span class="chip" data-threat-id="T001">T001</span> <span class="chip" data-threat-id="T002">T002</span></td></tr>
    <tr><td>ECS Service [ecs] -> Customer RDS&lt;br&gt;PII [rds] : reads/writes data (SQL/TLS)</td><td>TCP</td><td><span class="chip" data-threat-id="T003">T003</span> <span class="chip" data-threat-id="T004">T004</span></td></tr>
    <tr><td>ECS Service [ecs] -> S3 Bucket&lt;br&gt;Logs/Uploads [s3] : stores/reads objects (S3 API)</td><td>HTTPS</td><td><span class="chip" data-threat-id="T004">T004</span> <span class="chip" data-threat-id="T005">T005</span></td></tr>
  </table>
  <h2>Threat Details</h2>
  <div class="section" id="T001" data-threat-id="T001">
    <h3>T001: Potential Lack of Authentication/Authorization on ALB to ECS Path</h3>
    <div class="meta">
      Severity: <span class="severity sev-High">High</span> | Score: 8.0 | STRIDE: Elevation of Privilege, Spoofing
    </div>
    <p><strong>Affected Components:</strong> ALB, ECS Service</p>
    <p><strong>Why:</strong> No explicit authentication or authorization is described between ALB and ECS, risking unauthorized access to internal APIs.</p>
    <p><strong>References:</strong> ASVS V2.1, ASVS V4.2, CWE-285</p>
    <p><strong>Recommended Actions:</strong><br>Require strong authentication (e.g., JWT, OAuth 2.0) and enforce authorization checks on ECS endpoints.</p>
    <div class="section">
      <h4>Evidence Mapping</h4>
      <p><em>Nodes:</em></p>
      <ul class="mapping-list">
        <li>ALB [alb] (zone=VPC &gt; Public subnet, type=elb)</li>
        <li>ECS Service [ecs] (zone=VPC &gt; Private subnet, type=service)</li>
      </ul>
      <p><em>Edges:</em></p>
      <ul class="mapping-list">
        <li>ALB [alb] -&gt; ECS Service [ecs] : routes HTTP request (HTTP)</li>
      </ul>
    </div>
  </div>
  <div class="section" id="T002" data-threat-id="T002">
    <h3>T002: Unencrypted Traffic Between ALB and ECS Allows Tampering and Disclosure</h3>
    <div class="meta">
      Severity: <span class="severity sev-High">High</span> | Score: 8.0 | STRIDE: Tampering, Information Disclosure
    </div>
    <p><strong>Affected Components:</strong> ALB, ECS Service</p>
    <p><strong>Why:</strong> HTTP is used between ALB and ECS, exposing internal traffic to interception or modification if the subnet is compromised.</p>
    <p><strong>References:</strong> ASVS V9.1.1, ASVS V9.2.1, CWE-319</p>
    <p><strong>Recommended Actions:</strong><br>Enforce HTTPS/TLS 1.2+ for all traffic between ALB and ECS to ensure confidentiality and integrity.</p>
    <div class="section">
      <h4>Evidence Mapping</h4>
      <p><em>Nodes:</em></p>
      <ul class="mapping-list">
        <li>ALB [alb] (zone=VPC &gt; Public subnet, type=elb)</li>
        <li>ECS Service [ecs] (zone=VPC &gt; Private subnet, type=service)</li>
      </ul>
      <p><em>Edges:</em></p>
      <ul class="mapping-list">
        <li>ALB [alb] -&gt; ECS Service [ecs] : routes HTTP request (HTTP)</li>
      </ul>
    </div>
  </div>
  <div class="section" id="T003" data-threat-id="T003">
    <h3>T003: Exposure of PII in RDS Without Explicit Encryption at Rest</h3>
    <div class="meta">
      Severity: <span class="severity sev-High">High</span> | Score: 7.0 | STRIDE: Information Disclosure
    </div>
    <p><strong>Affected Components:</strong> ECS Service, Customer RDS&lt;br&gt;PII</p>
    <p><strong>Why:</strong> RDS contains PII but there is no mention of encryption at rest, risking data exposure if storage is compromised.</p>
    <p><strong>References:</strong> ASVS V9.4.1, ASVS V9.4.2, CWE-311</p>
    <p><strong>Recommended Actions:</strong><br>Enable RDS encryption at rest and enforce encrypted backups for all PII-containing databases.</p>
    <div class="section">
      <h4>Evidence Mapping</h4>
      <p><em>Nodes:</em></p>
      <ul class="mapping-list">
        <li>Customer RDS&lt;br&gt;PII [rds] (zone=VPC &gt; Private subnet, type=database)</li>
      </ul>
      <p><em>Edges:</em></p>
      <ul class="mapping-list">
        <li>ECS Service [ecs] -&gt; Customer RDS&lt;br&gt;PII [rds] : reads/writes data (SQL/TLS) (TCP)</li>
      </ul>
    </div>
  </div>
  <div class="section" id="T004" data-threat-id="T004">
    <h3>T004: Insufficient Logging and Monitoring for Sensitive Operations</h3>
    <div class="meta">
      Severity: <span class="severity sev-Medium">Medium</span> | Score: 6.0 | STRIDE: Repudiation
    </div>
    <p><strong>Affected Components:</strong> ECS Service, Customer RDS&lt;br&gt;PII, S3 Bucket&lt;br&gt;Logs/Uploads</p>
    <p><strong>Why:</strong> No evidence of audit logging for access to PII or uploads, making it hard to detect or investigate misuse.</p>
    <p><strong>References:</strong> ASVS V10.1, ASVS V10.2, CWE-778</p>
    <p><strong>Recommended Actions:</strong><br>Implement detailed audit logging for all access to RDS and S3, and monitor logs for suspicious activity.</p>
    <div class="section">
      <h4>Evidence Mapping</h4>
      <p><em>Nodes:</em></p>
      <ul class="mapping-list">
        <li>ECS Service [ecs] (zone=VPC &gt; Private subnet, type=service)</li>
        <li>Customer RDS&lt;br&gt;PII [rds] (zone=VPC &gt; Private subnet, type=database)</li>
        <li>S3 Bucket&lt;br&gt;Logs/Uploads [s3] (zone=VPC &gt; Private subnet, type=s3)</li>
      </ul>
      <p><em>Edges:</em></p>
      <ul class="mapping-list">
        <li>ECS Service [ecs] -&gt; Customer RDS&lt;br&gt;PII [rds] : reads/writes data (SQL/TLS) (TCP)</li>
        <li>ECS Service [ecs] -&gt; S3 Bucket&lt;br&gt;Logs/Uploads [s3] : stores/reads objects (S3 API) (HTTPS)</li>
      </ul>
    </div>
  </div>
  <div class="section" id="T005" data-threat-id="T005">
    <h3>T005: Potential S3 Bucket Misconfiguration Exposing Internal Data</h3>
    <div class="meta">
      Severity: <span class="severity sev-Medium">Medium</span> | Score: 6.0 | STRIDE: Information Disclosure
    </div>
    <p><strong>Affected Components:</strong> S3 Bucket&lt;br&gt;Logs/Uploads</p>
    <p><strong>Why:</strong> S3 bucket is used for internal data, but no mention of bucket policy or access controls, risking accidental public exposure.</p>
    <p><strong>References:</strong> ASVS V9.1.1, ASVS V9.4.3, CWE-200</p>
    <p><strong>Recommended Actions:</strong><br>Restrict S3 bucket access to only necessary IAM roles and enable bucket policy to deny public access.</p>
    <div class="section">
      <h4>Evidence Mapping</h4>
      <p><em>Nodes:</em></p>
      <ul class="mapping-list">
        <li>S3 Bucket&lt;br&gt;Logs/Uploads [s3] (zone=VPC &gt; Private subnet, type=s3)</li>
      </ul>
      <p><em>Edges:</em></p>
      <ul class="mapping-list">
        <li>ECS Service [ecs] -&gt; S3 Bucket&lt;br&gt;Logs/Uploads [s3] : stores/reads objects (S3 API) (HTTPS)</li>
      </ul>
    </div>
  </div>
  <script>
    window.THREAT_REPORT = {"graph": {"nodes": [{"id": "user", "label": "User", "zone": "Internet", "zones": ["zone_0"], "zone_path": ["Internet"], "type": "actor", "data": [], "auth": null, "notes": null}, {"id": "cf", "label": "CloudFront", "zone": "Edge", "zones": ["zone_1"], "zone_path": ["Edge"], "type": "service", "data": [], "auth": null, "notes": null}, {"id": "alb", "label": "ALB", "zone": "Public subnet", "zones": ["zone_2", "zone_3"], "zone_path": ["VPC", "Public subnet"], "type": "elb", "data": [], "auth": null, "notes": null}, {"id": "ecs", "label": "ECS Service", "zone": "Private subnet", "zones": ["zone_2", "zone_4"], "zone_path": ["VPC", "Private subnet"], "type": "service", "data": ["Internal"], "auth": true, "notes": null}, {"id": "rds", "label": "Customer RDS<br>PII", "zone": "Private subnet", "zones": ["zone_2", "zone_4"], "zone_path": ["VPC", "Private subnet"], "type": "database", "data": ["PII"], "auth": true, "notes": null}, {"id": "s3", "label": "S3 Bucket<br>Logs/Uploads", "zone": "Private subnet", "zones": ["zone_2", "zone_4"], "zone_path": ["VPC", "Private subnet"], "type": "s3", "data": ["Internal"], "auth": true, "notes": null}], "edges": [{"id": "user->cf", "src": "user", "dst": "cf", "label": "sends HTTPS request", "protocol": "HTTPS", "data": []}, {"id": "cf->alb", "src": "cf", "dst": "alb", "label": "forwards HTTPS request", "protocol": "HTTPS", "data": []}, {"id": "alb->ecs", "src": "alb", "dst": "ecs", "label": "routes HTTP request", "protocol": "HTTP", "data": []}, {"id": "ecs->rds", "src": "ecs", "dst": "rds", "label": "reads/writes data (SQL/TLS)", "protocol": "TCP", "data": ["PII"]}, {"id": "ecs->s3", "src": "ecs", "dst": "s3", "label": "stores/reads objects (S3 API)", "protocol": "HTTPS", "data": ["Internal"]}], "zones": [{"id": "zone_0", "name": "Internet", "parent_id": null}, {"id": "zone_1", "name": "Edge", "parent_id": null}, {"id": "zone_2", "name": "VPC", "parent_id": null}, {"id": "zone_3", "name": "Public subnet", "parent_id": "zone_2"}, {"id": "zone_4", "name": "Private subnet", "parent_id": "zone_2"}]}, "threats": [{"id": "T001", "title": "Potential Lack of Authentication/Authorization on ALB to ECS Path", "severity": "High", "score": 8.0, "stride": ["Elevation of Privilege", "Spoofing"], "affected": ["ALB", "ECS Service"], "why": "No explicit authentication or authorization is described between ALB and ECS, risking unauthorized access to internal APIs.", "references": ["ASVS V2.1", "ASVS V4.2", "CWE-285"], "recommended_action": "Require strong authentication (e.g., JWT, OAuth 2.0) and enforce authorization checks on ECS endpoints.", "evidence": {"nodes": ["alb", "ecs"], "edges": ["alb->ecs"]}}, {"id": "T002", "title": "Unencrypted Traffic Between ALB and ECS Allows Tampering and Disclosure", "severity": "High", "score": 8.0, "stride": ["Tampering", "Information Disclosure"], "affected": ["ALB", "ECS Service"], "why": "HTTP is used between ALB and ECS, exposing internal traffic to interception or modification if the subnet is compromised.", "references": ["ASVS V9.1.1", "ASVS V9.2.1", "CWE-319"], "recommended_action": "Enforce HTTPS/TLS 1.2+ for all traffic between ALB and ECS to ensure confidentiality and integrity.", "evidence": {"nodes": ["alb", "ecs"], "edges": ["alb->ecs"]}}, {"id": "T003", "title": "Exposure of PII in RDS Without Explicit Encryption at Rest", "severity": "High", "score": 7.0, "stride": ["Information Disclosure"], "affected": ["ECS Service", "Customer RDS<br>PII"], "why": "RDS contains PII but there is no mention of encryption at rest, risking data exposure if storage is compromised.", "references": ["ASVS V9.4.1", "ASVS V9.4.2", "CWE-311"], "recommended_action": "Enable RDS encryption at rest and enforce encrypted backups for all PII-containing databases.", "evidence": {"nodes": ["rds"], "edges": ["ecs->rds"]}}, {"id": "T004", "title": "Insufficient Logging and Monitoring for Sensitive Operations", "severity": "Medium", "score": 6.0, "stride": ["Repudiation"], "affected": ["ECS Service", "Customer RDS<br>PII", "S3 Bucket<br>Logs/Uploads"], "why": "No evidence of audit logging for access to PII or uploads, making it hard to detect or investigate misuse.", "references": ["ASVS V10.1", "ASVS V10.2", "CWE-778"], "recommended_action": "Implement detailed audit logging for all access to RDS and S3, and monitor logs for suspicious activity.", "evidence": {"nodes": ["ecs", "rds", "s3"], "edges": ["ecs->rds", "ecs->s3"]}}, {"id": "T005", "title": "Potential S3 Bucket Misconfiguration Exposing Internal Data", "severity": "Medium", "score": 6.0, "stride": ["Information Disclosure"], "affected": ["S3 Bucket<br>Logs/Uploads"], "why": "S3 bucket is used for internal data, but no mention of bucket policy or access controls, risking accidental public exposure.", "references": ["ASVS V9.1.1", "ASVS V9.4.3", "CWE-200"], "recommended_action": "Restrict S3 bucket access to only necessary IAM roles and enable bucket policy to deny public access.", "evidence": {"nodes": ["s3"], "edges": ["ecs->s3"]}}]};
  </script>
  <script src="https://unpkg.com/cytoscape@3.33.1/dist/cytoscape.min.js"></script>
  <script src="https://unpkg.com/dagre@0.8.5/dist/dagre.min.js"></script>
  <script src="https://unpkg.com/cytoscape-dagre@2.5.0/cytoscape-dagre.js"></script>
  <script>
    (function() {
      const report = window.THREAT_REPORT || {};
      const nodes = (report.graph && report.graph.nodes) || [];
      const edges = (report.graph && report.graph.edges) || [];
      const container = document.getElementById('graph');
      if (!container) return;
      let initialZoom = 1;
      const cssEscape = (value) => (window.CSS && CSS.escape ? CSS.escape(value) : value);
      const edgeIdMap = new Map();
      edges.forEach((e) => {
        const primary = e.id || `${e.src}->${e.dst}`;
        const aliases = new Set([primary, `${e.src}->${e.dst}`]);
        if (e.label) aliases.add(`${e.src}->${e.dst}:${e.label}`);
        aliases.forEach((alias) => edgeIdMap.set(alias, primary));
      });

      function clearCyHighlight(cy) {
        cy.elements().removeClass('cy-highlight cy-dim');
      }

      function highlightThreat(cy, reportObj, threatId) {
        if (!threatId) return;
        const threat = (reportObj.threats || []).find((t) => t.id === threatId);
        clearCyHighlight(cy);
        if (!threat) return;
        const nodeIds = (threat.evidence && threat.evidence.nodes) || [];
        const edgeIds = ((threat.evidence && threat.evidence.edges) || []).map((id) => edgeIdMap.get(id) || id);
        const threatNodes = cy.nodes().filter((n) => nodeIds.includes(n.id()));
        const threatEdges = cy.edges().filter((e) => edgeIds.includes(e.id()));
        const targets = threatNodes.union(threatEdges);
        if (targets.length) {
          targets.addClass('cy-highlight');
          cy.elements().difference(targets).addClass('cy-dim');
          try {
            cy.fit(targets, 60);
            const bbox = targets.boundingBox();
            const center = { x: (bbox.x1 + bbox.x2) / 2, y: (bbox.y1 + bbox.y2) / 2 };
            const clampedZoom = Math.min(cy.zoom(), initialZoom * 1.2);
            if (cy.zoom() > clampedZoom) {
              cy.zoom({ level: clampedZoom, position: center });
            }
          } catch (err) {}
        }
      }

      function clearDomHighlights() {
        document.querySelectorAll('.dom-highlight').forEach((el) => el.classList.remove('dom-highlight'));
      }

      function scrollToThreat(threatId) {
        const detail = document.getElementById(threatId);
        if (detail && typeof detail.scrollIntoView === 'function') {
          detail.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
      }

      function highlightRows(threatIds) {
        clearDomHighlights();
        threatIds.forEach((tid) => {
          const row = document.querySelector(`.threat-row[data-threat-id="${cssEscape(tid)}"]`);
          if (row) row.classList.add('dom-highlight');
        });
      }

      function bindInteractions(cy) {
        const rows = Array.from(document.querySelectorAll('.threat-row[data-threat-id]'));
        rows.forEach((row) => {
          row.addEventListener('click', () => {
            const tid = row.getAttribute('data-threat-id');
            highlightRows([tid]);
            highlightThreat(cy, report, tid);
          });
        });

        const chips = Array.from(document.querySelectorAll('.chip[data-threat-id]'));
        chips.forEach((chip) => {
          chip.addEventListener('click', () => {
            const tid = chip.getAttribute('data-threat-id');
            highlightRows([tid]);
            highlightThreat(cy, report, tid);
          });
        });

        cy.on('tap', 'node, edge', (evt) => {
          const elementId = evt.target.id();
          const matchingThreats = (report.threats || []).filter((t) => {
            const ev = t.evidence || {};
            const nids = ev.nodes || [];
            const eids = ev.edges || [];
            return nids.includes(elementId) || eids.includes(elementId);
          });
          const ids = matchingThreats.map((t) => t.id);
          highlightRows(ids);
          clearCyHighlight(cy);
          evt.target.addClass('cy-highlight');
          cy.elements().difference(evt.target).addClass('cy-dim');
        });
      }

      function createCy() {
        const palette = ['#0ea5e9','#22c55e','#f97316','#a78bfa','#f43f5e','#14b8a6','#eab308','#3b82f6'];
        const zoneColor = {};
        const graphZones = (report.graph && report.graph.zones) || [];
        const zoneElements = graphZones.length
          ? graphZones.map((z) => {
              const label = z.name || z.id;
              if (label && !zoneColor[label]) {
                zoneColor[label] = palette[Object.keys(zoneColor).length % palette.length];
              }
              const zoneId = `zone::${String(z.id).replace(/\s+/g, '_')}`;
              const parent = z.parent_id ? `zone::${String(z.parent_id).replace(/\s+/g, '_')}` : undefined;
              return { data: { id: zoneId, label: label, type: 'zone', parent } };
            })
          : Array.from(new Set(nodes.map((n) => (n.zone_path && n.zone_path.length ? n.zone_path[n.zone_path.length - 1] : n.zone)).filter(Boolean))).map((label) => {
              if (!zoneColor[label]) {
                zoneColor[label] = palette[Object.keys(zoneColor).length % palette.length];
              }
              const zoneId = `zone::${String(label).replace(/\s+/g, '_')}`;
              return { data: { id: zoneId, label, type: 'zone' } };
            });
        const elements = {
          nodes: [
            ...zoneElements,
            ...nodes.map((n) => {
              const zoneId = (n.zones && n.zones.length) ? n.zones[n.zones.length - 1] : null;
              const zoneLabel = (n.zone_path && n.zone_path.length) ? n.zone_path[n.zone_path.length - 1] : (n.zone || zoneId || 'default');
              if (zoneLabel && !zoneColor[zoneLabel]) {
                zoneColor[zoneLabel] = palette[Object.keys(zoneColor).length % palette.length];
              }
              const color = zoneColor[zoneLabel] || '#0ea5e9';
              const parent = zoneId ? `zone::${String(zoneId).replace(/\s+/g, '_')}` : undefined;
              return { data: { id: n.id, label: n.label, zone: zoneLabel, type: n.type, color, parent } };
            })
          ],
          edges: edges.map((e) => {
            const edgeId = e.id || `${e.src}->${e.dst}`;
            return { data: { id: edgeId, source: e.src, target: e.dst, label: e.label || '', protocol: e.protocol || '' } };
          })
        };
        const cy = cytoscape({
          container,
          elements,
          style: [
            { selector: 'node[type = \"zone\"]', style: { 'background-color': '#f8fafc', 'background-opacity': 0.25, 'shape': 'round-rectangle', 'label': 'data(label)', 'color': '#0f172a', 'text-valign': 'top', 'text-halign': 'center', 'text-wrap': 'wrap', 'font-weight': 700, 'font-size': 12, 'text-background-color': '#f8fafc', 'text-background-opacity': 0.9, 'text-background-padding': 4, 'text-margin-y': -12, 'border-style': 'dashed', 'border-color': '#94a3b8', 'border-width': 2, 'padding': 18, 'z-compound-depth': 'bottom' } },
            { selector: 'node', style: { 'background-color': 'data(color)', 'label': 'data(label)', 'color': '#0f172a', 'text-valign': 'center', 'text-halign': 'center', 'text-wrap': 'wrap', 'font-size': 10, 'border-width': 1, 'border-color': '#0f172a10', 'z-compound-depth': 'top' } },
            { selector: 'edge', style: { 'curve-style': 'bezier', 'target-arrow-shape': 'triangle', 'width': 2, 'line-color': '#94a3b8', 'target-arrow-color': '#94a3b8', 'label': 'data(label)', 'font-size': 8, 'text-background-color': '#fff', 'text-background-opacity': 0.7, 'text-background-padding': 2 } },
            { selector: 'node.cy-highlight', style: { 'background-color': '#f97316', 'border-color': '#f97316', 'border-width': 3 } },
            { selector: 'edge.cy-highlight', style: { 'line-color': '#f97316', 'target-arrow-color': '#f97316', 'width': 3 } },
            { selector: '.cy-dim', style: { 'opacity': 0.25 } }
          ],
        });
        try {
          cy.layout({ name: 'dagre', rankDir: 'LR', padding: 30, nodeSep: 40, edgeSep: 20 }).run();
        } catch (e) {
          cy.layout({ name: 'cose', padding: 30, animate: false }).run();
        }
        cy.nodes().forEach((n) => n.grabbable(true));
        initialZoom = cy.zoom();
        return cy;
      }

      const cy = createCy();
      bindInteractions(cy);
    })();
  </script>
</body>
</html>


抽出されたシステム構成図


脅威がシステム構成図のどこにあるのかを表示

企業ネットワーク

システム構成図

対象となる企業ネットワークの構成図はこちらです。draw.ioで作成しています。インターネット、DMZ、社内ネットワークの3つに分かれた、中小企業向けのシンプルなネットワーク構成となっています。


企業ネットワークのシステム構成図

Threat Thinkerの実行

ここではWeb UIを用いて実行してみます。Web UIはthreat-thinker webuiで起動することができます。

$ threat-thinker webui
ℹ️ Starting Threat Thinker Web UI
* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set `share=True` in `launch()`.

draw.io(XML)形式のシステム構成図をコピーして貼り付け、Diagram Formatdrawioを指定します。

諸々のオプションを指定して、Generate Reportをクリックします。

洗い出された脅威

Markdownレポートで示される上位5件の脅威は以下の通りです。外部からアクセス可能な Webサーバに対する攻撃のリスク、入力バリデーション不備によるインジェクション脆弱性、DMZから社内ネットワークへの横展開、内部サーバに保存された機密データの露出リスクなどが含まれています。企業ネットワークで典型的に想定されるリスクがバランスよく指摘されています。

Markdownレポート

Threat Analysis Report

Threat Summary

ID Threat Severity Score
T001 External Attackers Can Reach Public-Facing Web Server High 9.0
T002 Insufficient Input Validation on Public Web Server High 8.0
T003 Potential Lateral Movement from DMZ to Internal Network High 8.0
T004 Sensitive Data Exposure on File and Directory Servers High 8.0
T005 VPN Gateway Exposed to Credential Attacks High 8.0

Threat Details

T001: External Attackers Can Reach Public-Facing Web Server

Severity: High

Score: 9.0

STRIDE: Spoofing, Tampering, Information Disclosure, Denial of Service

Affected Components: Internet, Web Server

Why: Web Server (node 5) is accessible from the Internet (node 2) via External Firewall (node 3), exposing it to direct attacks.

References: ASVS V1.1, ASVS V10.1, CWE-20

Recommended Actions:

Harden the Web Server, apply latest security patches, enforce input validation, and deploy a Web Application Firewall (WAF).


T002: Insufficient Input Validation on Public Web Server

Severity: High

Score: 8.0

STRIDE: Tampering, Information Disclosure, Elevation of Privilege

Affected Components: Web Server

Why: Web Server (node 5) processes user input from the Internet, risking injection and XSS if input is not validated.

References: ASVS V5.1, ASVS V5.3, CWE-20

Recommended Actions:

Implement strict server-side input validation and output encoding.


T003: Potential Lateral Movement from DMZ to Internal Network

Severity: High

Score: 8.0

STRIDE: Elevation of Privilege, Information Disclosure, Tampering

Affected Components: Web Server, Internal Firewall, Internal Network

Why: Traffic can flow from DMZ (Web Server node 5) through Internal Firewall (node 7) to Core Switch/Internal Network (node 9/8), risking lateral movement if DMZ is compromised.

References: ASVS V9.1, ASVS V10.2, CWE-284

Recommended Actions:

Strictly limit and monitor DMZ-to-internal traffic, enforce least privilege firewall rules, and segment networks.


T004: Sensitive Data Exposure on File and Directory Servers

Severity: High

Score: 8.0

STRIDE: Information Disclosure

Affected Components: File Server, Directory Server (AD)

Why: File Server (node 11) and Directory Server (node 12) store sensitive data and credentials, risking exposure if access controls are weak.

References: ASVS V9.4, ASVS V10.4, CWE-200

Recommended Actions:

Encrypt data at rest, enforce strict access controls, and audit access to sensitive servers.


T005: VPN Gateway Exposed to Credential Attacks

Severity: High

Score: 8.0

STRIDE: Spoofing, Elevation of Privilege, Denial of Service

Affected Components: Internet, VPN Gateway

Why: VPN Gateway (node 6) is reachable from the Internet (node 2) and processes credentials, making it a target for brute-force and credential stuffing.

References: ASVS V2.1, ASVS V2.2, CWE-307

Recommended Actions:

Enforce strong authentication (MFA), rate-limit login attempts, and monitor for suspicious access patterns.


ID Threat Severity Score
T001 External Attackers Can Reach Public-Facing Web Server High 9.0
T002 Insufficient Input Validation on Public Web Server High 8.0
T003 Potential Lateral Movement from DMZ to Internal Network High 8.0
T004 Sensitive Data Exposure on File and Directory Servers High 8.0
T005 VPN Gateway Exposed to Credential Attacks High 8.0

スマートホーム

システム構成図

対象となるスマートホームの構成図はこちらです。脅威モデリングツール OWASP Threat Dragonで作成しています。自宅の住人がモバイルアプリを通じてクラウドの制御サービスにアクセスし、そのクラウドからホームルーター経由でIPカメラやスマートロック、スマートスピーカーなどのデバイスを操作する、典型的なクラウド連携型スマートホーム環境を示しています。


スマートホームのシステム構成図

Threat Thinkerの実行

ここではWeb UIを用いて実行します。

Threat ThinkerにはRAG機能があり、MarkdownやHTMLなどのファイルをアップロードしてKnowledge Baseを構築することができ、脅威推論の際に参照させることができます。今回はスマートホームのシステムなのでOWASP IoT Top 10に基づくKnowledge Baseを構築します。

OWASP IoT Top10に基づくKnowledge Baseの構築

脅威の推論で構築したKnowledge Baseを参照させるための設定をします。

構築したKnowledge Baseを脅威推論で使用

洗い出された脅威

Markdownレポートで示される上位5件の脅威は以下の通りです。モバイルアプリとクラウド間の通信が暗号化されていない可能性、クラウドからホームデバイスに送信されるコマンドの真正性が確認できないこと、クラウド側に保存される動画・ログの保護不足などが挙げられています。また、ユーザー認証が弱い場合は、第三者がデバイスを不正操作できる危険性も指摘されています。OWASP IoT Top10を参照した脅威分析の結果としても納得感のある内容となっています。

Markdownレポート

Threat Analysis Report

Threat Summary

ID Threat Severity Score
T001 Insecure Communication Between Mobile App and Cloud Control Service High 9.0
T002 Lack of Authentication for Device Commands from Cloud to Home Network High 9.0
T003 Insecure Storage of Sensitive Video/Logs in Cloud High 8.0
T004 Unencrypted Video/Telemetry Data in Transit High 8.0
T005 Weak or Missing Authentication for Mobile App User Actions High 8.0

Threat Details

T001: Insecure Communication Between Mobile App and Cloud Control Service

Severity: High

Score: 9.0

STRIDE: Tampering, Information Disclosure, Spoofing

Affected Components: Mobile App, Cloud Control Service

Why: If communication is not encrypted and authenticated, attackers can intercept or modify control commands or device status (assumed due to unspecified protocol).

References: ASVS V9.1, ASVS V2.1, CWE-319

Recommended Actions:

Enforce TLS 1.2+ with certificate validation and mutual authentication between Mobile App and Cloud Control Service.


T002: Lack of Authentication for Device Commands from Cloud to Home Network

Severity: High

Score: 9.0

STRIDE: Spoofing, Elevation of Privilege, Tampering

Affected Components: Cloud Control Service, Home Router, IP Camera, Smart Lock, Smart Speaker

Why: If device commands are not authenticated, attackers could impersonate the cloud and control home devices (protocol and auth not specified).

References: ASVS V2.1, ASVS V4.2, CWE-287

Recommended Actions:

Require strong authentication (e.g., signed tokens or mutual TLS) for all device command channels from cloud to home devices.


T003: Insecure Storage of Sensitive Video/Logs in Cloud

Severity: High

Score: 8.0

STRIDE: Information Disclosure, Repudiation

Affected Components: Cloud Storage
Video/Logs

Why: If cloud storage is not encrypted or access-controlled, attackers or insiders could access or tamper with sensitive video/logs.

References: ASVS V10.1, ASVS V1.4, CWE-200

Recommended Actions:

Encrypt data at rest in cloud storage and enforce strict access controls and audit logging.


T004: Unencrypted Video/Telemetry Data in Transit

Severity: High

Score: 8.0

STRIDE: Information Disclosure, Tampering

Affected Components: IP Camera, Home Router, Cloud Control Service, Cloud Storage
Video/Logs

Why: Sensitive video/log data may be exposed if not encrypted during transmission (protocols not specified).

References: ASVS V9.1, ASVS V10.1, CWE-319

Recommended Actions:

Encrypt all telemetry/video data in transit using TLS 1.2+ and validate certificates at each hop.


T005: Weak or Missing Authentication for Mobile App User Actions

Severity: High

Score: 8.0

STRIDE: Spoofing, Elevation of Privilege

Affected Components: Resident User, Mobile App

Why: If the Mobile App does not enforce strong authentication, attackers could impersonate users and control devices (auth not specified).

References: ASVS V2.1, ASVS V4.2, CWE-287

Recommended Actions:

Implement strong user authentication (e.g., OAuth 2.0, MFA) in the Mobile App for all sensitive actions.


ID Threat Severity Score
T001 Insecure Communication Between Mobile App and Cloud Control Service High 9.0
T002 Lack of Authentication for Device Commands from Cloud to Home Network High 9.0
T003 Insecure Storage of Sensitive Video/Logs in Cloud High 8.0
T004 Unencrypted Video/Telemetry Data in Transit High 8.0
T005 Weak or Missing Authentication for Mobile App User Actions High 8.0

また、Threat ThinkerではThreat Dragonのフォーマットで入力した際には推論した脅威を加えた上でThreat Dragon形式で出力をすることができます。


各要素に紐づく脅威が追加されている

おわりに

本記事では、Threat Thinkerを使ってAWSベースシステム、企業ネットワーク、スマートホームという異なる3種類のシステムに対してThreat Thinkerを用いた脅威モデリングを試しました。システム構成図を読み込ませるだけで基本的な脅威は洗い出せることが分かりました。

一方で、ビジネスロジック固有のリスクや各組織特有の運用に依存する問題などは人間が見ていく必要があります。LLMによる脅威モデリングは専門家のレビューの代替ではなく、初期のたたき台を用意したり、抜け漏れ防止のための補助として活用するのが現実的だと考えています。OWASP IoT Top 10 のようなガイドラインをKnowledge Baseとして取り込めば、ドメイン特化の推論も可能になります。気になった方は、お手元のシステム構成図を用いてThreat Thinkerによる脅威モデリングを試していただけると幸いです。

Discussion