前言
在sql注入的延時注入中,常見的函數有sleep()直接延時、BENCHMARK()通過讓數據庫進行大量的計算而達到延時的效果、笛卡爾積、正則匹配等,但還有一個常常被忽略的函數,也就是Mysql中的鎖機制。雖然早些年就已經出現過相關的技術文章,但是他的應用卻幾乎見不到,也沒有看到有文章對他的機制和運用進行深入講解,而且該函數也常常被waf忽略導致延時。
Mysql鎖機制介紹
函數介紹
GET_LOCK() 是 MySQL 提供的一個用戶級鎖函數,用于在應用層實現跨會話的鎖機制。以下是該函數的解析:
GET_LOCK(str lock_name, int timeout)
參數:
lock_name(字符串):鎖名稱(最大64字符,區分大小寫)
timeout(整數):等待超時時間(秒),0表示立即返回,負數表示無限等待(MySQL 5.7.5+)
返回值:
1:成功獲取鎖
0:獲取鎖超時(其他會話持有鎖且未在指定時間內釋放)
NULL:發生錯誤(如參數無效、內存不足等)
RELEASE_LOCK(str)
用于解開鎖,str表示要解開的鎖名
返回值:
1:成功釋放,當前會話持有鎖并成功釋放
0:釋放失敗,當前會話不持有該鎖
NULL:錯誤或鎖不存在, 鎖名稱從未被獲取
示例:
通過返回值1判斷成功獲取名為1的鎖
通過返回值1判斷成功釋放名為1的鎖
再次釋放時,則為Null(鎖不存在)
核心特性
命名鎖機制:
基于字符串名稱的鎖,不同鎖名互不影響,例如:
GET_LOCK('test1', 10)
GET_LOCK('test2', 10)
他們直接由于鎖名不同,所以互不影響
會話級作用域:
當一個會話成功獲取了某個命名鎖后,其他會話在嘗試獲取相同名稱的鎖時將會被阻塞,直到鎖被釋放或超時。
而在同一個會話內部,即使多次請求相同的鎖,也不會造成阻塞,會直接返回成功,因為該會話已經持有該鎖。
示例:
會話1先通過GET_LOCK()函數獲取了名為1的鎖,返回結果為1表示獲取成功
會話2再次通過GET_LOCK()函數獲取名為1的鎖,但是名為1的鎖以及被會話1占有,所以會話2直到超時5秒,結果為0表示獲取失敗
鎖釋放規則:
顯式釋放:RELEASE_LOCK('lock_name')
隱式釋放:會話終止(連接關閉)
時效釋放:受wait_timeout 參數控制
不會隨事務結束自動釋放(與InnoDB行鎖不同)
SHOW VARIABLES LIKE 'wait_timeout';
可以查看會話超時時間(秒),這里是2分鐘
SET SESSION wait_timeout = 600;
可以通過該函數來設置wait_timeout參數
通過網上搜索發現wait_timeout參數的默認值是8小時,但是我的數據庫默認就是2分鐘,也沒有看到官方的具體說明,可能是受到Mysql版本的影響。
Web各類布局中的利用
利用條件
剛剛講完了Mysql鎖的機制,那么要使用GET_LOCK()函數成功讓目標數據庫發現延遲,就需要具備以下兩個條件:
- 需要不同會話(只有不同會話的鎖競爭才會導致延時)
- 獲取鎖的會話具備長時效應(既會話或鎖不被馬上釋放)
短會話模式
模式簡介:
每個HTTP請求都新建數據庫連接,請求完成后立即關閉連接。無連接復用。
使用規模:
極少,主要存在于遺留系統或極低流量場景
利用可行性:
這種情況下,大概率是無法造成鎖等待,雖然每次用戶的請求滿足條件1:需要不同會話。但是無法滿足條件2:獲取鎖的會話具備長時效應。也就是說當A會話獲取完鎖a,B會話還沒來得及等待鎖a,A會話就已經結束了,就自動釋放鎖,就無法達到鎖等待的效果。
舉一個最簡單的例子,phpstudy中搭建的靶場sqli-labs就是如此(短會話模式)
這里攔截后大量發包,在一次性并發。
通過數據庫監聽工具可以看到,當數據庫還沒來得及因為其他會話的鎖造成等待,其他會話的鎖就已經結束并關閉連接,自動釋放鎖了。所以不滿足條件2:獲取鎖的會話具備長時效應。
長會話模式
模式簡介:
整個Web應用使用單個持久數據庫連接,所有用戶請求共享此連接。
使用規模:
較少,特定場景:金融交易系統、小型嵌入式應用,一些較老的cms也存在此情況
利用可行性:
所有用戶操作在同一個會話中,但條件1需要不同會話才會產生鎖等待,所以不會發生鎖等待,也就不能造成延時的效果
這里以MRCMS-3.1.2版本為例,他就是長會話模式
通過數據庫監聽工具可以看到,無論進行多少次數據庫操作他的thread_id一直都為994,盡管切換登錄,替換用戶憑證,ip等都還是thread_id為994且沒有斷開連接,那么就不滿足條件1:需要不同會話(只有不同會話的鎖競爭才會導致延時),也就不會導致鎖等待,就不會造成延時的效果。
連接池模式
模式簡介:
預先創建連接池(如20個連接)
每個HTTP請求從池中借用連接,用完歸還
物理連接復用,邏輯會話隔離,既每個會話重復的從連接池中使用連接
使用規模:
主流,現代Web應用常出現,Spring Boot(HikariCP), Django, Laravel等默認使用
利用可行性:
鎖綁定物理連接,不自動釋放
用戶A獲取鎖 → 未釋放 → 連接1歸還但不自動釋放 → 用戶B使用連接2 → 鎖沖突
利用方法:
那么只要我們通過大量的請求,就一定會從Pool中請求到兩個不同的物理連接,這樣利用GET_LOCK函數就能達到延時的效果
用戶級長連接(綁定會話)
模式簡介:
每個用戶分配專屬數據庫連接,在整個會話期間保持打開(既長連接)。
使用規模:
較少,實時系統:在線協作工具、交易平臺
利用可行性:
用戶A在會話1持有鎖
用戶B在會話2請求同名鎖 → 滿足條件1(不同會話)+條件2(鎖未釋放)
利用方法:
只要不同用戶的憑證去請求同一個鎖,比如GET_LOCK(1,5)那么就會發生鎖等待,造成延時效果
IP/客戶端級連接
模式簡介:
與上一個用戶級長連接極其相似,只不過按客戶端IP分配固定連接,同IP的多個用戶共享連接。
使用規模:
極少,特殊場景:游戲服務器、定制網關
利用可行性:
IP組內:同會話無沖突(如IP1用戶A和用戶B無鎖等待)
IP組間:不同會話有沖突(如IP1用戶 vs IP2用戶)
利用方法:
和用戶級長連接(綁定會話)類似,只是需要ip不同
多Web共用數據庫
模式簡介:
現在常見的web部署方式為站庫分離,且多站共用一個庫,多個獨立應用(如微服務)共享同一數據庫,各自使用連接池。
使用規模:
常見,微服務架構,現代云原生應用常見模式
利用可行性:
多web共享同一連接池:那么原理與連接池模式相同
多web使用獨立連接池:不同連接池=不同會話,原理與連接池模式相同
多web不使用連接池+全是短會話:無法產生,與短連接原理相同
多web不使用連接池+全是長會話:無法產生,與長連接原理相同
多web不使用連接池+長會話+短會話:不同web的連接也就會產生不同的會話,滿足條件1:需要不同會話;長會話滿足條件2:獲取鎖的會話具備長時效應。
利用方法:
多web不使用連接池+長會話+短會話:先對長會話的web進行注入來獲取鎖,再通過短會話web注入來達到鎖等待,造成延時效果。
雷池waf延時實戰
雷池waf配置
這里均采用默認配置
常規延時手段
Sleep延時被攔截
BENCHMARK函數被攔截
可以看到常規的延時手法都直接攔截了,那么接下來使用我們剛剛講解的鎖機制來延時
鎖函數造成延時
這里采用兩個web共用一個數據庫的形式,一個是phpstudy中搭建的靶場sqli-labs短會話模式,另一個是MRCMS-3.1.2長會話模式,那么兩者構成的就是多Web共用數據庫模式。
多web不使用連接池+長會話+短會話:
先對長會話的web進行注入來獲取鎖,再通過短會話web注入來達到鎖等待,造成延時效果。
先使用MRCMS-3.1.2的漏洞接口注入來獲取鎖:
然后在sqli-labs中直接注入
?id=1%27||%20GET_LOCK(1,5)%20||%27
成功造成延時效果,且不觸發雷池waf攔截
接著嵌套if語句,依舊成功造成延時,且不觸發雷池waf攔截
?id=1%27||%20if(1,GET_LOCK(1,5),1)%20||%27
通過數據庫監聽工具看到時間部分確實造成了延時,以及長會話和短會話共用數據庫連接導致的不同會話造成了鎖等待,最終產生延時效果
到雷池waf后臺可以看到鎖函數實際上是檢測到了,但在默認配置下不攔截,只攔截了底部的兩條比較常見的延時函數。
總結
利用鎖機制的優勢就是不易被waf檢測(在寫文章的時候似乎寫入其他延時payload會導致出錯無法預覽,而鎖機制的延時payload則可以正常預覽,這也算是不易被檢測的一種),既然大名鼎鼎的雷池waf都能成功延時,那么市面上大多數的waf應該也一樣。
那么接下來的注入方法就由各位師父大顯神通了,這里就不再做太多的贅述。?