💡 今日學習目標:不是背 STRIDE 六個字母就結束。今天要用一個真正的免費開源工具(OWASP pytm)跑一次自動化威脅建模,親眼看到工具真實比對出的威脅清單,並用同一套工具驗證「加固後威脅真的變少了」。
Day 22 我們看了 Capital One 的案例:一次 SSRF,疊上三個架構疏忽,捲走一億筆資料。那篇的結論是:架構層級的疏忽,要在架構設計階段就被抓出來。
威脅建模(Threat Modeling)就是做這件事的方法:在系統還沒寫成程式碼之前,先看著架構圖,系統性地問「這裡可能會出什麼事」。微軟為此提出的記憶口訣,就是大家常聽到的 STRIDE。
但今天不想只給你一張填好的表格看看就算了,那樣頂多算「讀過」,不算「動手做」。這 30 天的動手做篇一貫的規矩,是接一個真的工具、跑出真的結果。 今天我們要用的是 OWASP pytm:一個完全免費、開源、用 Python 描述系統架構、然後自動幫你比對威脅的命令列工具。零預算,跟 SonarQube 系列一樣。
pip install pytm
# Ubuntu / Debian,用來把威脅模型畫成資料流圖(DFD)
sudo apt-get install graphviz
需要 Python 3.9 以上。裝完之後確認一下:
python3 -c "import pytm; print(pytm.__file__)"
dot -V
兩個指令都有正常輸出,就可以開始了。
pytm 的核心概念是 「Threat Modeling as Code」 :你不畫圖,你寫程式碼描述系統長什麼樣子,工具自己畫圖、自己比對威脅。
我們就描述前面幾天一直在用的那個電商付款流程:手機 App 呼叫 Payment API、Payment API 查訂單資料庫:
from pytm import TM, Boundary, Actor, Server, Datastore, Dataflow, Data, Classification
tm = TM("電商付款流程 STRIDE 威脅模型")
internet = Boundary("Internet")
internal = Boundary("Internal Network")
user = Actor("手機 App 使用者")
user.inBoundary = internet
payment_api = Server("Payment API")
payment_api.inBoundary = internal
# ⚠️ 刻意先不設任何 controls,模擬「還沒做威脅建模」的原始架構
orders_db = Datastore("Orders DB")
orders_db.inBoundary = internal
card_data = Data(
name="付款資料",
description="信用卡卡號、金額、收件地址",
classification=Classification.RESTRICTED,
)
req = Dataflow(user, payment_api, "送出付款請求")
req.protocol = "HTTPS"
req.data = card_data
query = Dataflow(payment_api, orders_db, "查詢/寫入訂單")
query.protocol = "SQL"
query.data = card_data
完整檔案(含
if __name__ == "__main__": tm.process())與加固版對照檔,放在筆者的Github2026-ironman-code/day23-stride/底下,照README.md的指令就能重現下面每一張圖與每一個數字。
2026-ironman-code-day23-stride/
存成 payment_model.py 後,跑:
python3 payment_model.py --dfd | dot -Tpng -Gdpi=192 -o dfd.png
不用自己拉框線、不用自己選圖示,工具直接把你寫的架構畫成標準的資料流圖(Data Flow Diagram):

🔑 這張圖裡藏著 Day 22 講的「信任邊界」:那兩個紅色虛線框,就是 pytm 幫你自動畫出來的信任邊界:
手機 App 使用者站在Internet邊界裡,Payment API和Orders DB站在Internal Network邊界裡。這不是巧合的美化效果,是威脅建模工具的標準表示法(notation):DFD 裡的每一條邊界線,都是「資料要重新被檢查一次」的地方。
執行威脅比對很簡單:
from payment_model import tm
tm.resolve()
print("total findings:", len(tm.findings))
輸出:
total findings: 62
62 筆。 如果你是照著 STRIDE 的六個字母去想,這裡應該預期看到 6 類、頂多十幾筆才對。這正是新手第一次自己動手跑工具時最容易愣住的地方。
原因是:STRIDE 六個字母是一套幫助你「思考分類」的記憶口訣,不是工具真正拿去比對的規則庫本身。 pytm 背後比對的,是收錄在 MITRE CAPEC(Common Attack Pattern Enumeration and Classification)裡上百種具體攻擊模式,每一種都可以被歸進 S、T、R、I、D、E 六個大類之一,但拆開來看,遠比六條規則細緻得多。
這也呼應了 Day 15 講過的事:別把助記口訣當成完整清單,那次是 OWASP Top 10 的排名,這次換成 STRIDE,道理相通。
62 筆全部列出來會超出今天的份量,這裡從真實輸出裡挑出剛好對應 S-T-R-I-D-E 六個類別的六筆,看看工具實際寫了什麼:

