上一章已經知道 Alice 可以讀 N-1,也知道 N-2 屬於 Bob。現在問 AI:「這裡會不會有越權?」它回答「可能有 IDOR」。這句話還不能交給工程師修,因為缺了會讓它成立或失敗的條件。
這篇把猜測寫成可測試假設。先備是攻擊面篇與基本 HTTP 操作。完成後,你應能把一個模型建議拆成來源、單一變因、確認條件、反證與未決狀態,而非累積一串漏洞名稱。
一個 ID 不是一個漏洞
工單 ID 出現在請求裡很正常。產品需要使用者選資源,問題在於後端是否檢查這個身分對資源的權限。PortSwigger 的 User ID 實驗展示可控 ID 配上缺少授權時的水平越權;它沒有說「有 ID 參數就必然有洞」。
回到本系列的政策:同租戶且本人擁有才可讀。Alice、Bob 都在 north,Carol 在 south。把測試拆成兩個假設:
| 假設 | 只改什麼 | 安全預期/確認漏洞的條件 |
|---|---|---|
| H-OWNER:缺少 owner 檢查 | Alice 保持登入,N-1 換 N-2 | 應拒絕;若真的回 Bob 的工單內容才確認 |
| H-TENANT:缺少租戶檢查 | Alice 保持登入,N-1 換 S-1 | 應拒絕;若真的回 Carol 的內容才確認 |
| 正常對照 | Bob 讀 N-2 | 應允許,證明資源和正常功能存在 |
分開的好處是能解釋部分修補:只加 tenant 檢查可能擋住 S-1,卻仍讓 Alice 讀 N-2。只做跨租戶測試,容易漏掉同公司的 owner 邊界。
給模型一張能反駁自己的表
下載假設與提示模板。檔案內有三個人工編寫的候選,包括之後的附件注入;它不是任何模型的實測輸出。你可以將 model_prompt、攻擊面觀察與服務程式交給自己的模型,但先移除真實帳號、金鑰與客戶資料。
每個候選至少填以下內容:
{
"id": "H-OWNER",
"change": "same Alice session; N-1 -> N-2",
"confirm": "N-2 content equals Bob owner control",
"disprove": "403 and no ticket content",
"inconclusive": "wrong target, timeout, or missing baseline"
}
確認條件故意沒有寫「模型說成功」。如果 HTTP 回 200,內容卻是自己的 N-1,代表 ID 沒生效或服務有其他行為;仍不能確認讀到 N-2。若只回空殼、通用錯誤頁或登入頁,也不能用狀態碼代替資料證據。
原始碼線索也要走同一條路。看到 ticket = tickets[id] 後直接回傳,很可疑;但身分檢查可能在上層中介層。請模型列出「請求入口 → 認證 → 資源查找 → 政策 → 回覆」的呼叫路徑,再把缺少的環節列成待查,而非只看一行就確定漏洞。
真送請求,保留安全反例
python3 attack-runner.py hypotheses --build vulnerable-v1 --out hypotheses-before.json
python3 attack-runner.py hypotheses --build fixed-v1 --out hypotheses-after.json
兩輪各自啟動新服務,使用同一份合成角色與資源。輸出中的 same_tenant 與 other_tenant,有洞版會是 content_returned,修補版是 denied_without_content。trace 包含 Alice 正常讀取、兩個換 ID 測試,以及 Bob 的正常對照。
這裡的「修補版」反例很重要:模型如果仍把固定版拒絕的同一條路徑報成已確認漏洞,就應退回它的判定。另一方面,這一輪拒絕只推翻目前的請求與身分組合,不代表搜尋、匯出或快取等所有路徑都安全。範圍寫得窄,別人才知道下一步要補什麼。
NIST 的評測調查發現過修改本機原始碼、卻沒有改到遠端目標的無效嘗試。這正提醒我們:模型讀到的檔案版本,必須和實際服務版本連起來。改了本機檔案,不會自動改掉你正在測的雲端程序。
未決是必要結果
假設第一筆健康檢查正確,N-1 卻因網路逾時沒有結果。接著 N-2 得到 403。這時不能寫「H-OWNER 已推翻」,因為正常基準沒建立。記成未決,保留錯誤與請求時間,再修環境重跑。
其他未決原因包括:資料被前一輪刪掉、Bob 正常對照也失敗、認證中途過期、代理改寫回覆。測試器應停止並顯示原因,模型不能替缺失證據補出「可能是防火牆成功阻擋」。
搬到自己的雲端副本時,記住 HTTPS 入口、內部程序版本與 fixture 是否一致;使用重播器的 --base-url 和 --target-id,不要把同一個 200 當作跨環境的同一筆證據。PortSwigger 練習則把「工單內容」改成題目指定帳號資料,以自己實例的工作階段與題目成功條件判斷,無須把測試限制在本機。
練習與答案
模型交來:「Alice 換成 N-2 得到 403,所以這個產品沒有越權。」你會怎麼改?
改成:「在指定版本與 Alice 工作階段,正常 N-1 成功後,N-2 請求被拒,回覆沒有工單內容;目前沒有重現這個 owner 越權假設。」還要附 Bob 能正常讀 N-2 的對照。產品整體的結論需要其他讀取路徑和角色證據。
再問自己:若 N-2 回 200 且內容和 Bob 的對照一致,但政策其實允許同公司共享呢?答案是先修正政策與預期,這不構成該政策下的 owner 越權。測試要挑戰系統真正承諾的邊界。
真模型小樣本:已知政策能否變成測試?
2026-10-22,我們另外透過 Codex CLI 指定 gpt-6.1-sol,三次分別請模型檢查 return actor is not None。提示直接提供同租戶且本人擁有的政策與合成角色;三次都提出 N-1 應允許、N-2 與 S-1 應拒絕,獨立檢查的必要案例覆蓋為 3/3。這測到的是依已知政策整理測試,不是自行發現未知漏洞,也還不是 HTTP 利用證據。
打開實測結果的 hypotheses,逐筆看模型理由及 covers_required。本節與前面的人工 attack-hypotheses.json 是兩份不同來源;真正 HTTP 的觀察在模型迴圈篇。
要重跑,把model-eval.py與ticket-lab.py下載至同一資料夾,準備 Python 3.11 以上與已登入的 Codex CLI,再執行:
python3 model-eval.py --out live-run-01 --repetitions 3
輸出目錄必須全新;程式會真的呼叫模型,並保存提示、回答、用量與 HTTP 紀錄。Codex 非互動文件說明 CLI 與 JSONL 用法。下載版已修正注入階段的輸出契約,重跑會產生新觀測,不會逐位元重現這份結果。
這是三次重複的小樣本。CLI 接受指定 model ID,沒有回傳可驗證的精確權重快照,也未控制溫度;CLI 系統提示與執行框架都會影響結果。沒有逐次實付金額,亦不據此排名模型。
更新紀錄
- 2026-10-22:加入真模型小樣本、可重跑程式與結果,保留原固定案例及各自限制。
參考資料
- PortSwigger:User ID controlled by request parameter:可控 ID 與授權缺口的完整實驗說明。
- NIST:Examples of cheating in CAISI’s agent evaluations:答案污染、評分捷徑與無效的本機修改。