ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Twig raw 过滤器:标记输出为“安全值“以绕过自动转义

Twig raw 过滤器:标记输出为“安全值“以绕过自动转义 后端【免费下载链接】TwigTwig, the flexible, fast, and secure template language for PHP项目地址https://gitcode.com/gh_mirrors/tw/Twig点击查看免费下载raw是 Twig 中用于标记变量为安全值的过滤器在启用了自动转义auto-escaping的环境中被raw作为最后一个过滤器处理的变量将按原样输出、不再被转义。本文基于 raw 过滤器官方文档 展开并结合当前仓库源码解释它零运行时成本的实现原理、raw必须位于过滤器链末端的原因以及它与Markup类、escape过滤器等安全机制的协同关系帮助你在模板中安全地输出已转义内容如json_encode的结果而不产生二次转义。一、raw 过滤器的基本用法原始文档doc/filters/raw.rst给出的核心语义只有一句话raw过滤器将值标记为 safe安全。具体含义是在启用自动转义的环境中当raw是该变量上应用的最后一个过滤器时该变量不会被转义{% autoescape %} {{ var|raw }} {# var wont be escaped #} {% endautoescape %}autoescape标签本身的作用域与策略html、js、false等详见 autoescape 标签文档。文档中强调了一个容易忽略的细节ifrawis the last filter applied to it当raw是最后应用的过滤器时。这不是文档措辞的随意性而是由安全值safe analysis的编译期推导规则决定的这一点会在第三节结合源码解释。一个实用的推论在未启用自动转义的环境中{{ var|raw }}与{{ var }}的输出完全相同——因为raw本质上是一个编译期的身份变换它不会改变值本身只是向编译器声明这个值已经安全。二、源码实现raw 是一个零成本的编译期标记2.1 过滤器注册没有可调用对象raw过滤器并非由 CoreExtension 提供而是由负责转义体系的 EscaperExtension 注册new TwigFilter(escape, [EscaperRuntime::class, escape], [is_safe_callback [self::class, escapeFilterIsSafe]]), new TwigFilter(e, [EscaperRuntime::class, escape], [is_safe_callback [self::class, escapeFilterIsSafe]]), new TwigFilter(raw, null, [is_safe [all], node_class RawFilter::class]),这一行注册代码包含三个关键信息callable为nullraw没有任何运行时实现它不需要对值做转换is_safe [all]静态声明该过滤器对所有转义策略html、js、css、url等都安全这正是EscaperNodeVisitor判断该表达式是否需要再包一层escape的依据node_class RawFilter::class解析raw时生成的不是通用的FilterExpression节点而是专门的 RawFilter 节点。2.2 RawFiltercompile 时只做透传RawFilter 继承自FilterExpression其compile()方法的全部逻辑只有一行public function compile(Compiler $compiler): void { $compiler-subcompile($this-getNode(node)); }即编译{{ var|raw }}时只会把内层表达式var编译进输出不会生成任何调用raw函数的 PHP 代码。换言之raw过滤器在运行时的开销为零它的全部效果都发生在模板编译阶段——由节点访问器NodeVisitor读取is_safe [all]这个元数据来决定是否跳过转义包裹。另外FilterExpression::compile() 中保留了一段针对raw的兼容分支并自 Twig 3.11 起发出弃用提示Creating the raw filter via FilterExpression is deprecated; use RawFilter instead.。这说明当前版本中节点访问器必须为raw构造RawFilter实例而不能走通用过滤器节点路径。三、为什么 raw 必须是最后一个过滤器安全值的推导规则3.1 转义决策发生在编译期是否对某个PrintNode即{{ ... }}输出语句包上escape过滤器由 EscaperNodeVisitor 在遍历 AST 时决定进入AutoEscapeNode时把当前转义策略压入statusStackenterNode离开时弹出没有autoescape标签时则回落到环境的默认策略getDefaultStrategy可通过FileExtensionEscapingStrategy等机制按文件扩展名设置见 src/FileExtensionEscapingStrategy.php处理PrintNode时调用escapeExpression()核心判断是isSafeFor($type, $expression, $env)leaveNode、isSafeForreturn \in_array($type, $safe, true) || \in_array(all, $safe, true);只要表达式的安全集合包含当前策略或all就跳过escape包裹。而表达式的安全集合由 SafeAnalysisNodeVisitor 计算。3.2 过滤器链的安全集合是从外到内传播的SafeAnalysisNodeVisitor 对 FilterExpression 的处理 揭示了raw位置敏感性的根源$safe $filter-getSafe($node-getNode(arguments)); // ... if (!$safe) { $safe $this-intersectSafe($this-getSafe($node-getNode(node)), $filter-getPreservesSafety()); } $this-setSafe($node, $safe);若过滤器自身声明了安全策略如raw声明了[all]整个过滤器表达式就直接安全若外层过滤器未声明安全如upper、e则安全集合等于内层表达式的安全集合与外层过滤器保持安全的能力preservesSafety的交集——通常交集为空。由此可以精确理解文档中raw 是最后一个过滤器才有效的含义{{ safeHtml|raw }} {# raw 在最外层 → 安全集合为 [all] → 不转义 #} {{ safeHtml|raw|upper }} {# 外层 upper 未声明安全交集为空 → 仍会被转义 #} {{ var|e|raw }} {# raw 在最外层 → 不再转义e 的转义仍保留在值里 #}这也是为什么raw常被写在过滤器链的末尾——它是最终裁决而不是中间步骤。3.3 与 escape 过滤器的协同escape别名e过滤器注册时使用了is_safe_callback见 EscaperExtension运行时实现位于 EscaperRuntime。autoescape 文档 还提到两条相关规则与raw的用法直接相关不会二次转义Twig 足够智能不会用escape过滤器对已按相同策略转义过的值再次转义静态表达式不转义{% set hello strongHello/strong %}{{ hello }}会原样输出strongHello/strong因为编译器能识别字面量是安全的。四、与 Markup 类、安全类注册表的对比除模板内用|raw标记外Twig 还支持在PHP 侧声明一个值是安全的典型代表是 Markup 类class Markup implements \Countable, \JsonSerializable, \Stringable { private $content; private ?string $charset; public function __construct($content, $charset) { ... } }其类注释src/Markup.php明确说明Markup实例及既有子类被视为已被判定可安全输出的内容在 Twig 沙箱中对其方法调用与属性访问会绕过SecurityPolicy的允许清单。此外EscaperRuntime 维护了一份安全类注册表safeClasses/safeLookup通过addSafeClass()注册从源码结构看运行时转义会借助该注册表识别Markup这类天生安全的值并跳过转义。可以这样对比两种声明安全的途径途径位置适用场景{{ value\|raw }}模板中值在模板上下文里已确认安全如缓存的 HTML 片段、json_encode输出Markup实例 /addSafeClass()PHP 代码中在业务层就确定某类对象的内容已转义统一注册后模板侧无需再写raw注意raw只影响转义决策它本身不改变值的类型而Markup还会附带沙箱信任等运行时语义。两者配合时Markup值再经过|raw输出仍是安全的但实践中二选一即可。五、实战示例来自集成测试的真实用例当前仓库的集成测试用少量用例覆盖了raw的典型使用姿势基础用例tests/Fixtures/filters/raw.test{{ br|raw }}数据为[br br]期望输出br——变量排除了自动转义。JSON 场景tests/Fixtures/filters/json_encode.test{{ foo|json_encode|raw }}。json_encode生成的 JSON 串本身不是 HTML 安全内容若在autoescape块内直接输出会被再次转义如引号变成quot;因此需要|raw收尾。URL 场景tests/Fixtures/filters/urlencode.test{{ {...}|url_encode|raw }}同理防止等字符被 HTML 策略二次转义。与apply标签组合tests/Fixtures/tags/apply/json_encode.test{% apply json_encode|raw %}test{% endapply %}说明raw位于apply过滤器链的末端与文档最后一个过滤器的规则一致。这些用例由 tests/IntegrationTest.php 统一驱动执行可直接作为验证raw行为的参照。六、安全注意事项raw是一份责任声明不是转义豁免开关。它告诉编译器这个值已经安全但编译器无法替你验证值的真实性——对未净化的用户输入使用|raw会直接引入 XSS 风险。默认应让自动转义生效仅在内容确实已经转义如经过escape处理并存储、json_encode序列化、白名单模板生成的 HTML 片段时才使用raw。注意策略匹配。raw的安全声明是[all]即声明对所有策略html、js、css、url都安全。若一段内容只做过 HTML 转义却在{% autoescape js %}块中用|raw输出它实际上并未按js策略转义——此时输出是否安全取决于内容本身。优先选择 PHP 侧注册安全类如果某类对象的内容在业务层就保证已转义用 Markup 或EscaperRuntime::addSafeClass()统一声明可以显著减少模板中散落各处的|raw让安全边界集中在代码侧管理。小结raw过滤器的完整工作链路是EscaperExtension 以is_safe [all]注册且callable为null→ 解析器生成 RawFilter 节点编译时仅透传子表达式零运行时开销→ SafeAnalysisNodeVisitor 将最外层过滤器安全集合为[all]记入 AST → EscaperNodeVisitor 据此跳过escape包裹。理解这条链路后你就能准确把握为什么raw要放在过滤器链末尾并把它与Markup、escape、autoescape策略正确组合在防转义过度与防 XSS之间取得平衡。赞分享后端【免费下载链接】TwigTwig, the flexible, fast, and secure template language for PHP项目地址https://gitcode.com/gh_mirrors/tw/Twig点击查看免费下载相关推荐Twig nl2br 过滤器详解HTML 换行转换与自动转义的前置转义机制Twig nl2br 过滤器详解HTML 换行转换与自动转义的前置转义机制 本文以 Twig 官方文档中的 nl2br 过滤器为切入点完整讲解其在模板中的用后端终极LFI过滤器绕过指南2024年最新Payload与实战技巧终极LFI过滤器绕过指南2024年最新Payload与实战技巧 Local File Inclusion本地文件包含LFI漏洞是Web应用安全中最常见且网络安全应用安全渗透测试告别手动复制粘贴3步高效获取国家中小学智慧教育平台电子课本告别手动复制粘贴3步高效获取国家中小学智慧教育平台电子课本 你是否曾经为了获取电子课本而反复复制粘贴网址是否因为下载速度慢、文件命名混乱而烦恼tchMat网页爬虫教育上一篇戴森球计划8000蓝图库从零开始打造高效星际工厂的终极指南下一篇conda init 命令详解Shell 初始化机制、参数全解与源码级实现分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表