0%

从一道题看Smarty_ssti

0x00 前言

​ 在BUUCTF做到了[CISCN2019 华东赛区]WEB11这道题。虽然只是一道很简单的SSTI,网上也很容易找到Payload。但是鉴于我找到的和我自己的Payload都不能用,这说明比赛时我不一定做得出来,于是在此记录并且浅谈Smarty SSTI

0x01 利用

漏洞确认:

​ 一般情况下直接在注入点输入{$smarty.version}就可以查看smarty的版本号。当然这同时也说明了这里存在SSTI。本题的版本号是3.1.30

旧版利用方式:

​ 首先是我在晚上找到的利用方法。{self::getStreamVariable("file:///proc/self/loginuid")}。大佬说可以利用这种方式来读文件,但是在这道题中会报错。具体原因我也不知道是为什么。毕竟我是真的没有尝试过Smarty模板开发。那么接下来谈谈合天大佬提到的注入方法。

标签方式:

​ 首先是Smarty支持使用{php}echo 1;{/php}这样的形式执行PHP代码。最常规的思路就是使用该标签。但是,好巧不巧的是,这道题目会报错。

官方文档中有下面的描述:

1
Smarty已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。

该题目使用的是Smarty类,所以只能另寻它路。

​ 另一个思路就是利用{literal}{/literal}标签,这个标签中间是可以插入JavaScript代码的。所以我们可以考虑利用{literal}<script language="php">phpinfo();</script>{/literal}这样的方式来插入PHP代码。

​ 但是就这道题而言,因为环境选用的是PHP7,而我在PHP5与PHP7的区别中也说过,PHP7中取消了<script language="php"></script>。所以这道题不能使用。

静态方法:

​ 通过self获取Smarty类再调用其静态方法实现文件读写被网上很多文章采用。

Smarty类的getStreamVariable方法的代码如下:

1
public function getStreamVariable($variable){ $_result = ”; $fp = fopen($variable, ‘r+’); if ($fp) { while (!feof($fp) && ($current_line = fgets($fp)) !== false) { $_result .= $current_line; } fclose($fp); return $_result; } $smarty = isset($this->smarty) ? $this->smarty : $this; if ($smarty->error_unassigned) { throw new SmartyException(‘Undefined stream variable “‘ . $variable . ‘”‘); } else { return null; } }

​ 可以看到这个方法可以读取一个文件并且返回它的内容,就像上文提到的那样,我们在网上见到的Payload大多都是这样的{self::getStreamVariable("file:///proc/self/loginuid")}。但是事实也证明了这种payload在新版本是没有用的,会抛出下面的错误:

1
2
Fatal error:  Uncaught  --> Smarty Compiler: Syntax error in template "string:<html lang="en"><head><meta http-equiv="..."  on line 12 "<div style="float:right;margin-top:30px;">Current IP:{if self::getStreamVariable("file:///flag")}{/if} </div>" static class 'self' is undefined or not allowed by security setting <-- 
thrown in

​ 而且在Smarty3.1.30版本中官方已经把该静态方法删除。对于那些文章提到的利用 Smarty_Internal_Write_File类的writeFile方法来写shell也由于同样的原因无法使用。

新版利用方式:

{if}标签:

在官方文档中有下面这些话:

1
Smarty的{if}条件判断和PHP的if 非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if}. 也可以使用{else} 和 {elseif}. 全部的PHP条件表达式和函数都可以在if内使用,如*||*, or, &&, and, is_array(), 等等

既然全部的PHP函数都可以使用,那么可以执行PHP代码吗?

​ 尝试使用下面的Payload:{if phpinfo()}{/if}。发现成功爆出phpinfo。接下来直接命令执行就好了。

0x02 分析

​ 最后跟着合天大佬看一看漏洞的成因。本题的代码是这样的。

1
<?phprequire_once(‘./smarty/libs/’ . ‘Smarty.class.php’);$smarty = new Smarty();$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];$smarty->display(“string:”.$ip);}

​ 因为$ip没有经过任何检验就被当做Smarty模板,所以Smarty标签被直接执行从而导致了SSTI

0x03 结语

​ 这是非博客外的第一篇SSTI的文章。以后应该会有Java或者别的框架的。Flask就算了,说太多就没意思了。说起来SSTI的秘诀其实就是查官方文档。所以暑假一定要把官方文档看一遍。

0x04 参考

Smarty官方文档:https://www.smarty.net/docs/

https://portswigger.net/research/server-side-template-injection

合天大佬的文章:https://www.freebuf.com/column/219913.html

各种模板注入的集合:https://www.k0rz3n.com/2018/11/12/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%E6%BC%8F%E6%B4%9E%E4%B9%8BSSTI%E6%BC%8F%E6%B4%9E/