本文档提供 Zandy_Template 模板引擎的安全使用指南。
- 变量泄露:模板可以访问所有全局变量
- 敏感信息泄露:可能泄露数据库连接信息、API密钥等
- 变量覆盖:可能导致变量覆盖,影响程序逻辑
// 配置白名单,只允许指定的变量被模板访问
$GLOBALS['siteConf']['template_vars_mode'] = 'whitelist';
$GLOBALS['siteConf']['template_vars_whitelist'] = ['user', 'data', 'items'];
// 使用方式不变
$GLOBALS['user'] = $user;
$GLOBALS['data'] = $data;
$html = Zandy_Template::outString('template.htm', $tplDir, $cacheDir);优点:
- 平衡安全性和易用性
- 只允许模板访问指定的变量
- 推荐用于生产环境
// 显式传递变量,完全控制变量访问
$html = Zandy_Template::outString('template.htm', $tplDir, $cacheDir, false, [
'user' => $user,
'data' => $data
]);优点:
- 最安全,完全控制变量访问
- 不依赖全局变量
- 适合高安全要求的场景
- 开发环境:可以使用完全开放模式(默认),方便调试
- 生产环境:推荐使用白名单模式或显式传递模式
- 高安全要求:使用显式传递模式,完全控制变量访问
- 模板来源不可信:必须使用显式传递模式,并禁用 PHP 代码块
<!--{php}-->...<!--{/php}-->- 执行任意 PHP 代码<!--{set ...}-->- 设置变量,可执行代码<!--{include ...}-->- 包含 PHP 文件
- 任意代码执行:模板可以执行任意 PHP 代码
- 系统访问:可以访问文件系统、数据库等
- 数据泄露:可以读取敏感文件
-
模板来源可信:
- 如果模板由开发者编写,可以使用这些功能
- 确保模板文件权限正确
-
模板来源不可信:
- 必须禁用 PHP 代码块(如果可能)
- 使用白名单机制限制可包含的文件
- 严格验证模板路径
-
生产环境:
- 如果模板来源不可信,应禁用这些功能
- 考虑在模板解析阶段过滤这些语法
模板引擎会自动验证:
- ✅ 模板目录必须在
tplBaseDir内 - ✅ 缓存目录必须在
tplCacheBaseDir内 - ✅ 防止路径遍历攻击(使用
realpath()检查)
// 模板路径验证
if (!$tplDir2 || !$tplBaseDir || false === stripos($tplDir2, $tplBaseDir)) {
self::halt('$tplDir is not a valid tpl path', true);
}
// 缓存路径验证
if (!$cacheDir2 || !$tplCacheBaseDir || false === stripos(realpath($cacheDir2), $tplCacheBaseDir)) {
self::halt('cache path is not valid', true);
}-
配置正确的路径:
- 确保
tplBaseDir和tplCacheBaseDir配置正确 - 使用绝对路径,避免相对路径问题
- 确保
-
文件权限:
- 模板文件:只读权限
- 缓存目录:可写权限,但限制访问
-
路径验证:
- 引擎已自动验证路径,无需额外处理
- 确保配置的路径正确
Zandy_Template 采用手动转义策略:
- 默认行为:不转义(保持向后兼容)
- 转义方式:需要转义时手动添加
|escape修饰符 - 语法:
- 转义:
{$var|escape}→htmlspecialchars($var, ENT_QUOTES, 'UTF-8') - 不转义:
{$var}→$var
- 转义:
<!-- 用户输入必须转义,防止 XSS 攻击 -->
用户名:{$username|escape}
评论内容:{$comment|escape}
搜索关键词:{$keyword|escape}为什么必须转义:
- 用户输入可能包含恶意脚本(如
<script>alert('XSS')</script>) - 不转义会导致 XSS 攻击
- 转义后:
<script>alert('XSS')</script>(安全)
<!-- 系统生成的内容,来源可信,可不转义 -->
标题:{$title}
描述:{$description}什么时候可以不转义:
- 内容由系统生成,不来自用户输入
- 内容已经过验证和清理
- 内容来源完全可信
<!-- 如果变量包含 HTML 内容,需要根据情况处理 -->
<!-- 情况1:HTML 来自用户输入,必须转义 -->
用户提交的 HTML:{$userHtml|escape}
<!-- 情况2:HTML 来自系统生成,且需要渲染 -->
系统生成的 HTML:{$systemHtml}
<!-- 注意:确保 $systemHtml 的内容是安全的 -->最佳实践:
- 如果 HTML 来自用户输入,必须转义(显示为纯文本)
- 如果 HTML 来自系统生成,且需要渲染,可以不转义(但需确保内容安全)
- 如果允许用户输入 HTML,应使用 HTML 清理库(如 HTMLPurifier)处理后再输出
转义功能通过 filters 插件提供:
// 加载 filters 插件
require_once 'plugins/filters.php';
// 或通过 builtin 插件加载
require_once 'plugins/builtin.php';转义实现方式:
// 内部实现
htmlspecialchars($var, ENT_QUOTES, 'UTF-8')-
用户输入必须转义:
<!-- ✅ 正确:转义用户输入 --> {$userInput|escape} <!-- ❌ 错误:不转义用户输入 --> {$userInput}
-
系统内容可不转义:
<!-- ✅ 可以:系统生成的内容 --> {$systemTitle} {$systemDescription} -
HTML 内容谨慎处理:
<!-- ✅ 推荐:用户 HTML 转义为纯文本 --> {$userHtml|escape} <!-- ⚠️ 谨慎:系统 HTML 不转义(需确保安全) --> {$systemHtml}
-
使用过滤器链:
<!-- 可以组合多个过滤器 --> {$username|upper|escape} <!-- 先转大写,再转义 --> {$description|truncate:50|escape} <!-- 先截断,再转义 -->
<!-- 危险:用户输入未转义 -->
用户名:{$username}
评论:{$comment}风险:如果用户输入 <script>alert('XSS')</script>,会导致 XSS 攻击。
<!-- 安全:用户输入已转义 -->
用户名:{$username|escape}
评论:{$comment|escape}<!-- 不必要:系统内容不需要转义 -->
标题:{$systemTitle|escape}说明:虽然不会造成安全问题,但会显示 HTML 实体(如 <),影响显示效果。
<!-- 正确:系统内容不转义 -->
标题:{$systemTitle}- 所有用户输入是否都使用了
|escape转义? - 表单提交的数据是否都转义了?
- URL 参数、Cookie 数据是否都转义了?
- 数据库存储的用户数据输出时是否转义了?
- HTML 内容是否根据来源正确处理了(用户输入转义,系统生成可不转义)?
// 可以使用完全开放模式(默认),方便调试
$GLOBALS['user'] = $user;
$html = Zandy_Template::outString('template.htm', $tplDir, $cacheDir);// 推荐:白名单模式
$GLOBALS['siteConf']['template_vars_mode'] = 'whitelist';
$GLOBALS['siteConf']['template_vars_whitelist'] = ['user', 'data', 'items'];
$GLOBALS['user'] = $user;
$html = Zandy_Template::outString('template.htm', $tplDir, $cacheDir);// 推荐:显式传递模式
$html = Zandy_Template::outString('template.htm', $tplDir, $cacheDir, false, [
'user' => $user,
'data' => $data
]);如果模板来源不可信,必须:
-
使用显式传递模式:
$html = Zandy_Template::outString('template.htm', $tplDir, $cacheDir, false, [ 'user' => $user ]);
-
禁用 PHP 代码块(如果可能):
- 在模板解析阶段过滤
<!--{php}-->语法 - 或使用模板验证机制
- 在模板解析阶段过滤
-
严格验证模板路径:
- 确保模板文件在允许的目录内
- 使用白名单机制限制可包含的文件
- 生产环境是否使用白名单模式或显式传递模式?
- 模板文件权限是否正确(只读)?
- 缓存目录权限是否正确(可写但限制访问)?
- 模板路径配置是否正确?
- 如果模板来源不可信,是否禁用了 PHP 代码块?
- 是否使用了
includeTemplate()或getTemplateVars()而不是直接extract($GLOBALS)?