絡(luò)安全】Mysql注入應(yīng)用:GET_LOCK鎖機(jī)制繞過WAF的實戰(zhàn)復(fù)現(xiàn))
1. MySQL 注入里被忽略的延時手法GET_LOCK 鎖機(jī)制到底是什么如果你做過 SQL 注入的時間盲注大概率用過sleep()、BENCHMARK()、笛卡爾積這幾類延時手段。它們原理直白、上手快但問題也很明顯主流 WAF 早就把這些函數(shù)名寫進(jìn)了規(guī)則庫payload 一發(fā)出去就被攔。我在本地靶場復(fù)現(xiàn)時發(fā)現(xiàn)MySQL 里還有一個幾乎沒人認(rèn)真用的延時函數(shù)——GET_LOCK()它靠的是用戶級命名鎖的會話競爭來制造等待很多 WAF 的默認(rèn)規(guī)則里根本沒有覆蓋它。GET_LOCK()是 MySQL 提供的一個用戶級鎖函數(shù)用來在應(yīng)用層實現(xiàn)跨會話的鎖機(jī)制。它的完整簽名是GET_LOCK(str lock_name, int timeout)第一個參數(shù)是鎖名稱最大 64 字符且區(qū)分大小寫第二個參數(shù)是等待超時時間單位秒傳 0 表示立即返回傳負(fù)數(shù)表示無限等待MySQL 5.7.5 之后支持。返回值有三種返回 1 表示成功拿到鎖返回 0 表示在超時時間內(nèi)沒搶到鎖返回 NULL 表示參數(shù)非法或內(nèi)存不足之類的錯誤。配套的釋放函數(shù)是RELEASE_LOCK(str)返回 1 表示當(dāng)前會話持有該鎖并成功釋放返回 0 表示當(dāng)前會話并不持有這個鎖返回 NULL 表示鎖壓根不存在。這里有個容易踩的坑同一個會話里重復(fù)調(diào)用GET_LOCK(x, 10)不會阻塞會直接返回 1因為鎖已經(jīng)被這個會話自己持有了。真正產(chǎn)生等待的前提是不同會話去搶同一個鎖名。鎖的釋放規(guī)則也值得記一下。顯式釋放靠RELEASE_LOCK()隱式釋放發(fā)生在會話終止、連接關(guān)閉的時候另外還受wait_timeout參數(shù)控制。它和 InnoDB 的行鎖不一樣不會隨事務(wù)結(jié)束自動釋放。你可以用SHOW VARIABLES LIKE wait_timeout;查看當(dāng)前會話超時時間也能用SET SESSION wait_timeout 600;臨時改大。我本地這套環(huán)境默認(rèn)是 120 秒網(wǎng)上有人說默認(rèn) 8 小時實際跟 MySQL 版本和發(fā)行版配置有關(guān)別照搬。理解了這個機(jī)制就能明白它在注入場景里的價值只要能讓兩個不同會話去競爭同一個鎖名其中一個會話持有鎖不釋放另一個會話就會卡在等待上形成可觀測的延時。這個延時不是靠 CPU 空轉(zhuǎn)也不是靠sleep那種顯式休眠而是靠鎖等待行為特征和常規(guī)延時函數(shù)完全不同這正是它能繞過一部分 WAF 檢測邏輯的原因。接下來我會把本地靶場的復(fù)現(xiàn)步驟、可復(fù)制的 payload、以及和雷池 WAF 的攔截對比完整走一遍。2. 復(fù)現(xiàn)前的環(huán)境準(zhǔn)備TaoToken 接入與靶場數(shù)據(jù)庫配置要在本地把 GET_LOCK 延時注入跑通你需要兩樣?xùn)|西一個能發(fā)請求的注入點(diǎn)和一個能觀察會話與鎖狀態(tài)的數(shù)據(jù)庫環(huán)境。注入點(diǎn)我用 sqli-labs 和 MRCMS 兩個靶場組合數(shù)據(jù)庫用 MySQL 5.7 以上版本。為了讓整個調(diào)試過程更順手我習(xí)慣把模型對話和代碼輔助接到 TaoToken 上邊復(fù)現(xiàn)邊讓它幫我解釋報錯、生成 payload 變體省去來回查文檔的時間。TaoToken 是一個大模型 API 聚合平臺兼容 OpenAI 風(fēng)格的接口協(xié)議你拿到的 Key 可以直接用在支持自定義 Base URL 的客戶端里。對做安全復(fù)現(xiàn)的人來說它的用處在于遇到 WAF 攔截、SQL 語法報錯、鎖等待不生效這類問題時可以把報錯原文丟給模型讓它幫你分析是語法問題、會話問題還是規(guī)則命中問題。下面是我實際用的接入配置。先到官網(wǎng) https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注冊賬號然后在控制臺創(chuàng)建 API Key??刂婆_地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理頁面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。API 的基礎(chǔ)地址是 https://taotoken.net/api 注意這個地址不帶任何查詢參數(shù)。如果你用的是 Cline、Continue 這類支持 OpenAI 兼容協(xié)議的插件配置項就三項Base URL 填https://taotoken.net/apiAPI Key 填你創(chuàng)建的那串Model ID 填你在模型列表里選中的模型名。這三件套缺一不可尤其是 Model ID填錯了會直接返回 404 或者 model not found。想先驗證模型通不通可以用模型對話頁面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 發(fā)一條測試消息能正常返回就說明 Key 和地址沒問題。數(shù)據(jù)庫這邊確認(rèn)你的 MySQL 版本在 5.7.5 以上因為負(fù)數(shù)超時無限等待是那個版本之后才支持的。然后檢查wait_timeoutSHOW VARIABLES LIKE wait_timeout; SHOW VARIABLES LIKE max_connections;max_connections決定了你能同時開多少個會話做鎖競爭測試時至少要保證兩個獨(dú)立連接。sqli-labs 用 phpstudy 搭起來默認(rèn)就是短會話模式每個請求新建連接、請求完就關(guān)。MRCMS-3.1.2 是長會話模式整個應(yīng)用共享一個持久連接。這兩個組合起來正好構(gòu)成多 Web 共用數(shù)據(jù)庫 長會話 短會話的利用場景也是后面能穩(wěn)定觸發(fā)鎖等待的關(guān)鍵。3. 可復(fù)制的 GET_LOCK 注入 Payload 與配置片段這一節(jié)是整篇的核心我把實際跑通的 payload、數(shù)據(jù)庫配置和會話觀察方法都列出來你可以直接復(fù)制到自己的靶場里改。先明確利用條件只有兩條同時滿足才會產(chǎn)生延時第一必須是不同會話去競爭同一個鎖名第二持有鎖的那個會話要長時間不釋放。短會話模式下A 會話拿到鎖后請求一結(jié)束連接就關(guān)了鎖自動釋放B 會話還沒來得及等就沒了所以單靠短會話靶場是打不出延時的。先看最基礎(chǔ)的鎖獲取和釋放在 MySQL 客戶端里手動驗證一遍機(jī)制-- 會話1獲取名為 testlock 的鎖超時 10 秒 SELECT GET_LOCK(testlock, 10); -- 返回 1表示獲取成功 -- 會話2另開一個連接嘗試獲取同名鎖 SELECT GET_LOCK(testlock, 5); -- 卡住 5 秒后返回 0表示超時未獲取到 -- 會話1釋放鎖 SELECT RELEASE_LOCK(testlock); -- 返回 1 -- 會話1再次釋放 SELECT RELEASE_LOCK(testlock); -- 返回 NULL鎖已不存在確認(rèn)機(jī)制沒問題后進(jìn)入注入場景。核心 payload 是把GET_LOCK拼進(jìn)注入點(diǎn)讓數(shù)據(jù)庫執(zhí)行它并等待-- 基礎(chǔ)形式直接調(diào)用 GET_LOCK 制造鎖等待 ?id1|| GET_LOCK(1,5) || -- 嵌套 if 形式條件為真時才觸發(fā)鎖等待更適合盲注判斷 ?id1|| if(1,GET_LOCK(1,5),1) || -- 配合布爾判斷把鎖等待當(dāng)作真值信號 ?id1 AND IF(ASCII(SUBSTRING(database(),1,1))100, GET_LOCK(1,5), 0) --這里GET_LOCK(1,5)的第一個參數(shù)是鎖名用數(shù)字 1 也行MySQL 會把它當(dāng)字符串處理。第二個參數(shù) 5 是超時秒數(shù)實際延時大約就是 5 秒。如果你想要更長的延時把 5 改成 10 或 15但別太大否則請求容易超時斷開反而觀察不到結(jié)果。關(guān)鍵的一步是讓長會話靶場先持有鎖。用 MRCMS-3.1.2 的漏洞接口發(fā)一次注入讓它的持久連接拿到鎖且不釋放-- 通過 MRCMS 漏洞點(diǎn)注入讓長會話持有名為 1 的鎖 -- 該會話是持久連接不會主動釋放鎖一直掛著 1 AND GET_LOCK(1,100) --長會話拿到鎖后再用 sqli-labs 的短會話注入點(diǎn)去搶同一個鎖名就會卡住?id1|| GET_LOCK(1,5) ||短會話請求進(jìn)來嘗試獲取鎖名 1發(fā)現(xiàn)已被長會話占用于是等待 5 秒后超時返回。這 5 秒就是可觀測的延時信號。整個過程不涉及sleep、BENCHMARK這些被重點(diǎn)監(jiān)控的函數(shù)WAF 的規(guī)則庫里如果沒有專門針對GET_LOCK的條目就不會攔截。如果你用 Cline 或 Claude Code 這類工具輔助調(diào)試配置片段可以寫成這樣以 OpenAI 兼容格式為例{ base_url: https://taotoken.net/api, api_key: 你的_API_KEY, model: 你的_MODEL_ID }把報錯原文、payload、數(shù)據(jù)庫返回貼給模型讓它幫你判斷是會話沒對上、鎖名不一致還是 WAF 規(guī)則命中。實測下來這種邊打邊問的方式比自己翻文檔快很多尤其是排查鎖等待不生效的時候。4. 驗證請求與成功結(jié)果從數(shù)據(jù)庫監(jiān)聽看鎖等待payload 寫好了怎么確認(rèn)延時真的來自鎖等待而不是網(wǎng)絡(luò)抖動或者別的原因我一般從三個層面交叉驗證HTTP 響應(yīng)時間、數(shù)據(jù)庫會話狀態(tài)、以及 WAF 后臺日志。先看 HTTP 層面。用 Burp Suite 或者 curl 發(fā)請求記錄響應(yīng)時間。正常請求幾十毫秒返回觸發(fā)鎖等待的請求會穩(wěn)定多出 5 秒左右# 正常請求觀察基線響應(yīng)時間 curl -o /dev/null -s -w time_total: %{time_total}s\n http://靶場地址/sqli-labs/Less-1/?id1 # 觸發(fā)鎖等待的請求 curl -o /dev/null -s -w time_total: %{time_total}s\n http://靶場地址/sqli-labs/Less-1/?id1||GET_LOCK(1,5)||如果第二條穩(wěn)定比第一條多 5 秒說明延時生效了。但光看時間還不夠得確認(rèn)是鎖造成的。這時候打開數(shù)據(jù)庫監(jiān)聽工具比如 MySQL 的SHOW PROCESSLIST或者用抓包工具看連接狀態(tài)SHOW PROCESSLIST;你會看到兩個不同Id的連接一個處于User sleep或者等待狀態(tài)另一個持有鎖。長會話那個連接的Time字段會持續(xù)增長說明它一直掛著沒釋放。短會話那個連接在等待鎖超時。再進(jìn)一步可以直接查鎖的持有情況。MySQL 5.7 之后可以用 performance_schema 里的 metadata_locks 表或者簡單點(diǎn)在另一個會話里試著手動搶同名鎖看會不會卡-- 在第三個會話里執(zhí)行如果卡住說明鎖確實被占著 SELECT GET_LOCK(1, 3);如果這個也卡 3 秒返回 0就坐實了鎖名 1 被長會話持有。這一步很關(guān)鍵很多人以為延時生效了其實是別的原因用這個方法能排除掉。最后看 WAF 后臺。雷池 WAF 默認(rèn)配置下sleep和BENCHMARK的 payload 會被直接攔截日志里能看到命中規(guī)則。而GET_LOCK的 payload 在后臺能看到被檢測到但默認(rèn)不攔截只記錄。我復(fù)現(xiàn)時對比過發(fā)sleep(5)的請求直接返回攔截頁面發(fā)GET_LOCK(1,5)的請求正常返回且延時 5 秒。這個對比最能說明問題——不是 WAF 沒看見而是它的默認(rèn)規(guī)則沒把鎖函數(shù)當(dāng)成高危延時手段。嵌套 if 的形式也一樣能過?id1|| if(1,GET_LOCK(1,5),1) ||響應(yīng)時間同樣多 5 秒WAF 后臺依舊只記錄不攔截。到這一步整個鏈路就驗證完了長會話持鎖 → 短會話搶鎖 → 鎖等待 → 延時 → WAF 不攔。5. 常見報錯排查401、local proxy failed、reading choices 與 OAuth復(fù)現(xiàn)過程中最容易卡住的不是 payload 本身而是環(huán)境和工具鏈的報錯。我把幾個高頻問題和排查思路列出來對照著看能省不少時間。401 Unauthorized這個基本都出在 API 接入環(huán)節(jié)。如果你用 Cline 或 Claude Code 調(diào)模型時返回 401先檢查三件套——Base URL 是不是https://taotoken.net/api注意不要多加斜杠或者路徑API Key 是不是復(fù)制完整前后別有空格Model ID 是不是當(dāng)前賬號有權(quán)限的模型。三個里錯一個都會 401 或者 404。我踩過的坑是 Key 復(fù)制時帶了個換行排查了半天。local proxy failed這個報錯通常出現(xiàn)在客戶端配置了本地代理但代理沒起來或者端口不對。如果你在 Cline 里看到這個檢查設(shè)置里的代理選項把不需要的代理關(guān)掉直連https://taotoken.net/api。注意這里說的是客戶端自身的網(wǎng)絡(luò)配置問題不是讓你去搞什么網(wǎng)絡(luò)工具純粹是本地端口沒通。reading choices 相關(guān)報錯這類報錯一般出現(xiàn)在流式響應(yīng)解析階段比如error reading choices或者unexpected end of JSON。原因多半是模型返回被中途截斷或者客戶端超時設(shè)置太短。把客戶端的超時時間調(diào)大比如從 30 秒改成 120 秒再試一次。如果還不行換模型對話頁面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 直接發(fā)請求看是不是模型側(cè)的問題。OAuth 相關(guān)報錯如果你用的是 Claude Code 這類帶 OAuth 流程的工具報錯里出現(xiàn)OAuth token expired或者invalid_grant說明授權(quán)過期了。重新走一遍授權(quán)流程或者在配置里改用 API Key 方式接入。Claude Code 的接入文檔在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的配置步驟包括 Base URL、Key 和 Model ID 怎么填。鎖等待不生效這個不算報錯但比報錯更讓人頭疼。排查順序是先確認(rèn)兩個會話是不是真的不同SHOW PROCESSLIST看 Id 是否不一樣再確認(rèn)鎖名是不是完全一致大小寫敏感最后確認(rèn)長會話有沒有提前釋放鎖連接斷了鎖就沒了。如果用的是短會話靶場單獨(dú)打那永遠(yuǎn)打不出延時必須配一個長會話靶場先持鎖。WAF 攔截了 GET_LOCK雖然默認(rèn)配置下雷池不攔但有些 WAF 規(guī)則更嚴(yán)。如果被攔試試把鎖名換成隨機(jī)字符串或者用if嵌套把函數(shù)調(diào)用藏深一點(diǎn)。也可以把GET_LOCK拆成大小寫混合或者加注釋比如GET_/**/LOCK(1,5)看能不能繞過規(guī)則匹配。不過這類變形要看具體 WAF 的解析能力不一定都有效。6. 從復(fù)現(xiàn)到實戰(zhàn)鎖機(jī)制注入的邊界與后續(xù)方向把整個流程跑通之后我對 GET_LOCK 這類鎖機(jī)制注入的理解清晰了不少。它的核心優(yōu)勢不在于延時有多精準(zhǔn)而在于行為特征和常規(guī)延時函數(shù)完全不同。sleep是顯式休眠BENCHMARK是 CPU 空轉(zhuǎn)這倆在流量特征和數(shù)據(jù)庫行為上都很顯眼。而鎖等待表現(xiàn)為一個會話持鎖、另一個會話阻塞從數(shù)據(jù)庫角度看是正常的并發(fā)控制行為很多 WAF 的規(guī)則庫壓根沒把它當(dāng)成攻擊特征。但它也有明確的邊界。利用條件里不同會話和長時效應(yīng)這兩條決定了它只在特定部署模式下有效。短會話模式打不出來純長會話模式也打不出來只有連接池模式、多 Web 共用數(shù)據(jù)庫、長會話加短會話混合這些場景才有戲。所以實戰(zhàn)中第一步不是急著發(fā) payload而是先判斷目標(biāo)屬于哪種連接模式。判斷方法也簡單多次請求看數(shù)據(jù)庫連接的thread_id變不變變了就是短會話或連接池不變就是長會話。另一個值得注意的點(diǎn)是wait_timeout。鎖的隱式釋放受這個參數(shù)控制如果目標(biāo)數(shù)據(jù)庫的wait_timeout設(shè)得很短長會話可能在你發(fā)第二個請求之前就超時斷開了鎖也就釋放了。復(fù)現(xiàn)時我本地默認(rèn)是 120 秒夠用。如果遇到鎖等待時有時無先查這個參數(shù)。后續(xù)想深入的話可以研究幾個方向一是不同 WAF 對GET_LOCK的檢測策略差異看看哪些規(guī)則更嚴(yán)二是把鎖機(jī)制和其他盲注技巧結(jié)合比如用鎖等待做布爾判斷的延時信號替代傳統(tǒng)的sleep三是看看 MySQL 8.0 之后鎖相關(guān)函數(shù)有沒有變化比如GET_LOCK的行為調(diào)整。這些在本地靶場都能復(fù)現(xiàn)成本不高。如果你在復(fù)現(xiàn)時遇到模型調(diào)用或者配置上的問題可以到接入文檔 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 查具體步驟或者用 API Keys 頁面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 重新生成 Key 試試。長期做安全研究和編碼輔助的話Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 會更劃算一些。先把本地這套環(huán)境跑通再慢慢擴(kuò)展到更復(fù)雜的場景。