Guidant AI 資安檢視總報告 · STRIDE 威脅分類

R 事後無法追查(Repudiation)

命中這一類的 14 件,還原成模組頁的 15 條原始條目逐條展開。每一條用白話寫出:在哪裡、攻擊面、信任邊界、元件端點、駭客怎麼打、得手什麼、修正的做法。

🟠 3 🟡 8 ⚪ 4 已修 15
§1

這一類是什麼

做了事卻能否認,系統沒有能力追查或證明。STRIDE 六類裡它破壞的是「不可否認性」——事情發生之後,系統要能回答「是誰、在什麼時候、從哪裡、做了什麼」,而且這份紀錄本身要可信。判定時問的是:做完之後,查不查得到是誰、紀錄可不可信? 典型樣子是操作不留紀錄、冒名留言、日誌被偽造、刪除不留痕。

對稽核產品來說這一類格外要緊:客戶買我們,買的就是「紀錄可以拿來當證據」。這次命中的 15 條在本專案長成三種形狀:

  • 經手人可以被冒(M06-7、M03-14、M05-8、M05-9、M06-12、M23-2、M06-19):紀錄照寫,但寫上去的「是誰做的」不對——不該動手的人動了手、名字取自畫面送上來的值、或事件記到別的專案名下。
  • 紀錄寫不進去或寫錯來源(M09-6、M09-7、M08-7):操作照做,日誌卻漏掉這一筆,或來源 IP 全記成同一台代理伺服器、甚至讀的是對方自己填的值。
  • 紀錄本身能被偽造或抹掉(M09-4、M12-6、M18-8、M08-2、M08-1):往客戶的監控系統塞假日誌、受稽核方改掉稽核方的紀錄、刪掉停權與鎖定紀錄、讓防竄改機制對竄改視而不見。

STRIDE 與 OWASP 的關係:OWASP 講「程式哪裡寫錯」,STRIDE 講「攻擊者能拿它做什麼」。同一條問題通常兩邊都命中,所以這一頁的條目會與 OWASP 各頁重複出現,內容相同——兩邊各自完整,讀者從哪一邊進來都看得完。這一類 14 件裡只有 7 件以 R 為主分類,其餘 7 件主要歸在「竄改資料」或「權限提升」,R 是它們的附帶後果。

為什麼是 15 條不是 14 件:總結問題總表的 #37 由稽核流程(M06-7)與弱點檢測(M03-14)兩個模組各撈到一次——同一道守門,被兩邊的功能共用——總表併成一件;本頁拆回模組頁原始條目,兩條都列。標題括號內是總表編號,可回去對照。

§2

條目清單

# 條目 嚴重度 出自 狀態
1 M08-1 換掉負責核對簽章的零件,整套防竄改失效且零警示 🟠 高 防竄改檢查 ✅ 已修
2 M08-2 刪掉一個檔案,被鎖的機器就能重開、鎖定證據跟著消失 🟠 高 防竄改檢查 ✅ 已修
3 M18-8 子單位帳號刪得掉母公司的停權紀錄與授權異動紀錄 🟠 高 授權管理 ✅ 已修
4 M03-14 唯讀角色可以對客戶機器發動掃描、刪掉失敗紀錄 🟡 中 弱點檢測整合 ✅ 已修
5 M06-7 只能看的人可以完成或退回別人的稽核任務 🟡 中 稽核流程 ✅ 已修
6 M05-8 改別人的問卷討論還掛原作者名字,刪除不留痕 🟡 中 問卷 ✅ 已修
7 M23-2 任何員工都能改刪別人的意見回饋並關掉外部問題單 🟡 中 意見回饋 ✅ 已修
8 M05-9 多人同時填問卷時,署名採用畫面送上來的名字 🟡 中 問卷 ✅ 已修
9 M09-4 在欄位裡塞換行,就能往客戶監控系統塞偽造日誌 🟡 中 系統日誌 ✅ 已修
10 M06-19 甲專案管理人能強制開始乙專案的任務,事件記在甲名下 🟡 中 稽核流程 ✅ 已修
11 M09-6 把網址加長,這次操作就不會出現在操作日誌 🟡 中 系統日誌 ✅ 已修
12 M06-12 推進或退回階段時,顯示的操作者名稱由呼叫端自填 ⚪ 低 稽核流程 ✅ 已修
13 M09-7 操作日誌的來源 IP 全記成前端代理伺服器 ⚪ 低 系統日誌 ✅ 已修
14 M12-6 專案經理能改寫、再刪掉稽核人員寫的改善建議 ⚪ 低 稽核輪次管理 ✅ 已修
15 M08-7 防竄改旁證裡的來源 IP 讀的是用戶端可填的欄位 ⚪ 低 防竄改檢查 ✅ 已修

§3

M08-1 🟠 換掉負責核對簽章的零件,整套防竄改失效且零警示

