web安全代码基础-PHP(模板组件插件安全)

存在安全问题的模板

模板引擎基本概念

模板引擎核心目标:前后端代码分离。在开始介绍模板框架之前先了解一下模板引擎,模板引擎是为了让前端界(html)与程序代码(php)分离而产生的一种解决方案,简单来说就是html文件里再也不用写php代码了。

模版使用案例

Php模版框架:Smarty、Twig,codeeval等

python模版框架:jinja2、mako、tornad、Django等

Java模版框架:Thymeleaf、jade、velocity、FreeMarker等

JavaScript模版框架:doT,Nunjucks,Pug,Marko,EJS,Dust等

Smarty 实现思路

1、模板文件(.tpl):纯 HTML + Smarty 标签 {$变量},无 PHP 代码

2、PHP 程序文件:只负责业务逻辑、查询数据、赋值变量,不写页面

3、底层原理:Smarty 读取模板,将 PHP 传入的变量替换模板标签,生成静态 HTML 输出浏览器

Smarty的原理是变量替换原则,我们只需在html文件写好Smarty的标签即可,例{name},然后调用Smarty的方法传递变量参数即可。

简单演示Smarty

环境说明:

复制代码
composer require smarty/smarty
//需提前安装 Smarty(Composer 一键安装)
  1. 目录结构规范

    project/
    ├── templates/ # 存放模板文件(html/tpl)
    │ └── index.tpl
    ├── templates_c/ # 编译缓存目录(需设置读写权限777)
    ├── cache/ # 页面缓存目录
    └── index.php # PHP后端逻辑

  2. 后端文件 index.php(PHP 逻辑)

php 复制代码
<?php
// 引入Smarty自动加载
require 'vendor/autoload.php';

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

// 配置模板路径、编译缓存路径
$smarty->setTemplateDir('templates/');    // 模板目录
$smarty->setCompileDir('templates_c/');  // 编译文件目录
$smarty->setCacheDir('cache/');          // 缓存目录

// 开启缓存(可选,开发环境可关闭)
$smarty->caching = false;

// ========== 给模板赋值变量 ==========
// 普通字符串变量
$smarty->assign('name', '张三');
$smarty->assign('age', 22);

// 数组变量
$userInfo = [
    'phone' => '13800138000',
    'address' => '北京市朝阳区'
];
$smarty->assign('user', $userInfo);

// 索引数组(列表)
$fruit = ['苹果', '香蕉', '橙子'];
$smarty->assign('fruitList', $fruit);

// 对象变量
class Student{
    public $stuId = 1001;
    public $stuName = '李四';
}
$stu = new Student();
$smarty->assign('student', $stu);

// 渲染并输出模板,参数为模板文件名
$smarty->display('index.tpl');
  1. 模板文件 templates/index.tpl(纯 HTML+Smarty 标签,无 PHP)
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Smarty模板示例</title>
</head>
<body>
    <h3>普通变量输出</h3>
    姓名:{$name} <br>
    年龄:{$age}

    <hr>
    <h3>数组输出</h3>
    手机号:{$user.phone} <br>
    地址:{$user.address}

    <hr>
    <h3>循环遍历数组</h3>
    {foreach from=$fruitList item=val}
        水果:{$val} <br>
    {/foreach}

    <hr>
    <h3>对象属性输出</h3>
    学号:{$student->stuId} <br>
    学生名:{$student->stuName}
</body>
</html>

4、页面最终渲染结果

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Smarty模板示例</title>
</head>
<body>
    <h3>普通变量输出</h3>
    姓名:张三 <br>
    年龄:22

    <hr>
    <h3>数组输出</h3>
    手机号:13800138000 <br>
    地址:北京市朝阳区

    <hr>
    <h3>循环遍历数组</h3>
    水果:苹果 <br>
    水果:香蕉 <br>
    水果:橙子 <br>

    <hr>
    <h3>对象属性输出</h3>
    学号:1001 <br>
    学生名:李四
</body>
</html>

5、运行过程解析

