今天要討論的主題是 Agent 對於使用者的記憶能力,以及攻擊者除了昨天提到的IPI(間接提示詞注入攻擊)外,如何利用 Agent 的記憶機制進行更隱蔽的攻擊。這種攻擊方式有個名字叫記憶汙染(memory poisoning)。
關於記憶這個名詞,我們在 AI 與 LLM 的世界一般而言會區別成大致兩類:
上下文視窗(context window):
也就是模型每次生成內容時,能夠讀得到的所有文字內容:包含系統提示、你的提示詞、工具回傳的結果。上下文視窗的記憶是短期的,一旦這次的對話結束、視窗清空,記憶內容就不在了。間接注入通常都是針對上下文視窗的機制來攻擊的。
長期記憶(long-term memory):
這是一個獨立在模型之外的儲存空間,agent 會跨對話、跨會話(通常就是我們每次聊天時開啟的新聊天室)反覆讀取和寫入。也就是說 Agent 需要記下來的資料並不是僅有一次性的輸入,而是需要記住更多之後還要拿出來用的可變狀態與使用者需求,例如:使用者的偏好與情境、過往的對話摘要、多個任務的目標,有時還包含權限設定與使用者的操作習慣。值得一提的是,長期記憶在執行期間是持續寫入新的記憶資料的,agent 會一邊做事,一邊把它認為值得記住的內容存進去。
而長期記憶常見的實作方式有兩種。
一種是建立滾動摘要:
把過去發生的事濃縮成一份筆記,隨著對話持續往後追加,例如我們操作時會叫 AI 持續更新 記憶.md 檔案。
一種是建立向量資料庫,並搭配檢索增強生成(RAG,Retrieval-Augmented Generation):
系統會把每一段要記住的文字拆分成小部分,各自轉換成一串向量數據並儲存起來,需要時再依指示詞的要求或系統判斷,將與需求資料相關性強的向量數據檢索並回傳,並提供給上下文。
通常以這樣的形式, 使用者可以更快速與有效率的讓 AI 找到符合指示的資料並根據需求進行更精確的動作決策與資料引用,不需要每次都花大量時間與Token重新指導 AI 。
其實這概念就跟交代公司中的新手與老鳥做事一樣,交代新手做事要教很久還不一定做的好,但老鳥只要給的大概的方向,他就能快速又正確的依循工作慣例把任務完成得七七八八了。因此,長期記憶的 RAG 資料庫的價值就在這。
記憶汙染,指的是攻擊者設法讓自己提供資料及內容進入 agent 的持久記憶或知識庫中,使 agent 在後續因特定需求而去調用記憶時,攻擊者埋藏在記憶中的惡意內容將會有概率地被檢索、參考、使用。
攻擊者的目的是讓惡意內容被當成 Agent 可信任的參考資料或記憶規範,進而左右模型的回答甚至行動。而且這種透過汙染記憶使 Agent 行為偏差的攻擊手法,是可以分屬不同的會話、不同的使用者、不同的時機點的。
IPI 攻擊是即時的:因為惡意內容的被讀取與被執行都在同一次對話的 session 裡。
記憶汙染比較接近先埋個不定時炸彈,等到攻擊者誘導特定檢索條件時,才會被引爆並導致 Agent 異常的威脅手法,這種潛伏式的攻擊相對難以被 Agent 主動偵測到,且是有辦法透過時間與不斷嘗試,在不經意間使 Agent 建立有偏差性的認知及行為。
接下來我用兩個經過同儕審查、而且攻擊目標不同的實際研究,把「它到底怎麼成立」拆給你看:一個叫 PoisonedRAG,攻擊的是知識庫的答案;一個叫 AgentPoison,攻擊的是 agent 的行動。
PoisonedRAG 攻擊成立的判定條件是:
檢索條件:
攻擊者提供的汙染內容在使用者問出目標問題時,必須真的被檢索系統挑中並送進模型的上下文。
生成條件:
攻擊者提供的汙染內容一旦被送進上下文、被模型讀取,必須真的能誘導模型輸出攻擊者要的答案。
PoisonedRAG 的核心手法,就是針對這兩道門檻,把每段汙染內容拆分成兩個部分來寫,這裡把它叫做 S 和 I。
S 段落負責達成被優先檢索的條件:它要讓整段汙染內容的向量,盡量貼近目標問題,好在相關性最強的檢索條件中勝出。I 段落負責達成引誘 Agent 生成需求結果的條件:它是一段捏造得像模像樣的內容,功能是盡可能的包裝攻擊者指定的惡意答案,引誘模型參考與執行。
S 在黑箱情境最直接的做法是把目標問題本身的字句直接放進 S 段落本身:問題和它自己當然最像,相似度自然就高。
研究案例顯示:在一個有 268 萬筆正常文本的知識庫裡,**針對一個目標問題,只要塞進 5 筆這樣的汙染文本,達成目標的 ASR 最高可以到 97 % ** 。攻擊者不必把惡意內容填充滿整個資料庫,只要讓自己那幾筆惡意資料,對於「目標問題」相關性能擠進前幾名的優先檢索名額即可。
剛剛講的 PoisonedRAG 是針對 RAG 機制設計的「特定問題」的攻擊研究。
AgentPoison 則是更進一步地往兩個面向延伸討論:它將遇到的問題建立成後門(Backdoor);而且它影響是 agent 的實際行為。
所謂後門,是攻擊者在系統裡預埋一個隱藏的惡意行為,這個行為平常不會出現,只有在輸入裡出現某個攻擊者事先選定的特定訊號時,才會被啟動。
這個訊號就叫觸發字串(trigger):通常是一段特定的字詞或符號組合,作用像一把鑰匙。輸入中帶著這段關鍵字詞,惡意行為就被啟用。
後門的隱蔽性正來自這裡:只有攻擊者想發動攻擊時,才把觸發字串寫進輸入裡。
在 AgentPoison 裡,觸發字串要同時顧及兩個範圍都能符合觸發條件。
攻擊者植入記憶庫的汙染資料:
這些資料是所謂的惡意範例,由三個部分組成:一個看似正常的指示詞、觸發字串、以及攻擊者指定的任務。agent 運作時本來就會參考記憶裡「過去類似情境是怎麼處理的」示範,所以只要這則惡意範例被檢索回來,它等於在教模型:碰到這種情況,就照範例去處理。
觸發詞的必要性:
攻擊者需要把同一段觸發字串放進 agent 當下要處理的輸入裡(例如夾在指令中,或夾在它會讀取到的環境內容裡),好讓這次檢索範圍正確的將汙染資料納入 RAG 中。
擬定觸發字串的技術核心,不是人類純粹手刻代碼,而是透過解有限制的最佳化問題而算出來的。用的是逐步、梯度引導的離散 token 搜尋(在一個個候選 token 裡,挑出能讓目標函數符合預期方向的token,並頂替至優先序列上)。
計算這個目標函數要同時滿足四個條件,且剛好與先前提及的檢索條件和生成條件相關:
四項湊在一起,效果是:只有帶著那段觸發字串的查詢會中招,其餘正常查詢完全不受影響。
這項研究被拿去應用在三個測試場景:
而這三個場景實際應用的汙染資料僅佔整個資料庫不到 0.1%,帶觸發字串的查詢有 80% 以上能成功把毒示範檢索回來,端到端(檢索成功、而且真的做出目標行動)的成功率約 63%,對正常任務的表現則掉不到 1%;少到只放 1 筆、用單一 token 當觸發字串,都還可能奏效。
主要有兩種路徑:
第一種是直接寫入資料來源:
RAG 的知識庫,內容常常來自網頁、論壇、共筆這類開放、可被外人貢獻的來源。這正是 PoisonedRAG 的威脅情境——攻擊者提供有汙染內容的資料貢獻,只要有人使用開源知識庫就有一定機率中招。
第二種是間接的自我汙染。
當 agent 讀取外部內容(例如評論、信箱、網頁),也有可能為了日後記得而把讀取到的資料摘要,並寫進自己的長期記憶。如果那段內容夾帶了惡意指令,寫進去的就是一筆汙染記憶。
完整的防禦想法可能等之後會再找機會多補充一點,這邊先下點今天的小結論。
簡單來說,檢索條件要補足的部分是讓 Agent 檢索時不再只仰賴唯一的向量距離:在寫入記憶時就驗證並簽章來源、讓來源標記能一路保留到檢索之後、按使用者與會話把記憶隔離開來(免得一個人的毒波及到另一個人)。而生成條件需要補足的部分就是不要讓「檢索回來的記憶」直接驅動高風險行動,一定要重新稽核過。
感謝大家今日份的閱讀,我們明天見。
參考資料