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 | 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 <-- |
而且在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