访问 index.php,PHP 不会直接输出 HTML;-->Smarty 读取 index.tpl 模板文件;-->内部自动将assign()赋值的变量,替换模板中{$xxx}标签;-->生成编译后的 HTML 缓存文件,输出到浏览器展示最终页面。

安全漏洞影响

SSTI(Server Side Template Injection,服务器端模板注入):用户可控输入直接拼接 / 传入模板引擎执行,而非仅作为普通变量赋值 。 模板引擎会解析模板语法({}{php}、函数、系统调用等),攻击者构造恶意模板标签,服务端执行后造成:信息泄露、文件读取、命令执行、服务器沦陷。

需要区分两个场景:

1、变量赋值(安全):用户输入只是变量值,模板引擎只输出文本,不解析模板语法;

2、模板拼接渲染(危险 / SSTI 漏洞):把用户输入直接拼进模板代码再渲染,输入会被当作模板语法执行。

模板拼接渲染(危险 / SSTI 漏洞)

漏洞代码:

php 复制代码
<?php
require 'vendor/autoload.php';
$smarty = new Smarty();
$smarty->setTemplateDir('templates/');
$smarty->setCompileDir('templates_c/');
$smarty->setCacheDir('cache/');
$smarty->caching = false;

// 漏洞根源:直接获取用户GET参数,拼接进模板字符串渲染
// display() 加载模板文件;fetch() 支持传入模板字符串
$user_input = $_GET['payload'];
// 危险操作:用户输入直接作为模板代码被Smarty解析
$tpl_code = "欢迎你:" . $user_input;
echo $smarty->fetch($tpl_code);

正常访问(无害输入):

访问:ssti_demo.php?payload=testuser 渲染逻辑: 模板字符串,无模板语法,直接输出文本。

运行结果``:欢迎你:testuser

攻击载荷(SSTI 恶意输入):

载荷 1:读取变量 / 环境信息

php 复制代码
?ssti={$smarty}
//Smarty 会输出 Smarty 对象,泄露路径、配置、缓存目录等敏感信息。

载荷 2:低版本 Smarty {php} 直接执行 PHP 代码(高危)

php 复制代码
?payload={php}system('whoami');{/php}
//模板引擎识别{php}标签,执行system()系统命令,查看服务器用户。
?payload={php}echo file_get_contents('/etc/passwd');{/php}
//读取文件

载荷 3:无 {php} 标签时,利用 Smarty 内置函数绕过

php 复制代码
?payload={eval var=$_GET['cmd']}{$cmd}
再传参:?payload={eval var=$_GET['cmd']}{$cmd}&cmd=system('ls -l');
//新版关闭{php},仍可通过{eval}、内置函数执行代码:
变量赋值(安全)
php 复制代码
// 安全写法:后端固定键赋值,用户只能修改变量值,不能修改模板标签
$username = $_GET['name'];
$smarty->assign('userName', $username);
// 模板 {$userName} 只会输出传入的字符串,不会解析模板语法
//PHP 后端通过assign()可控变量赋值,模板中{$变量}仅做简单字符串替换,用户输入无法控制模板语法本身

安全代码:

php 复制代码
<?php
require 'vendor/autoload.php';
$smarty = new Smarty();
$smarty->setTemplateDir('templates/');
$smarty->setCompileDir('templates_c/');
$smarty->setCacheDir('cache/');
$smarty->caching = false;

$user_input = $_GET['name'];
// 安全操作:用户输入仅作为变量值赋值,模板固定写死,不拼接用户输入
$smarty->assign('username', $user_input);
$smarty->display('test.tpl');

templates/test.tpl文件内容:

php 复制代码
欢迎你:{$username}

此时传入恶意载荷 ?name={php}system('id');{/php} 页面只会原样输出字符串 {php}system('id');{/php}不会执行任何代码

++原因:assign 传入的是变量值,Smarty 仅做输出转义,不会将变量内容解析为模板语法。++

模板探针

