昨天那個本機模型表現得很難看。編號幾乎全錯,五條能核對的候選裡只有一條行號對、而那一條把原因講錯了,乾淨的檔案上還亂報一條。
那個模型是 Cisco 放出來的 Antares-1B。要跟著跑要 Apple Silicon 的 Mac,還要先在模型頁上填一份表,兩件事都在昨天那篇裡。今天的東西不跑也讀得懂,但先講清楚,免得你讀到最後才發現機器不行。
我昨天查到它的論文之後才知道,昨天那樣用根本不在它的評測範圍裡,那次難看不能拿來斷定它本來的能力。它練的是「給一個類別,去幾百個檔案裡挑出值得看的」,我卻拿著它逐檔問「這個檔有什麼問題」。
今天把問題往它練過的方向靠:給它一個 CWE,讓它挑檔案。
第 19 天我修過一個命令注入。那天的結論是:模型改完之後我能證明的只有兩件事,我那組攻擊輸入失效了,而且功能還符合我當時訂的判準。除此之外都沒證明。
修完之後有件事一直卡著:同一類的洞,這個專案裡還有沒有別的?
我沒辦法每個檔案重讀一遍。這類工作就是昨天那個模型設計來做的。
命令注入這一類的編號是 CWE-78。我把編號、它的官方描述,加上專案裡七個檔的完整內容一起丟給它,然後只問一句:這幾個檔裡,哪幾個有 CWE-78?
七個檔加起來四千多個字元,這個大小一次塞得進去。它官方的用法是給一個 CWE、讓它自己下指令查看整個程式碼庫,我這個玩具專案小到可以整包貼進去,省掉那一層。所以這裡量的是「照它練過的方向問,會不會找對」,不是它在論文那套流程下的成績。在我那台 M4 Pro 上跑了五秒,交出來的是這樣:
I'll scan the repository files for OS command usage. The relevant file is
server/tools.js which uses child_process.exec to run system commands. I'll
confirm CWE-78 by checking if externally influenced input (like domains or
filenames) is interpolated into the command without sanitization.
</think>
server/tools.js
一行,一個檔,就是那個檔。
昨天它把 innerHTML 的行號指到第 4 行、把路徑穿越掛上點擊劫持的編號。今天沒換權重、沒換機器、檔案也是那七個,只換了問法,就乾淨成這樣。
我又問了另外三類。路徑穿越、跨站腳本、金鑰寫死在程式碼裡,三次都只交出一個檔,三次都對。
我埋了六類問題,它指到四類,那四次都只交出一個檔而且就是對的,沒有多報。 這個四是六裡面的四,不是十三題的正確率,後面會把另外七題攤開。
今天數的單位是「類」不是「處」:同一類就算有兩處也只算一次。等一下打開 tools.js 你會看到這個差別。
沒指到的兩類值得記一下。查訂單沒綁使用者那一條,它指了另一個檔,把那個檔的路徑穿越重新解釋成授權繞過。錯誤訊息外洩那一條更有意思,它開頭就寫對了:
--- server/files.js ---
This endpoint reads files from UPLOAD_DIR and returns their contents. If an
error occurs while reading a file, it's caught and the error message is sent
in the response. This is a CWE-209 candidate.
然後它繼續往下逐檔比較,最後結論改成另外兩個檔。十三份輸出裡只有這一次它自己推翻自己。
回到 CWE-78。它說 server/tools.js 要看,我打開了。
裡面有兩個 exec,不是一個。一個是模型呼叫工具時自己填的網域名,一個是使用者上傳的檔名。模型只說「這個檔要看」,它沒說有幾處。
然後我寫了支腳本真的打一次,兩個入口各一發。它會先用良性參數打一次,確認兩個版本都真的把指令送出去、原本功能還在;哪個版本的良性那發沒送出去,就回報沒有結論,不會把「功能根本沒執行」誤算成攻擊被擋下。攻擊那發的判準是檔案系統上有沒有生出一個標記檔:
=== 有洞的那份(playground/server/tools.js 現在的樣子)===
lookupDomain 打進去了
makeThumbnail 打進去了
=== 換成 execFile 之後 ===
lookupDomain 沒打進去
makeThumbnail 沒打進去
到這裡才算數。模型給的是「這個檔可能有問題」,打完之後我手上的是「這兩個入口真的能被打進來」。
這支腳本的判準我寫錯過一次。第一版比對「輸出裡有沒有出現 payload 那串字」,結果修好的版本也判成打進去了:dig 解不出那個網域的時候,會把整串參數原樣印回錯誤訊息裡。
payload 出現在輸出裡,只證明有人把那串字印出來了,證不出它有沒有同時被 shell 當成指令跑掉。 那個判準從頭到尾就分不開這兩件事。改成看檔案系統上有沒有多一個檔,才有得分。
第 21 天講回歸測試的時候我寫過,判準要看行為,不要看程式碼長什麼樣。今天我自己違反了一次,而且是在最後那一步。
server/tools.js 那兩處確認為真問題,修法就是第 19 天那個:走 execFile,把參數分開交出去。差別在中間那層有沒有 shell。字串交給 shell,分號、反引號、$() 都會被它重新解讀成另一條指令;execFile 預設不經過 shell,那些符號原樣送到目標程式手上。
這不是萬靈丹:目標程式還是可能把你傳過去的東西讀成命令列選項,所以格式跟參數位置還是要管。
我沒有真的去改 playground/server/tools.js,因為那份是這個系列的靶場,故意留著洞給前面幾天掃,改掉會把答案卷弄壞。真正的防禦是改用 execFile 這件事本身,靶場沒有套用它。剛才那支確認腳本擋不了攻擊,它做的是另一件事:拿修好的 execFile 版跟現在這份有洞的版各打一次,哪天修好的又被改回去經過 shell,這支就立刻紅。這就是回歸紀律的行為級版本,守的是修法有沒有退步,不是某一個檔。
換到你自己的專案,這一步才走得完:把 execFile 這個修法套進真的那支,再把這支確認腳本收進你的回歸清單,讓同一個洞之後被改壞就擋得下來。這兩處沒有補進第 24 天那份十六列的清單,是因為那份的對象是客服 bot,範例專案是另一個目標,混進同一個分母就講不清楚了。
上面這一輪是我真的把洞打過、修過。但我會打開 tools.js,是因為模型指了它。那模型這一步到底能信多少?接下來幾組測試就在回答這件事,從最直接的懷疑開始。
那個檔叫 tools.js。我問命令注入,它答一個叫 tools 的檔,這件事有另一個解釋:它讀的可能不是內容,是名字。
這個懷疑很好排除,跑一次就知道。我把七個檔名全換成 m1.js 到 m7.js,內容一個字不動,同樣的問題再問一次。
它答 server/m4.js,那就是 tools.js。路徑穿越跟跨站腳本那兩條也一樣,換完名字指的還是那三個檔。
改完名字答案沒變,所以這三題不支持「它只是認得真實檔名」這個解釋。 至於它讀懂多少,這一組量不出來:它可能靠的是檔案順序、某幾個特徵字,或別的表面模式。
這一組花不到兩分鐘,但少了它,上面那個成績就有兩種解釋,而我沒辦法排除比較難看的那一種。
我一開始其實省事過,只給它七個檔名的清單、不給內容,想看它能不能先幫我圈範圍。真名那次答對了,名字換掉那次它換一個檔答,一樣有自信,也就是說那一輪它從頭到尾都在猜名字。要它讀內容,就得把內容給它。
昨天從論文查到的那件事,一直放在我腦子裡:官方跑分的 Phase A,每一題都保證有洞。而「拿修補過的程式碼,要模型說出這裡沒問題」的 Phase B,論文寫著沒有評測。
The experiments in this report focus on Phase A localization; Phase B is included in the benchmark specification but is not evaluated here.
論文沒有評測這一段,所以我自己量。
我另外挑了七個這個專案裡根本沒有的類別一起問。有幾個名字不常見,一句話帶過:SQL 注入、反序列化(把外面來的資料還原成物件,過程中被塞進不該執行的東西)、XXE(解析 XML 的時候被騙去讀本機檔案)、弱加密演算法、整數溢位、正規表示式效能(一個字串讓比對卡住不動)、亂數不夠隨機。
判這七類「不在」是我親手做的,而且每一條都得說得出查了什麼,不然這個實驗就是我挑七個容易讓它出錯的題目。七個檔一共一百多行,我一條一條掃過:
| 類別 | 我查了什麼 |
|---|---|
| SQL 注入 | 唯一那行查詢的值走問號佔位符,表名、欄名、排序方式都是寫死的字串 |
| 反序列化 | 外部資料從 req.params 與 req.body 進來,沒有 req.query、標頭或 Cookie;兩個入口都沒有再交給 eval、Function、vm 或任何能還原成物件的東西 |
| XXE | 沒有 XML 輸入,也沒有任何 XML 剖析器被 require 進來 |
| 弱加密 | 全專案零個加密相關字眼,沒有套件也沒有自己刻的雜湊 |
| 整數溢位 | 數值運算只有健康檢查那一行的相減與 Math.floor,沒有位元運算、沒有 TypedArray、沒有固定寬度整數轉換 |
| 正規表示式效能 | 零個正規表示式:沒有字面量、沒有 new RegExp、match/replace/test 一個都沒有 |
| 亂數不夠隨機 | 沒有亂數來源,也沒有拿可預測的值去產生任何權杖 |
這張表是我的判準,不是模型的。撐不住的話,「七類裡亂指五類」就只是我挑了七個容易出錯的題目。
七類問完,它有兩次說了沒有,另外五次都交出一個檔。說沒有那兩次是反序列化跟正規表示式效能。