挑兩筆細看實際內容,你會發現工具給的說明比表格更具體:
DE04 Audit Log Manipulation(Orders DB):"The attacker injects, manipulates, deletes, or forges malicious log entries into the log file, in an attempt to mislead an audit of the log file or cover tracks of an attack.",這跟 Day 26 要講的「稽核日誌」是同一件事:Log 如果能被竄改,Repudiation 防線就形同虛設。DR01 Unprotected Sensitive Data(送出付款請求這條 Dataflow):工具直接盯上了我們標記為 Classification.RESTRICTED 的付款資料,因為這條資料流當時沒有任何加密控制。這就是 Day 17 講的「敏感資料保護」在架構層級的對應。跟 Day 08 修 Security Hotspot 邏輯一樣:不是一筆一筆去對付 62 筆警告,而是在架構層級補上該有的控制,讓一整類問題一次消失。
在加固版模型裡,我們針對 Day 22 那三大柱石各補上對應的控制。下面節錄其中八行,讓你看清楚三大柱石是怎麼對應到 pytm 的控制屬性的:
# 柱石 1:信任邊界,跨邊界的呼叫要重新認證
payment_api.controls.authenticatesSource = True
payment_api.controls.authorizesSource = True
req.controls.implementsAuthenticationScheme = True
# 柱石 2:最小攻擊面,角色權限收斂到剛好夠用
payment_api.controls.implementsPOLP = True
orders_db.controls.hasAccessControl = True
# 柱石 3:故障安全,資源耗盡與逾時要有預設處置
payment_api.controls.handlesResourceConsumption = True
payment_api.controls.isResilient = True
payment_api.controls.definesConnectionTimeout = True
⚠️ 上面是節錄,不是全部。 完整的加固版一共在 4 個元件上設定了 23 個控制屬性:Payment API 十項、Orders DB 五項,以及兩條 Dataflow 各四項(加密、驗證來源與目的地、授權)。全部都在
/day23-stride/payment_model_secure.py裡。只複製上面那八行去跑,數字不會是 35。 要重現下面的結果,請直接用倉庫裡的那支檔案。
重新跑一次:
total findings: 35
62 筆降到 35 筆,而且前面挑出來對應 S-T-R-I-D-E 的那六筆,全部從清單裡消失。這不是筆者口頭保證,是工具重新掃描後的真實結果,你把 /day23-stride/ 裡的兩個檔案跑一次,會拿到一模一樣的數字。
⚠️ 35 筆還不是 0 筆,這是刻意的。 威脅建模不是玩「把數字歸零」的遊戲。剩下的 35 筆裡,有些是這個最小化範例本來就沒打算處理的情境(例如實體機房存取控制)。威脅建模的產出是一份「決定表」:哪些威脅要修、哪些要接受風險、哪些轉嫁給其他機制,不是無腦把清單清空。
這是新手最容易停在的地方。背熟 Spoofing、Tampering、Repudiation、Information Disclosure、Denial of Service、Elevation of Privilege 六個詞,只是拿到了一張分類地圖,不是拿到了威脅清單本身。
今天這 62 筆真實輸出應該讓這件事變得很具體:同一個「S」(Spoofing),可以是 Principal Spoof,也可以是憑證偽造、Session 偽冒、DNS Spoofing……字母只告訴你要往哪個方向想,細節永遠要回到你實際的架構去看。
另一個常見誤解是「威脅建模要買貴的商業工具(如 Microsoft Threat Modeling Tool 那種 GUI 軟體)才能做」。今天你用一支 Python 檔案外加免費的 Graphviz 就做完了整套流程,這正是這系列一路強調的:零預算不是打折版本,是真的能跑出完整結果。
pip install pytm 失敗? 確認 python3 --version 在 3.9 以上;pytm 依賴 pydantic 2.10+,太舊的 pip 可能解不出相容版本,先 pip install --upgrade pip。dot: command not found? 代表只裝了 pytm 沒裝 Graphviz,這是兩個獨立的套件,pip install 不會幫你裝系統層級的 Graphviz。fonts-noto-cjk(Ubuntu:sudo apt-get install fonts-noto-cjk)即可。total findings 跑出來是 0? 檢查是不是不小心從 payment_model_secure.py 匯入,加固版本身就只剩 35 筆,不是 0 筆是正常的。import 兩個模型,數字對不起來? 這是筆者實測時真的踩到的坑:pytm 用 class 層級的全域清單記錄所有已建立的元件,同一個行程裡先後匯入 payment_model 和 payment_model_secure,兩份模型的元件會被算在一起,len(tm.findings) 就不再是乾淨的 62 或 35。兩個模型要分開、各自用一個全新的 Python 行程執行,不要在同一支腳本裡先後匯入兩者,這也是為什麼 README.md 裡的指令是兩段獨立的 python3 -c "...",不是合併成一段。💬 明日預告:【Day 24】認識國際資安標準框架:NIST Cybersecurity Framework (CSF 2.0) 核心理念
明天我們將介紹國際級的資安標準頂層設計:NIST CSF 2.0 框架!