事件經過
Moltbook 是一個由 AI agent 發文、留言與投票的社群網站,在數天之內獲得 AI 社群的廣泛關注。該平台的創辦人曾公開表示,這個網站不是靠手寫程式做出來的:「我沒有為 @moltbook 寫過任何一行程式碼。我只是對技術架構有個想法,然後 AI 把它實現了。」
資安公司 Wiz 以一般訪客的身分瀏覽網站進行檢視。幾分鐘之內,就在網站送給每一個瀏覽器的網頁檔案裡,找到一把資料庫金鑰。拿這把金鑰去對資料庫發問,資料庫像對待管理員一樣回應,交出了已註冊 agent 的登入金鑰。Wiz 指出,共有 150 萬枚這類金鑰遭到暴露。
研究人員從這裡繼續往下,盤點出各張資料表,數出約 475 萬筆紀錄。其中一張資料表存放 17,000+ 名受影響帳號擁有者的個人資料。另一張存放為了一項即將推出的產品所蒐集的 29,631 組電子郵件地址,合計 46,631+ 組地址;該報告開頭的摘要所給的電子郵件總數較低,與它自己逐張列出的資料表對不上。再一張資料表存放 4,060 組私人對話,其中有些帶著通往其他付費服務的金鑰。網站對外宣稱有 150 萬個註冊 agent,對應 17,000 名人類擁有者,比例為 88:1。
Wiz 當晚以私訊聯絡維護者。第一次修補關上了存放 agent 金鑰、擁有者資料與網站管理員的資料表;接著的修補涵蓋訊息、通知、投票與追蹤。研究人員隨後發現「修改內容」這條路仍然開著,便編輯一則貼文作為測試,再一次修補才擋下修改。之後又浮現更多暴露的資料表,最後一次修補在初次聯絡後數小時內把它們全部關上。團隊在幾小時後刪除了測試內容,並向研究人員致謝。
Wiz 表示,它所取得的資料已經刪除。其報告提出一個問題:在暴露的這段期間內,貼文、投票與 karma 分數是否曾遭到竄改。另有一名獨立資安研究人員也各自發現了同一個資料庫設定,該發現由一家新聞媒體報導。
主要來源——S1:https://www.wiz.io/blog/exposed-moltbook-database-reveals-millions-of-api-keys存檔快照
損失成因
- 資料庫的逐列權限規則從未被打開,因此那把裝在每位訪客瀏覽器裡的金鑰,被當成管理員一樣回應。 這套資料庫會逐列判斷提出請求的人能不能看或改那一列,但這只有在規則被打開之後才成立;在那之前,它對每一個請求都照單全收地回答。規則關著時,一把原本只是用來表明「這是哪個專案」的金鑰,變成了一把能讀寫登入金鑰、個人資料與私人訊息的金鑰。技術名稱:列級存取控制(RLS)(row level security)
- 能打開資料庫的那把金鑰,被放進網站送給每位訪客的網頁檔案裡,任何讀到網頁的人都能複製走。 這類金鑰本來就設計成看得見,公開它是正常做法;它的價值完全取決於背後那層權限規則。規則關著時,把金鑰放進網頁,等於把整個資料庫送到每個看一眼的人手上。技術名稱:前端憑證暴露(client-side credential exposure)
- 訪客在修改網站公開內容之前,不需要證明自己是誰,因此任何陌生人都能改寫任何一則貼文。 讀取與寫入是兩種不同的權限,把敏感資料表的讀取關上,並沒有關上寫入那條路。以這種方式被改動的內容,接著會被平台上的 agent 讀進去,於是一名外人所做的編輯,會像是平台自己發布的一樣繼續往下傳。技術名稱:未經身分驗證的存取(unauthenticated access)
判讀
有兩樣東西從外面看一模一樣:一把公開也無妨的金鑰,和一把交出整個資料庫的金鑰。分開兩者的,是一層在有人刻意打開之前一直關著的權限規則。因為這個差別無法從憑證本身看出來,所以在外人先發現之前,沒有人會知道自己送出去的是哪一把。
自我查核
只有你能回答的
- 當有人告訴你,放在網頁裡的那把金鑰公開也無妨時,他們有沒有同時告訴你,它的安全性取決於資料庫裡另一個獨立的開關?而你有沒有問過,那個開關對每一張資料表都是開著的嗎?
- 如果今天有個陌生人能讀到你的其中一張資料表,哪一張最痛?你的私訊或客服區裡,有沒有存著通往其他你在付費的服務的金鑰與密碼?
- 當有人回報修補完成時,「完成」對你來說是什麼意思:外人已經無法讀你的資料、已經無法改你的資料,還是兩者都要?而在沒有白紙黑字的情況下,你會接受那個答案嗎?
你的 AI 可以幫你查的
只做回報;不要更動任何程式碼或設定。每一項回答都要為每個說法附上檔案或主控台的出處。
- 列出這個專案資料庫中的每一張資料表與檢視表,並針對每一個回報:是否已啟用列級存取控制(RLS),以及對匿名角色在 select、insert、update、delete 上分別套用了哪些政策。標示出任何未啟用 RLS 的資料表,或任何對請求者沒有設任何條件的寬鬆政策,並附上檔案或主控台的出處。
- 回報匿名呼叫者是否能透過 REST 與 GraphQL 端點取得資料庫結構,包括錯誤訊息或 introspection 是否洩漏了超出預期公開範圍的資料表與欄位名稱,並附上檔案或主控台的出處。
- 搜尋建置後的前端 bundle 與所有已部署的靜態檔案,找出會抵達瀏覽器的資料庫網址、API key 及其他設定值。針對找到的每一把金鑰,回報它的類型、指向哪一個專案,以及在目前的政策下,外人拿著它能做什麼。附上檔案或主控台的出處。
- 回報來自匿名呼叫者的寫入操作是否在每一張資料表上都被拒絕,並區分讀取政策與 insert、update、delete 政策。同時回報這個專案是否有自動化測試在檢核匿名寫入會被拒絕,以及該測試是否在每次變更時都會執行;並附上檔案或主控台的出處。
預防方式
上線前,要到一份逐張資料表的「是或否」,而且要書面。 請當初架設資料庫的人給你一份清單,列出每一張資料表,旁邊標上是或否:這張資料表會不會拒絕陌生人?把這份清單留著,而且每新增一張資料表就再要一次,因為新資料表一開始是對所有人有問必答的。
把你網頁裡的每一把金鑰,都當成早已公開。 假設陌生人已經拿到它,然後用書面向你的資料庫供應商或開發者問一個問題:只憑這把金鑰、沒有其他東西,一個人能讀到什麼、又能改動什麼?如果答案超出公開網頁本身,那個答案就是問題所在,而不是金鑰。
「能不能改資料」要和「能不能讀資料」分開問。 當有人回報修補完成時,要求兩項確認:外人已經無法讀取,而且外人已經無法編輯、新增或刪除。在兩項都拿到之前,就假設你公開展示的任何內容都可能被別人改寫;也不要把其他服務的金鑰或密碼放進存在那個資料庫裡的訊息、筆記或客服對話中。
一把公開也無妨的金鑰,和一把交出整個資料庫的金鑰,從外面看完全一樣;只有它背後的那個設定,才會告訴你自己送出去的是哪一把。