圖右邊那兩格是同一個問法的兩種下場,一種是真的找對檔案,一種是憑空亂報,而輸出的長相沒有可以分辨的差別。
它不是隨便挑一個。整數溢位那次,它指了健康檢查那個端點:
The server files use file reads and compute uptime in server/health.js using
(Date.now() - startedAt) / 1000; that subtraction could overflow if done with
32-bit integers, leading to wraparound.
那個檔是我故意留乾淨的兩個之一,而 JavaScript 的 number 是雙精度浮點數,那裡沒有 32 位元整數可以繞回。它拿一個不存在的前提,推出了一段看起來很合理的話。
XXE 那次,它說 HTML 標籤看起來像 XML,而整個專案沒有一行 XML。弱加密那次,它自己先列了一串要找的東西,MD5、SHA-1、DES。那些東西專案裡一個都沒有,而它還是把 tools.js 包在一個沒收尾的程式碼區塊裡交出來了。
有兩條類別描述我一開始是憑印象寫的,跟 CWE 官網那段不逐字相同。改成官方原文重跑,其中一條的答案就翻了。正規表示式效能那條,用我自己寫的版本時它指了 tools.js:
The `dig` command is part of the `dig` tool, which is a DNS client. DNS tools
often use regular expressions internally.
它推論的是 dig 這支外部程式的內部實作,不是我給它的程式碼。換成官方那一句之後,程式碼沒動、權重沒動、--temp 0 也沒動,它改成回答沒有。
兩輪的存檔我都留著,逐檔比過:十三條裡只有改過描述的那兩條變了,另外十一條逐字相同。
我只動了那兩段描述,十一份輸出一個位元都沒變。這一輪顯示描述文字的改動可能改變答案,但還不足以證明措辭是唯一的原因,我只重跑了一輪,沒有做到能排除別的因素。所以上面那個「亂指五類」我不會寫成這個模型的錯誤率,它是我這樣問、它這樣答的一次紀錄。
現在那十三條描述全部逐字取自官方頁,另有腳本比對它們跟官方原文一不一致,不然我下次換個講法,數字又會自己動。
這一節的東西是數出來的,不是我猜模型腦袋裡在幹嘛。十三個問題形狀都一樣:這幾個檔裡,哪幾個有這一類問題?答案可以是零個。
十三次裡有十一次,回來的是一個檔名。只有反序列化跟正規表示式效能那兩次,它回來的是「沒有」。
那十一次裡有四次是對的,五次那一類根本不在這個專案裡,另外兩次指錯了檔。這三種在輸出上長得一樣:五秒、一行檔名、一段寫得很像回事的理由。
多數人這時候會想改提示,加一句「如果沒有就一定要說 NONE」。我的提示裡本來就寫著這句,逐字是 If none of them do, output exactly: NONE。那兩次它答的是散文,NONE 這個字十三次一次都沒出現過。
那句話已經在提示裡了,再加一次一樣的指令,沒有理由期待結果會不一樣。
我要的是一個乾脆的答案,而論文沒有公布 Phase B 的結果,所以從論文看不出它碰到「這一類不存在」會怎麼答。我這七題也不是 Phase B(那是拿修補過的程式碼問同一個洞還在不在),是我自己在同一個缺口上做的小規模測試。
我也找過有沒有便宜一點的訊號,可以不開檔就先篩掉幾條。最像的一個是「它有沒有好好收尾」,因為答壞的那幾次看起來都比較囉嗦。把十三次列在一起數:有好好收尾的七次裡對三次,沒收尾的六次裡也對三次。
這個特徵分不出答案對錯,我也沒有再試別的訊號,所以只能講到這裡:它交出來的檔名只是候選,不是結論,下一步還是要人讀那段程式碼、跑靜態分析,或者像前面那樣真的打一次。
十三個類別、十三份原始輸出,加上一張我親手核的對照表。每一列都附一句逐字出自存檔的依據,憑印象寫「它好像有講到」過不了那支檢查腳本。
這三份是前面累積下來的:該被擋下的攻擊、應該放行的正常請求、修過的洞不准回來。今天都沒動,理由在上一段。
今天長的是另一份:十三列的候選標注表。四列指對檔案、兩列指錯檔案、兩列在那一類不存在時正確答了沒有、五列在那一類不存在時照樣指一個檔。
今天能確定的是一件事:模型指的檔案只是候選,我親手打過那一次,它才從待辦變成確認。這條路走完了。
不過這兩篇的數字底下有個前提我還沒顧好。它們是某一份權重、某一組量化參數、某一版提示、某一支腳本跑出來的。現在我記得是哪一版,三週後不會。到時候有人問「這張表還算數嗎」,我答不出當初拿什麼跑,這張表就不能重驗,不能重驗的東西就不能當證據。明天把這些條件記成一份對得起來的清單,讓每個數字都查得回它是怎麼來的。
上一篇:Day 26|候選產生器不是漏洞掃描器:它給你的是線索,不是答案
這篇引到的來源:Antares 論文 arXiv:2608.02407(Phase A 與 Phase B 那句)|內文引到的三頁 CWE-78、CWE-190、CWE-1333;丟給模型的十三類描述全部逐字取自官方頁,快照存在 recipe 的 cwe-pages/