解决Hexo博客中安全笔记模板语法冲突问题

问题背景

在使用Hexo搭建安全技术博客时,遇到了一个棘手的问题:博客中包含大量模板注入攻击相关的安全笔记,这些笔记中的示例代码包含了{{7*'7'}}{{ }}等模板语法,与Hexo使用的Nunjucks模板引擎产生了冲突。

错误现象

运行hexo g时出现以下错误:

1
FATAL Nunjucks Error: _posts/THM/外网打点/Injection Attacks.md [Line 138, Column 315] unexpected token: }}

根本原因分析

  1. Hexo渲染机制:Hexo使用Nunjucks模板引擎解析Markdown文件
  2. 语法冲突:安全笔记中的模板注入示例(如{{7*'7'}})被Nunjucks误认为是模板语法
  3. 解析失败:Nunjucks无法正确解析这些”恶意”模板语法,导致生成过程中断

解决方案演进

方案1:手动修复(临时方案)

最初采用手动修复的方式,将冲突的模板语法用`标签包围:

1
2
3
4
5
<!-- 原始内容 -->
{{7*'7'}}

<!-- 修复后 -->
{% raw %}{{7*'7'}}{% endraw %}
**优点**:简单直接,能够解决问题 **缺点**:需要手动维护,不适合大量文件 ### 方案2:修改原文件的自动化脚本(失败方案) 创建脚本自动修改原始Markdown文件:
1
2
3
// 扫描并修改原始文件中的模板语法
const regex = /{{[^}]*?}}|{{(?!.*}})/g;
content = content.replace(regex, match => `{% raw %}${match}{% endraw %}`);
**问题**: 1. 修改了原始文件内容,影响日常查看和编辑 2. 对于经常编辑的笔记来说,维护成本高 3. 破坏了文档的原始性 ### 方案3:渲染时临时修复(最终方案) 创建Hexo插件,在渲染过程中临时修复模板语法冲突,不修改原始文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Hexo 模板语法冲突修复插件
* 在渲染过程中临时修复模板语法冲突,不修改原始文件
*/

hexo.extend.filter.register('before_post_render', function(data) {
if (data.source.endsWith('.md')) {
data.content = fixTemplateConflicts(data.content);
}
return data;
});

function fixTemplateConflicts(content) {
// 智能识别并修复模板语法冲突
const lines = content.split('\n');
let inCodeBlock = false;

for (let i = 0; i < lines.length; i++) {
let line = lines[i];

// 跳过代码块内容
if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock;
continue;
}

if (inCodeBlock) continue;

// 修复单独的 {{
if (line.includes('{{') && !line.includes('{% raw %}')) {
line = line.replace(/{{(?![^}]*}})/g, '{% raw %}{{{% endraw %}');
}

// 修复完整的 {{...}}
if (line.includes('{{') && line.includes('}}') && !line.includes('{% raw %}')) {
line = line.replace(/{{[^}]*?}}/g, match => `{% raw %}${match}{% endraw %}`);
}

lines[i] = line;
}

return lines.join('\n');
}
## 技术实现细节 ### 1. Hexo插件机制 - 将脚本放在`scripts/`目录下,Hexo会自动加载 - 使用`before_post_render`过滤器拦截渲染过程 - 在内容渲染前临时修复模板语法冲突 ### 2. 智能识别策略 - **代码块检测**:跳过```代码块内的内容 - **语法识别**:区分单独的`{% raw %}{{`和完整的`{{...}}{% endraw %}

  • 避免重复:跳过已经被`