欄位 內容
嚴重度 🟠 高(總表 #17;OWASP:A08 軟體或資料完整性失效;主分類為竄改資料)
在哪裡 防竄改檢查模組:客戶機房那台機器開機、每四小時抽查、敏感操作前,都會拿原廠簽過名的檔案清單核對程式有沒有被改過;負責「核對簽名」的那個第三方加解密零件,被打包時歸在開機不檢查的那一層
攻擊面位置 客戶機房那台安裝機的檔案系統。需要主機管理權限(落地版客戶自己的維運人員、或已入侵主機的人);這一面本來就由客戶掌握,防竄改機制正是為了「客戶主機上的人改了東西要看得出來」而存在
信任邊界位置 主機上的檔案 ↔︎ 原廠簽章。可信的只有原廠私鑰簽出的清單;核對工具本身必須落在被核對的範圍內,否則等於自己驗自己。當時這個零件被別的套件相依「連坐」帶進來、原樣以 .py 檔附帶在不檢查的那層,邊界內缺了最關鍵的一塊
元件端點位置 無 HTTP 端點,在開機與執行期:驗簽 common/integrity/adapters.py 的 LicenseEngineVerifier.verify → jedi_license_runtime/common/engine.py 的 verify_payload(Ed25519,靠 cryptography/cffi);三層檢查在 jedi_integrity/app/startup_gate.py(開機)、app/runtime_check.py(抽查與敏感操作);打包分層在 scripts/build/build_release.sh、scripts/build/security_critical_pkgs.sh
駭客怎麼打 ① 有主機權限的人進到安裝目錄,找到原樣附帶的加解密零件;② 把它換成一支「不管簽章對不對都回答通過」的假貨——不是刪掉(刪掉服務會起不來、動靜很大),是掉包;③ 重開服務,開機核對照常印出「驗證通過」;④ 接著任意改產品程式:拿掉授權鎖、改計分邏輯、改寫稽核紀錄的寫法,四小時抽查與敏感操作即時驗證用的是同一個假零件,三層一起放行、全程零警示(2026-09-10 實測證實)
得手什麼 客戶機上的產品可被任意竄改而系統照樣顯示一切正常,之後產出的稽核紀錄都不再可信
修正的做法 ① 安全關鍵零件強制編進核心:新增 security_critical_pkgs.sh 列出 cryptography、cffi,打包時從「不編譯清單」扣除、改以 --include-package 整包編成機器碼,不再是可以單獨抽換的檔案;② 打包守門:產物內這兩支若還有任何 .py 原樣附帶、或找不到編譯痕跡,打包直接失敗(assert_security_critical_compiled);③ CM-2132 修正守門判斷——cffi 編譯後不留目錄、只在根目錄留 _cffi_backend*.so,原本「看目錄在不在」會誤擋,改成依套件型態各自認編譯證據,且只查產物根目錄,避免被 numpy 內同名子目錄誤判;④ 同批順手:打包版一律忽略「改核對目錄」的環境變數(M08-3 放大器)
狀態 ✅ 已修,1.21.0 出貨(CM-2053,BE commit 0dda326b8;守門修正 CM-2132,52df2cf6c+919432229)。驗證:188 打包產物竄改實測(CM-2136)確認開機會擋
§4

M08-2 🟠 刪掉一個檔案,被鎖的機器就能重開、鎖定證據跟著消失

欄位 內容
嚴重度 🟠 高(總表 #18;OWASP:A08 軟體或資料完整性失效;主分類為竄改資料)
在哪裡 防竄改檢查模組:偵測到竄改後機器被鎖住、拒絕開機。文件寫有「檔案標記」與「資料庫紀錄」兩道鎖,程式當時只做了檔案那一道
攻擊面位置 客戶機房那台安裝機的持久掛載目錄。需要主機權限;不需要任何系統帳號
信任邊界位置 主機檔案系統 ↔︎ 資料庫裡的竄改事件表。鎖定紀錄是這套機制要保留的證據,應該放在主機使用者不能隨手刪的地方再核對一次;當時查資料庫的程式寫好了卻沒人呼叫,證據只剩一個任何主機使用者都刪得掉的檔案
元件端點位置 無 HTTP 端點,在開機閘門:jedi_integrity/app/startup_gate.py(拒啟判定)、domain/tamper_marker.py(檔案標記 .integrity-tamper)、app/boot_db_lock.py 的 find_db_lock/restore_marker_from_db;宿主實作 common/integrity/boot_lock_store.py 的 PostgresBootLockStore.find_active_lock,查 public.integrity_tamper_events
駭客怎麼打 ① 機器因竄改被鎖、畫面顯示鎖定;② 有主機權限的人刪掉那一個標記檔;③ 重開服務,開機只看檔案、看不到標記就放行,機器照常開起來;④ 「這台曾被竄改、被鎖過」這筆證據跟著消失,維護者以為有兩道鎖、也不會想到去查
得手什麼 被鎖的機器自行解鎖,竄改事件的鎖定證據被抹掉
修正的做法 ① 開機另查資料庫(套件 953697c9):檔案無標記且為正式打包版時,用宿主開的短命連線查本機指紋有沒有未解鎖的竄改事件;查到就把標記檔補回去再拒啟;開機偵測到竄改時也同步寫資料庫;文件三處改成與程式一致;② 擋手寫解鎖回執(套件 ba129ca1):驗收發現刪標記+手寫一張解鎖回執也能排除事件,改成回執必須附原廠解鎖憑證原文,閘門與資料庫回填都重驗簽章、指紋、事件編號才採信;③ 權限不足改拒啟(CM-2112,套件 da9294fe+BE a7743f1b9):資料庫「連得上但沒權讀」或缺帳密時拒絕開機,不再放行;連不上、表不存在仍放行並印警示(開機時資料庫晚起是常態)
狀態 ✅ 已修(CM-2053+CM-2112,1.21.0 出貨)。已知限制:同時刪標記檔又刪竄改紀錄表仍會放行——需主機加資料庫管理員雙權限,超出本機制「擋隨手改檔」的定位
§5

M18-8 🟠 子單位帳號刪得掉母公司的停權紀錄與授權異動紀錄

欄位 內容
嚴重度 🟠 高(總表 #23;OWASP:A01 存取控制失效;主分類為權限提升)
在哪裡 授權管理模組:授權檔、授權異動紀錄、停權紀錄這三張資料表的資料庫門禁規則(決定哪個客戶碰得到哪幾筆)
攻擊面位置 任何以子單位帳號身分寫到這三張表的路徑。資料庫門禁是最後一道防線——程式層守門只要漏一支,這一道就是唯一的擋板。需要被停權客戶手上有任一子單位帳號
信任邊界位置 子單位 ↔︎ 母公司(租戶 ↔︎ 租戶)。子單位「讀」母公司的授權是設計需要(授權只發給最頂層、底下共用);但當時同一條規則把「讀得到」直接等同「改得到、刪得到」,資料庫這一側沒有另外守寫入
元件端點位置 資料表 config.tenant_licenses、config.tenant_license_events、config.tenant_license_suspensions 的門禁規則(RLS policy);套件 jedi-license-runtime/migrations/002-license-rls.sql、003-tenant-license-suspensions.sql;已裝機修正 scripts/sql/2026-09-28-fr114-rls-tenant-prefix-unify.sql、scripts/sql/2026-10-01-cm2369-license-tables-ancestor-read.sql;讀這三張的功能如 GET /license/status(jedi_license_runtime/api/routing.py)
駭客怎麼打 ① 被停權的客戶用手上任一個子單位帳號登入;② 經任何一條以子單位身分寫到停權紀錄表的路徑送出刪除;③ 資料庫規則把母公司那列算成「這個子單位碰得到的」,同一條規則連刪除一起放行;④ 停權紀錄表的設計是「有一列就代表停權中」,刪掉那列整棵樹當場復權;⑤ 再順手改掉授權到期日與模組清單、刪掉追查用的異動紀錄,把痕跡一併抹掉(模組頁未點名具體寫入入口,此處依資料庫規則推論)
得手什麼 被停權客戶自行復權並改寫授權內容,且刪得掉事後追查用的異動時間線
修正的做法 ① 改刪收緊(CM-2271,BE 6c74ff0e3+套件 6e0d0d4b):這三張連同弱點檢測、遠端代理共九張表,門禁規則由「把路徑拆成編號陣列逐一比」改成前綴比對,只碰得到自己與底下;已裝機另開主線 migration 重建(套件檔以 DO 區塊包,既有庫會跳過);新增守衛測試,斷言全庫不再有拆陣列寫法;② 讀取開回給子單位(CM-2369,BE 3574033a6+套件 ba14e128):第①步讓子單位讀不到上層的照、被判成沒買全面 403;三張表各加一條只管「讀」的規則(tenant_licenses_ancestor_read 等三條,重用既有的祖先判斷函式),改刪仍只吃前綴規則;③ 真庫測試 12 條:子讀上層=1、子改刪上層=0、兄弟互看=0
狀態 ✅ 已修(改刪:CM-2271,1.21.0;讀取:CM-2369,1.21.1)。驗證:子單位改不到也刪不掉母公司的授權與停權紀錄,且讀得到上層授權、功能正常
§6

M03-14 🟡 唯讀角色可以對客戶機器發動掃描、刪掉失敗紀錄

欄位 內容
嚴重度 🟡 中(總表 #37,與 M06-7 合併;OWASP:A01 存取控制失效;主分類為權限提升)
在哪裡 弱點檢測整合模組:任務頁上的掃描操作——開始掃描、取消、單台重跑、單台取消、整組取消、刪單筆、整組刪、立即開始,共八個會改狀態的功能
攻擊面位置 已登入、且是該專案參與者的任何角色,包含產品定義上「只能看、沒有待辦」的唯讀角色。需要知道任務或執行組的編號(在專案頁看得到)
信任邊界位置 API 層 ↔︎ 任務經手人。該守的是「你是不是這張任務的負責人、或這個專案的管理者」;這八支借用的是稽核流程那支「任一專案參與者都放行」的寬鬆守門,只問了「你是不是這個專案的人」
元件端點位置 jedi_detection/api/routing.py:POST /detection-tools/jobs/{job_uid}/execute、/detection-tools/jobs/{job_uid}/cancel、/detection-tools/execution-groups/{group_uid}/assignments/{assignment_uid}/rerun、…/assignments/{assignment_uid}/cancel、…/assignments/{assignment_uid}/start-now、/detection-tools/execution-groups/{group_uid}/cancel、DELETE /detection-tools/execution-groups/{group_uid}、DELETE /detection-tools/executions/{execution_uid};處理在 jedi_detection/app/service/detection_orchestration_service.py,守門呼叫主專案 WorkflowExecutionService.assert_job_operator
駭客怎麼打 ① 以唯讀角色登入,打開專案的弱點檢測任務;② 直接按或直接打「開始掃描」,系統只確認他是專案成員就放行;③ 掃描帶著維運帳密連進客戶的正式機器;④ 接著反覆取消再重派干擾正常檢測,或刪掉失敗的執行紀錄——「這次掃描失敗過」的痕跡被一個不該動手的人抹掉,紀錄上的操作者卻是合法成員
得手什麼 以唯讀身分對客戶正式機器發動帶帳密的掃描,並刪改掃描執行紀錄
修正的做法 ① 稽核流程那邊先新開一支嚴格版守門 assert_workflow_job_operator(只放行任務被指派人與專案 manager,見 M06-7);② 主專案加一層包裝(BE e10d10a83):套件手上只有流程實例編號,新增 assert_job_operator_by_workflow_execution_id 反查後委派嚴格守門,WorkflowExecutionService.assert_job_operator(workflow_execution_id, template_job_id) 提供一行呼叫;查無流程實例回 404,不把「編號打錯」變成「無權限」;③ 套件八處改接(套件 88e519b4):八支寫入功能由 assert_project_participant 換成 assert_job_operator;掃描跑完由代理程式回報觸發的自動完成路徑沒有使用者身分,維持原樣不掛守門
狀態 ✅ 已修(FR-114.1-2,CM-2022,套件 88e519b4+BE e10d10a83,1.21.0 出貨)
§7

M06-7 🟡 只能看的人可以完成或退回別人的稽核任務

欄位 內容
嚴重度 🟡 中(總表 #37,與 M03-14 合併;OWASP:A01 存取控制失效;主分類為權限提升)
在哪裡 稽核流程模組:任務的「完成」與「退回上一關」兩個按鈕
攻擊面位置 已登入、是該專案參與者的任何角色,包含唯讀角色(原本用意是開放給外部顧問或其他單位同事觀看)。需要知道任務編號(專案頁看得到)
信任邊界位置 API 層 ↔︎ 任務經手人。當時的權限政策寫明「任何角色的專案參與者都會通過」,另一處又把這個角色定義成「純瀏覽、沒有待辦」——產品承諾與判斷邏輯互相矛盾,守門只守到「是不是專案的人」
元件端點位置 api/flow_engine/__init__.py:/api/1.0/flow-engine/task/complete/{id}(JobCompleteRoute)、/api/1.0/flow-engine/task/revert/{id}(JobRevertRoute),路由在 api/flow_engine/routes/flow_engine_route.py;處理在 app/flow_engine/service/workflow_execution_service.py 的 complete_job/revert_job,守門在 app/flow_engine/service/job_operator_guard.py 與 common/authz/workflow.py
駭客怎麼打 ① 以唯讀角色登入,打開別人負責的稽核任務;② 按「完成」或「退回」,系統只確認他是專案成員就放行;③ 任務、問卷、流程狀態跟著被推進或退回;④ 稽核紀錄上的經手人變成這個只能看的人——事後看紀錄,分不出是負責人做的決定還是旁觀者動的手
得手什麼 以旁觀者身分結案或退回別人的稽核任務,稽核軌跡上的經手人失真
修正的做法 ① 新開嚴格版守門(FR-114.1-1,BE 18b2ba06c):common/authz/workflow.py 新增 assert_workflow_job_operator,前段政策與既有那支一致,最後多問「你是這張任務的被指派人、還是專案 manager」;不收嚴既有那支,因為佐證文件的增刪改也吃它;完成與退回各改一行呼叫;新增錯誤碼 GRC_NOT_JOB_OPERATOR,與「你不是這個專案的人」分開;② 保留兩條拿掉會壞的旁路:無使用者身分(掃描自動完成)不守、輪次主流程任務沿用已知專案編號;③ 歸屬判斷改走唯一入口(CM-2119,BE e8445ae16):「任務屬於哪個專案」原本查指派表,未指派的任務查不到會被誤擋,改委派全系統唯一的 JobProjectOwnershipQuery(走輪次鏈)
狀態 ✅ 已修(FR-114.1-1,CM-2021,18b2ba06c;誤擋修正 CM-2119,e8445ae16;1.21.0 出貨)。驗證:九種角色組合逐條跑過;HTTP 實打唯讀與管理成員改前 403、改後 200,非成員仍 403
§8

M05-8 🟡 改別人的問卷討論還掛原作者名字,刪除不留痕

欄位 內容
嚴重度 🟡 中(總表 #39;OWASP:A01 存取控制失效、A09 日誌與告警失效;主分類為事後無法追查)
在哪裡 問卷模組:填答頁每題底下的討論串,「編輯」與「刪除」留言
攻擊面位置 已登入、且持有問卷修改或刪除權限的人——一般登入帳號按不動。設計上這群人本來就能改問卷
信任邊界位置 問卷權限 ↔︎ 留言作者本人。改刪問卷的權限只回答「你能不能改問卷」,不回答「這則留言是不是你寫的」;該在服務層動手前比對作者,當時沒有;刪除也直接從資料庫抹掉
元件端點位置 jedi_survey/api/routing.py:PUT /survey-discussion/{uid}、DELETE /survey-discussion/{uid}(SurveyDiscussionRoute,api/routes/survey_discussion_route.py);處理在 jedi_survey/app/service/survey_discussion.py 的 update_discussion/delete_discussion,讀取在 app/service/survey_discussion_service.py
駭客怎麼打 ① 有問卷修改權限的人打開某題討論;② 對別人寫的留言按編輯,把內容改成對自己有利的說法;③ 系統不比對作者就存檔,留言照樣掛著原作者名字;④ 或者直接按刪除,那則留言從資料庫消失、沒有任何紀錄——事後看不出原作者說過什麼、誰改的、誰刪的
得手什麼 竄改或抹除他人在問卷上的討論紀錄,且看起來像原作者自己寫的
修正的做法 ① 改:動手前先載出那則留言比對建立者(不用最後修改者,那會被改留言的人覆寫),不是本人回 403(新錯誤碼 SURVEY_DISCUSSION_NOT_OWNER);② 刪:改成軟刪除——標記「已刪除/誰刪的/何時刪的」,資料列留著,同樣限本人;刪除端點補傳操作者帳號;③ 讀取端加上「未刪除」條件濾掉已刪的(資料庫門禁不看刪除旗標,濾除寫在讀取層);④ 軟刪除欄位由前置卡 CM-2025(BE 4e7754de7)先補上
狀態 ✅ 已修(FR-114.1-4a,CM-2024,套件 jedi-survey 81051e8a,1.21.0 出貨)。管理者代刪他人留言的路徑未做,要做需另開卡
§9

M23-2 🟡 任何員工都能改刪別人的意見回饋並關掉外部問題單

欄位 內容
嚴重度 🟡 中(總表 #45;OWASP:A01 存取控制失效;主分類為竄改資料)
在哪裡 意見回饋模組:回饋清單、明細、新增、修改、刪除、刪附件、匯出七個操作;刪除一筆回饋時會順手關掉外部問題單系統(GitLab/GitHub)上對應的單
攻擊面位置 任何登入帳號,不需要特殊權限,在正常操作畫面上就做得到——這是檢視時唯一一條「一般員工現在就能實際操作利用」的
信任邊界位置 使用者 ↔︎ 服務,以及本系統 ↔︎ 外部問題單系統。七個操作只有匯出掛權限檢查;改刪從不比對「這筆是不是你的」;而刪除會跨出本系統去關單,一旦送出就收不回
元件端點位置 jedi_issue/api/routing.py:POST /feedbacks(清單)、GET/POST/PUT/DELETE /feedback/{uid}、DELETE /feedback/file/{uid}/{file_uid}、POST /feedback/export/{export_type};處理在 jedi_issue/app/feedback/service/feedback_service.py 的 update_feedback/delete_feedback/delete_feedback_file,能力點守門在 jedi_issue/api/guards.py
駭客怎麼打 ① 一般員工登入,打開意見回饋清單看到別人送的回饋;② 對別人的回饋按修改改掉內容,或按刪除;③ 系統不比對歸屬就執行,刪除時還把開發團隊正在處理的那張外部問題單一起關掉;④ 操作日誌記下的是一個合法登入的人做了合法操作,事後看不出這是動了別人的東西
得手什麼 竄改或刪掉任何人的回饋,並關掉外部問題系統上正在處理的單
修正的做法 ① 六支端點補能力點:清單與明細收「管理」或「唯讀檢視」任一顆,新增收建立、改與刪附件收更新、刪除收刪除;守門改成可收多個能力點名稱;② 改、刪、刪附件補作者比對 _assert_owner:比對建立者,不是本人回 403(FEEDBACK_403002),取不到操作者身分一律擋;刪除的比對放在關閉外部問題單之前,非本人時一次外部呼叫都不送;③ 清單加查看範圍(我的/全部):不開第二支入口,「全部」需唯讀檢視能力點,不帶參數依能力點推定;範圍條件用獨立欄位以「且」套用,避免被搜尋條件的「或」群組吃掉變成恆真;④ 主專案接上套件新開的「有沒有這顆能力點」查詢(BE 4b1224c4a)
狀態 ✅ 已修(FR-114.1-7,CM-2030,套件 jedi-issue 23c2ee69,1.21.0 出貨)。驗證:六支端點 × 三種身分 18 組合全對;實查開發環境 14 個角色五顆能力點都有,修後不會誤擋現有使用者
§10

M05-9 🟡 多人同時填問卷時,署名採用畫面送上來的名字

欄位 內容
嚴重度 🟡 中(總表 #85;OWASP:A06 不安全的設計、A08 軟體或資料完整性失效;主分類為事後無法追查)
在哪裡 問卷模組:多人同時線上填答,每次修改即時同步給其他填答者,並記下「這一題是誰改的」
攻擊面位置 已登入、本來就能填這份問卷的合法填答者——這不是越權,問題在紀錄可信度
信任邊界位置 瀏覽器 ↔︎ 即時同步服務。「是誰」應該取伺服器手上的登入身分;當時直接採用瀏覽器送上來的 user 欄位當建立者與修改者
元件端點位置 即時通道 /socket/fill-survey 的 update 事件 → jedi_survey/app/handler/fill_survey_socketio_handler.py 的 on_update
駭客怎麼打 ① 合法填答者打開問卷,進入即時填答;② 修改某一題時,把送出的資料裡「使用者」那一欄改成同事的帳號;③ 伺服器照收,這筆修改記成同事改的,廣播給其他人看到的也是同事的名字;④ 事後追查「誰改了這一題」,指向的是沒做這件事的人
得手什麼 把自己的填答修改署成別人的名字,填答歷程不可信
修正的做法 ① 署名改讀伺服器端的登入身分 get_user_context().login_name(即時通道每個事件都會注入身分,REST 端的修改本來就是這樣取),不再採用畫面送上來的值;② 廣播給其他填答者的使用者欄位跟著變成真實帳號;③ 同批另修同模組資料夾的三處輸入信任問題(總表 #86/#126/#127)
狀態 ✅ 已修(CM-2060,套件 jedi-survey 54ac36f7,1.21.0 出貨)
§11

M09-4 🟡 在欄位裡塞換行,就能往客戶監控系統塞偽造日誌

欄位 內容
嚴重度 🟡 中(總表 #93;OWASP:A09 日誌與告警失效、A05 注入攻擊;主分類為事後無法追查)
在哪裡 系統日誌模組:日誌轉送功能——系統依設定把日誌一行一行即時送到客戶的資安監控系統(syslog)
攻擊面位置 不需要登入。只要有一個使用者填得到、又會被原樣記下來的欄位就行,例如登入時輸入的帳號(登入失敗也會被記下)
信任邊界位置 本系統 ↔︎ 客戶的資安監控系統。進來時照實記是對的(稽核紀錄本該如實);該把關的是交出去的出口——對方以換行切分紀錄,送出前要把換行編碼掉;當時沒有,且負責清理的程式只處理了訊息第一段
元件端點位置 轉送格式化 jedi_api_log/forwarding/common/forwarder.py 的 build_rfc5424_formatter/_escape_control_chars;轉送設定 /log-forwarding(jedi_api_log/forwarding/api/routing.py);可被利用的輸入口例如 POST /login(jedi_iam/api/routing.py)的帳號欄位
駭客怎麼打 ① 不需要帳號,打開登入頁;② 在帳號欄位填「隨便一個名字+換行+一段假日誌」,例如偽造成「某某管理員授予了超級權限」;③ 登入失敗,這個帳號字串被原樣寫進日誌並轉送出去;④ 客戶的監控系統把換行當成下一筆,收到兩筆紀錄,第二筆內容完全由攻擊者決定——事後追查時真假混在一起
得手什麼 在客戶的資安監控系統裡憑空捏造稽核紀錄,混淆事後追查
修正的做法 ① 轉送用的格式化器送出前把換行、歸位、定位與反斜線等控制字元編碼成看得見的轉義字元(\n 變成兩個字元),內容一個位元都不少,多行錯誤訊息仍完整送達、只是排在同一筆裡;② GELF 那條路實測已免疫(JSON 會把換行轉義、訊息邊界是封包),只補註解說明;③ 同批修操作日誌與意見回饋匯出的 Excel 公式中和(M09-2 等)
狀態 ✅ 已修(CM-2055,套件 38de2886,1.21.0 出貨)。驗證:塞換行與定位字元的字串經格式化器輸出零換行、內容完整
§12

M06-19 🟡 甲專案管理人能強制開始乙專案的任務,事件記在甲名下

欄位 內容
嚴重度 🟡 中(總表 #171;OWASP:A01 存取控制失效;主分類為竄改資料)
在哪裡 稽核流程模組:規劃頁上「強制開始」卡在合流的任務,以及查詢任務是否卡住
攻擊面位置 已登入、是某一個專案管理人的人。網址同時帶專案編號與任務編號;資料庫隔離只分客戶不分專案,同一家客戶內換個任務編號就進得去
信任邊界位置 專案 ↔︎ 專案(同一客戶內)。系統只檢查「你在網址上那個專案的身分」,從不核對「這筆任務屬不屬於那個專案」;稽核事件又依網址上的專案記錄
元件端點位置 api/flow_control/__init__.py:GET/POST /api/1.0/grc/project/{project_uid}/job/{job_uid}/force-start(JobForceStartResource,api/flow_control/routes/job_force_start_route.py)、/api/1.0/grc/project/{project_uid}/job/{job_uid}/blocked-status;處理在 app/flow_engine/service/job_force_start_service.py 的 _load;歸屬判斷 infra/readmodel/tasks/job_project_ownership_query.py
駭客怎麼打 ① 甲專案管理人登入,網址填自己的專案;② 任務編號換成乙專案一張卡在合流的任務;③ 按「強制開始」,系統只確認他是甲的管理人就推進;④ 乙的主管收到通知、但兩條分支的證據其實沒收齊;稽核事件記在甲專案名下,事後查乙的紀錄看不出是誰、從哪裡推的
得手什麼 推進別人專案的任務,且稽核事件記錯專案、追不到
修正的做法 ① 建立全系統唯一的「任務屬於哪個專案」判斷 JobProjectOwnershipQuery:先走控制項對應→輪次→專案,再走輪次主流程→專案,查無回空、呼叫端回 404;不再查指派表(開發環境 99.7% 的任務沒有指派紀錄,用它會誤擋);② 強制開始與查卡住共用的 _load:查到任務後比對網址上的專案與任務真正歸屬,不符或查無回「找不到任務」;③ 查卡住狀態另補專案成員檢查(CM-2148,a3fe63b03)
狀態 ✅ 已修,1.21.0 出貨(CM-2113,BE commit f52a53f1e)。驗證:別專案或查無一律 404、同專案照舊;未指派與輪次主流程任務 HTTP 實打 200
§13

M09-6 🟡 把網址加長,這次操作就不會出現在操作日誌

欄位 內容
嚴重度 🟡 中(總表 #174;OWASP:A09 日誌與告警失效;主分類為事後無法追查)
在哪裡 系統日誌模組:每一次 API 請求都會寫一筆操作日誌(平台管理員事後查詢用);日誌表「網址」欄最多 500 字
攻擊面位置 任何已登入的人,打任何一支 API 都行。網址的查詢參數由呼叫端自由決定長度
信任邊界位置 使用者請求 ↔︎ 操作日誌。外部送來的值寫進有長度上限的欄位前應先截斷;當時整串直接寫入,超長就整筆寫入失敗,而寫日誌失敗被設計成「不拖垮請求」,只印一行錯誤、請求照常執行
元件端點位置 所有 API(/api/1.0/*)共用的攔截器 common/middleware/app_mw.py 的 init_app_interceptor(before_request/after_request/teardown_request),欄位上限表 _API_LOG_COLUMN_LIMITS、截斷 _clip、替代紀錄 _add_fallback_api_log;寫入 jedi-log 的 ApiLog 表;查詢頁 /log/api-logs
駭客怎麼打 ① 已登入的人準備刪資料或改設定;② 在網址後面加一段無意義的查詢參數,把整串網址撐到超過 500 字;③ 請求照常執行,資料真的被刪、設定真的被改;④ 寫操作日誌時因網址太長整筆失敗,平台管理員事後查操作日誌,這次操作就像沒發生過
得手什麼 刪資料、改設定而不在操作日誌留下紀錄
修正的做法 ① 網址、來源 IP、瀏覽器資訊、使用者名稱等欄位寫入前一律截到欄位上限(對照表 _API_LOG_COLUMN_LIMITS);② 寫入仍失敗時補一筆最小替代紀錄(方法、路徑、請求編號、標註「寫入失敗」),替代也失敗才只印錯誤,兩者都不拖垮業務請求;③ 網址上的查詢參數改走密碼遮蔽,access_token 之類不再明文落地;④ 同批:請求與回應內容遮罩前先截到 64KB,修掉未登入就能卡死伺服器的遮罩問題(總表另一項)
狀態 ✅ 已修(FR-114 CM-2171,BE commit 908f9dd0e,1.21.0 出貨)。驗證:800 字網址的請求成功且操作日誌有這筆、網址長 500
§14

M06-12 ⚪ 推進或退回階段時,顯示的操作者名稱由呼叫端自填

欄位 內容
嚴重度 ⚪ 低(總表 #128;OWASP:A06 不安全的設計、A08 軟體或資料完整性失效;主分類為事後無法追查)
在哪裡 稽核流程模組:稽核輪次的「推進階段」與「退回階段」;操作者名稱顯示在階段歷程時間軸與流程圖留言的作者欄
攻擊面位置 已登入、且有推進這個階段權限的人(等於自己人偽造顯示名稱)
信任邊界位置 呼叫端送來的內容 ↔︎ 伺服器寫入的歷程。代表身分的帳號欄位由伺服器填、不受影響;但顯示名稱用「呼叫端沒填才由伺服器補」的寫法,呼叫端搶先填了就照收
元件端點位置 api/flow_engine/__init__.py:POST /api/1.0/project/{project_uid}/audit-round/{round_uid}/stage/advance(StageAdvanceResource,api/flow_engine/routes/stage_advance_route.py)、POST /api/1.0/project/{project_uid}/audit-round/{round_uid}/stage/rollback(StageRollbackResource,stage_rollback_route.py);內容白名單 api/flow_engine/serializers/stage_advance.py 的 strip_unknown_ctx
駭客怎麼打 ① 有推進權限的人在輪次頁按「推進」或「退回」;② 送出的請求裡多帶一個顯示名稱,填成另一位同事;③ 伺服器看到已經有值就不覆寫;④ 階段歷程與留言作者欄顯示的是那位同事——帳號欄位仍是真人,但看畫面的人會被誤導
得手什麼 階段歷程與流程圖留言上的操作者顯示名稱被偽造
修正的做法 ① 兩支入口都改成直接由伺服器指派登入者的顯示名稱,不再「沒填才補」;② 新增 strip_unknown_ctx 白名單,請求內容只放行處理端真的會讀的欄位,其餘丟掉;兩支路由共用同一支,避免兩個入口各長一套
狀態 ✅ 已修(CM-2059,BE commit 8edbaf944,1.21.0 出貨)
§15

M09-7 ⚪ 操作日誌的來源 IP 全記成前端代理伺服器

欄位 內容
嚴重度 ⚪ 低(總表 #185;OWASP:A09 日誌與告警失效;主分類為事後無法追查)
在哪裡 系統日誌模組:操作日誌的「來源 IP」欄;另有登入流程(人機驗證與登入防護會用到來源 IP)讀 IP 的寫法不一致
攻擊面位置 這條本身不是攻擊造成,是任何操作者都享有的匿名性:後端記到的是直接連進來的前端代理伺服器。登入那邊另一個問題則是不需要登入——呼叫端可自帶轉發標頭冒充 IP
信任邊界位置 前端代理伺服器(nginx)↔︎ 後端。真實客戶端 IP 只能由最外層那一跳代理告訴後端;當時操作日誌完全沒採信代理轉來的值,登入卻反過來無條件相信任何人都能自填的轉發標頭
元件端點位置 core/app_factory.py 的 create_app(掛 ProxyFix(x_for=1));操作日誌寫入 common/middleware/app_mw.py(source_ip=request.remote_addr);登入取 IP jedi_iam/api/routes/login_route.py 的 _client_ip(POST /login)
駭客怎麼打 ① 有人用合法帳號做了刪改操作;② 操作日誌照記,但來源 IP 是前端代理的內部位址(STG 22.8 萬筆全是同兩個位址);③ 出事時要查「是哪台電腦」,日誌給不出答案;④ 若天真地改讀轉發標頭,攻擊者只要在請求裡自帶一個假 IP 就能栽贓別台電腦——登入那邊當時就是這樣
得手什麼 操作出事後查不出來源電腦,或在登入紀錄上冒用別人的 IP
修正的做法 ① 後端只相信 nginx 那一跳:create_app 掛 ProxyFix(x_for=1),之後一律讀 request.remote_addr,掛在 app 建立處讓 API、即時通道與測試三種模式都吃得到;動手前查過安裝版只有 nginx 對外開埠,信一層正確;② 操作日誌改記 remote_addr;③ 套件登入的 _client_ip 不再先讀轉發標頭,只回 remote_addr;④ 防竄改旁證同步改(見 M08-7)——三處讀 IP 的地方統一
狀態 ✅ 已修(FR-114 CM-2171,BE commit 908f9dd0e,套件 jedi-iam commit 960d737e,1.21.0 出貨)。打折:本機無 nginx,「經 nginx 記到真實客戶端 IP」未實測;直連帶假標頭不被信只在 API 埠不對外時成立(安裝版即如此)
§16

M12-6 ⚪ 專案經理能改寫、再刪掉稽核人員寫的改善建議

欄位 內容
嚴重度 ⚪ 低(總表 #189;OWASP:A01 存取控制失效;主分類為竄改資料)
在哪裡 稽核輪次管理模組:輪次進入「整改中」後,稽核人員在每個風險底下留一筆「改善建議」,系統規定它不能刪
攻擊面位置 已登入、是該專案經理、且輪次在整改中——門檻高。專案經理是受稽核的一方
信任邊界位置 受稽核方 ↔︎ 稽核方。改善建議是稽核方的紀錄,受稽核方不該碰;「新增整改計畫」這支會把帶既有編號的請求當成更新,沒區分那一筆是誰的、是什麼類型
元件端點位置 api/project/__init__.py:POST /api/1.0/audit-round/{round_uid}/ar/risk/{risk_uid}/remediations(RiskRemediationsRoute,api/project/routes/audit_round_route.py)、DELETE /api/1.0/audit-round/{round_uid}/remediation/{remediation_uid}(PoamRemediationRoute);請求格式 api/project/serializers/audit_round.py 的 CreateRemediationRequest;處理在套件 jedi_compliance_audit/app/service/poam_app_service.py 的 add_remediation
駭客怎麼打 ① 專案經理在整改中的輪次拿到稽核人員那筆改善建議的編號;② 用「新增整改計畫」送出,編號填那筆建議;③ 系統當成更新,把建議改寫成一般整改計畫、內容換成經理想要的;④ 類型一變,「建議不能刪」的檢查就不成立,接著整筆刪掉——事後看不出稽核人員原本寫了什麼
得手什麼 受稽核方改掉並刪除稽核方的建議,破壞稽核獨立性與佐證軌跡
修正的做法 ① 套件:add_remediation 對類型是「建議」的一律拒絕新建(409 GRC_POAM_RECOMMENDATION_PROTECTED),帶編號且命中既有建議時拒絕覆蓋——建議只能由稽核階段建風險時寫入;② 主專案:請求格式的 lifecycle 欄只收「planned/completed」,「recommendation」直接 400;③ 刪除端原有的建議保護維持,覆蓋路徑堵住後就繞不過去
狀態 ✅ 已修(FR-114 CM-2174,BE a8f1a24b7/套件 b7022b8b,1.21.0 出貨)。驗證:經理以建議編號覆蓋回 409、新建建議類型回 400、刪建議 409、建議內容未被改;正常的整改計畫新增修改刪除照常
§17

M08-7 ⚪ 防竄改旁證裡的來源 IP 讀的是用戶端可填的欄位

欄位 內容
嚴重度 ⚪ 低(總表 #233;OWASP:A09 日誌與告警失效;主分類為事後無法追查)
在哪裡 防竄改檢查模組:敏感操作即時驗證抓到竄改時,會記下一份「旁證」(當下是誰、從哪台電腦、打哪支功能)供事後追查
攻擊面位置 觸發即時驗證的那個請求的發送者。轉發標頭任何呼叫端都能自己帶
信任邊界位置 呼叫端 ↔︎ 後端的連線資訊。來源 IP 只能信最外層代理那一跳;當時旁證直接讀 X-Forwarded-For,等於讓被追查的人自己填「我從哪來」
元件端點位置 common/integrity/adapters.py 的 _current_request_context(旁證的 client.source_ip),由 collect_host_trigger_context/hotpath_verify 呼叫;IP 來源由 core/app_factory.py 的 ProxyFix(x_for=1) 決定
駭客怎麼打 ① 已在主機上動過手腳的人,接著從系統做一個會觸發即時驗證的敏感操作;② 請求裡自帶一個假的轉發標頭,填成別台電腦的 IP;③ 系統偵測到竄改、記下旁證,來源 IP 寫的是那個假值;④ 事後追查指向無辜的電腦(不影響防竄改本身擋不擋得住)
得手什麼 竄改事件旁證裡的來源 IP 失真,追查被誤導
修正的做法 ① 拿掉旁證自讀 X-Forwarded-For 的寫法,改讀 request.remote_addr;② 真實客戶端 IP 由 app 統一掛的 ProxyFix(x_for=1) 放進 remote_addr(只信 nginx 一跳),與操作日誌、登入三處一致(見 M09-7)
狀態 ✅ 已修,1.21.0 出貨(FR-114 CM-2171,BE commit 908f9dd0e)。這條是主系統掃描查出的