寻找所有可控输入点(测试点位)

  1. GET 参数:?name=xxx?content=xxx?title=xxx
  2. POST 表单:留言、昵称、简介、搜索框、评论
  3. Cookie 值:Cookie: user=xxx
  4. 请求头:User-Agent、Referer、X-Forwarded-For(页面会打印 UA/IP 时)
  5. 文件上传文件名、自定义页面标题、自定义模板名称

关键判断前提:输入内容会在页面展示(回显型点位最容易测 SSTI;无回显需要延时盲注探针)

SSTI 漏洞形成的核心条件

1、用户可控输入:GET/POST/Cookie 等外部输入完全可控;

2、输入被当作模板代码解析

  • 直接拼接字符串传入fetch()
  • 动态包含用户可控模板文件名;
  • 允许用户自定义模板内容并渲染;

3、模板引擎存在可利用语法 / 函数:支持代码执行、文件读写、系统调用标签。

Smarty 环境防御 SSTI 方案

1、禁止动态拼接用户输入到模板字符串:

永远不要 $smarty->fetch($user_input),模板文件名称固定写死,不接受用户控制;

2、所有外部输入只用 assign 赋值:

用户数据仅作为变量输出,不参与模板语法构建;

3、关闭高危标签:

Smarty 配置禁用{php}{eval}等危险标签:

php 复制代码
$smarty->disableSecurity();
$smarty->security_policy->disabled_tags = ['php','eval'];

4、开启模板安全沙箱:

限制模板中可调用的 PHP 函数,屏蔽system/file_get_contents/exec等危险函数;

php 复制代码
$smarty->enableSecurity();

5、输入输出转义:

Smarty 默认变量输出自动转义 HTML 特殊字符,避免同时触发 XSS;

6、严格过滤模板文件名:

若必须动态加载模板,白名单限制文件名,禁止用户传入任意路径。

存在安全问题的组件插件

一、富文本编辑器插件(高危攻击面:上传漏洞、XSS、SSTI、文件包含)

1、CKEditor / UEditor(百度编辑器):文件上传漏洞(最常见)、存储型XSS、路径遍历(图片预览、文件下载接口)等

黑盒探针点:

php 复制代码
/ueditor/php/upload.php、ckeditor/image_upload.php、dialogs/filemanager

2、TinyMCE、KindEditor、wangEditor

  • 弱过滤 XSS、远程图片抓取 SSRF;
  • 文件管理器插件elfinder高危:任意文件读写、删除、RCE(大量 WP/PHP CMS 集成)。
  1. 页面可视化编辑器(WP 插件)

如 Advanced Views、Contact Form 表单编辑器(Twig 模板)

  • CVE-2025-10380、CVE-2026-4257:未认证 SSTI 直接 RCE ,用户输入直接渲染 Twig 模板,{``{system('id')}}执行系统命令。

二、图片处理组件(高危:命令注入 RCE、文件读取、SSRF)

1、ImageMagick / PHP 扩展 Imagick(经典 Imagetragick CVE-2016-3714)

漏洞原理:

处理用户上传图片时解析 SVG/MVG/PS 指令,可嵌入系统命令,调用curl/wget/rm上传一张图片即可拿服务器权限

攻击方式:

  • 伪装 jpg 的恶意 SVG,内含url("|ls /")管道命令;
  • 依赖 Ghostscript,未禁用 PS/PDF 格式时持续爆出新 RCE(CVE-2025-57807)。

黑盒探针:

上传恶意 svg 图片,观察页面处理图片是否超时、是否泄露目录内容。

  1. GD 库(PHP 原生)

风险较低,但存在:

  • 图片水印、远程图片下载产生SSRF
  • 解析恶意 GIF 产生内存耗尽 DOS。
  1. 第三方图片压缩 / 裁剪插件

如 WP 媒体库、Discuz 图片处理:文件名可控拼接系统命令,命令注入。

三、邮件发送组件(高危:命令注入 RCE、CRLF 邮件注入、文件写入)等等,还有很多存在安全问题的模板、组件、插件等等。