周三下午兩點,我們小組——Aki、Lark和Carl——打開那個用Flask寫的Python小應用,準備按課程要求做安全審計。應用功能不復雜,一個登錄頁,一個發帖頁,數據庫是sqlite3。老師定下的范圍就三個:SQL注入、CSRF和XSS,但我們一路看下來,修到根本停不住。最后統計,光是SQL注入就挖出七個,CSRF兩個,XSS一個,還有一些順手補上的邏輯坑。下面把最典型的四個SQLi原樣復現一遍,代碼怎么寫的、攻擊者怎么打、我們又怎么修,都擺明了。看完你可能會重新審視自己項目里那些“就先這么拼一下”的查詢。
先交代一句原應用是怎么構造SQL的。開發者在每個請求處理里,直接用加號把用戶輸入懟進查詢字符串。比如從表單拿用戶名和密碼,一拼接就扔給cursor執行。但凡輸入框、Cookie、URL參數,全都沒做任何過濾或轉義。這種做法像把前門鑰匙埋在地墊下,還掛個牌子“歡迎取用”。
![]()
SQLi?1:從密碼框把整條WHERE篡了
登錄驗證的查詢原本長這樣:res = cur.execute("SELECT id from users WHERE username = '" + request.form["username"] + "' AND password = '" + request.form["password"] + "'")
攻擊者完全不需要知道真實密碼。在用戶名框填入正常的alice,密碼框填' OR '1'='1。拼接之后,WHERE子句變成了:WHERE (username = 'alice' AND password = '') OR ('1'='1')'1'='1'永遠成立,數據庫直接把alice那行返回。于是攻擊者連密碼都不用試,就拿到了alice的登錄態。
修復很簡單:參數化查詢。把占位符?和參數元組傳給execute,讓驅動器自己去區分代碼和數據。改成:res = cur.execute("SELECT id FROM users WHERE username = ? AND password = ?", (request.form["username"], request.form["password"]))
這一步幾乎消滅后續所有基于拼接的注入點。
SQLi?2:注釋掉整個WHERE條件,連用戶名都不用知道
同樣是那個登錄查詢。這次攻擊者連具體用戶都不指定。用戶名輸入' OR 1=1 -- (注意末尾有個空格),密碼隨便填。拼接后得到:WHERE username = '' OR 1=1 -- ' AND password = '...'
雙橫線把后面的密碼檢查全部注釋,1=1又恒為真,于是數據庫返回第一行用戶。攻擊者稀里糊涂就成了系統里第一個注冊的人,毫無障礙。這在某些內部系統里可能就是管理員賬號。
修法跟SQLi?1一模一樣,轉參數化。但這件事暴露出一個更深的問題:原代碼對SQL注釋符沒有任何警惕,說明連接口本身根本沒考慮惡意片段。哪怕用了參數化,如果后頭還有動態拼接ORDER BY或表名的地方,仍然危險——后面幾條我們會看到同樣的毛病。
SQLi?3:指名道姓繞過密碼,針對性劫持
假如攻擊者已知道一個有效用戶名,比如alice,他就在用戶名框輸入alice' --,密碼隨便。拼接結果:WHERE username = 'alice' -- ' AND password = '...'
注釋把密碼校驗砍掉,數據庫只根據用戶名查,直接命中alice。這個手法比前兩條更精準,攻擊者可以專門瞄準某個賬號,比如運維人員或高權限用戶。
修復依然是參數化。但這時我們開始翻程序中所有構建查詢的地方,因為只要還有一處是字符串拼接,攻擊者就能找突破口。于是發現了Cookie驗證那塊。
SQLi?4:篡改Cookie拿走任意會話
驗證登錄態的代碼從Cookie里取session_token,直接縫合進INNER JOIN查詢:res = cur.execute("SELECT users.id, username FROM users INNER JOIN sessions ON " + "users.id = sessions.user WHERE sessions.token = '" + request.cookies.get("session_token") + "'")
攻擊者打開瀏覽器開發者工具的應用程序面板,找到對應域名的Cookie,把session_token的值改成' OR '1'='1。刷新頁面后,查詢變成:WHERE sessions.token = '' OR '1'='1'
條件恒真,數據庫返回所有用戶的第一條匹配記錄,攻擊者就能以某個合法用戶身份進入首頁。這次連登錄表單都不用碰,直接通過Cookie注入。
修復照舊:參數化。但這里提醒我們,所有外部輸入都要視為不可信,Cookie、Header、URL參數,一個都不能漏。另外,這個應用用了純粹拼接的動態表鏈接,也為后續的CSRF埋下了雷。
上面四個SQL注入模式占了我們挖到的一半。其余三個雖然形態不同——比如Union查詢直接拖庫、通過報錯信息推斷表結構、利用二階注入——但根源一致:字符串拼接。我們照著參數化路子一一改掉,之后重新審查所有SQL語句,把剩下的動態ORDER BY和表名用白名單映射,徹底切斷拼接路徑。至于CSRF和XSS漏洞,也都跟輸入輸出處理有關:CSRF因為請求里沒帶任何驗證Token,攻擊者可以偽造帖子創建請求;XSS則出現在帖子內容渲染時沒轉義,直接丟進HTML。這兩類修起來同樣是加Token、對輸出做轉義,但那是下一次分享的內容。
回過頭看,這個應用的問題不怪Flask,也不怪sqlite3,而是“先拼再執行”的習慣太順手。很多原型和內部工具就是這么起來的,上線后才補課。好在參數化幾乎零成本,看完這些攻擊寫法,你回去查一下自己的項目里有沒有+ request.form[...]或f"...{input}..."出現在SQL旁邊,有就趕緊換成占位符——補一個算一個。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.