在 Umbraco 13 中缓解 CVE-2025-67288(如果您觉得有必要)
Source: Dev.to
请提供您希望翻译的完整文本内容(除代码块和 URL 之外),我将为您翻译成简体中文并保持原有的格式和 Markdown 语法。
概述
这篇文章是对最近发布的 CVE 的回应,该 CVE 声称 Umbraco 存在严重漏洞。下面是对该说法大体不准确的详细拆解,以及您可以在 Umbraco 项目中实施的实际缓解策略。
TL;DR
- 该 CVE 基于一个 PDF‑based proof‑of‑concept (PoC),其在 浏览器内部 执行 JavaScript,而非在服务器上。
- Umbraco 从未以任何方式处理 PDF,从而导致站点本身出现 remote code execution (RCE) 或 cross‑site scripting (XSS)。
- 该问题实际上是 browser‑level 的关注点(PDF 查看器的沙箱),而非 Umbraco 的缺陷。
- 您仍然可以添加 file‑stream security analyzer,以防止恶意上传(例如,嵌入 JavaScript 的 PDF、SVG 等)。
为什么 CVE 不适用于 Umbraco
| 声称 | 实际情况 |
|---|---|
| 上传精心制作的 PDF → JavaScript 运行 | JavaScript 仅在 客户端浏览器(PDF 查看器沙箱)中运行。 |
| Umbraco 处理 PDF 并执行代码 | Umbraco 不 解析或执行 PDF 内容。 |
| 远程代码执行 → CVSS 10.0 | 没有服务器端代码执行,因此 CVSS 分数 被夸大。 |
| 因为 JavaScript 运行而产生 XSS | 脚本在 Umbraco 域之外(沙箱)运行,所以 不是 XSS 漏洞。 |
| 浏览器 API(fetch、document.cookie)可用 | PDF 查看器 限制 对危险 API 的访问;仅暴露 PDF 专用 API(例如 app.alert())。 |
| 如果恶意 JavaScript 能运行,那就是浏览器的问题 | 正是如此——漏洞在 浏览器,而不是 Umbraco。 |
注意: Chromium 甚至有一个 FAQ,标题为 “在 PDF 文件中执行 JavaScript 是否意味着存在 XSS 漏洞?”——答案是 否。
实际风险
- 社会工程 / 钓鱼:精心制作的 PDF 可能诱骗用户点击恶意链接,但这是一种 通用 PDF 查看器问题,并非 Umbraco 缺陷。
在 Umbraco 中缓解恶意文件上传
Umbraco 提供了一个简单的可扩展点:实现 IFileStreamSecurityAnalyzer。平台会对每个上传的文件流调用你的分析器,从而让你能够拒绝不安全的内容。
接口定义
public interface IFileStreamSecurityAnalyzer
{
/// <summary>
/// Indicates whether the analyzer should process the file.
/// The implementation should be considerably faster than IsConsideredSafe.
/// </summary>
/// <param name="fileStream">The uploaded file stream.</param>
/// <returns>True if the analyzer wants to handle this file.</returns>
bool ShouldHandle(Stream fileStream);
/// <summary>
/// Analyzes whether the file content is considered safe.
/// </summary>
/// <param name="fileStream">Needs to be a read/write seekable stream.</param>
/// <returns>True if the file is considered safe.</returns>
bool IsConsideredSafe(Stream fileStream);
}
官方文档中包含了一个用于防御恶意 SVG 文件的示例。
以下是一个 针对 PDF 的实现,它会扫描嵌入的 JavaScript。
必需的包
dotnet add package FileSignatures # For quick file‑type detection via header signatures
dotnet add package PdfPig # Lightweight PDF parser (MIT‑licensed)
PDF 安全分析器(兼容 Umbraco)
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Logging;
using PdfPig;
using PdfPig.Core;
using PdfPig.Parser;
using PdfPig.Tokens;
using Umbraco.Cms.Core.IO;
internal class PdfSecurityAnalyzer(
ILogger logger,
IFileFormatInspector fileFormatInspector) : IFileStreamSecurityAnalyzer
{
// PDF dictionary keys that indicate potentially dangerous actions
private static readonly HashSet<string> _suspiciousKeys = new()
{
"AA", // Additional Actions
"OpenAction", // Actions triggered on document open
"JS", // JavaScript stream
"JavaScript", // Explicit JavaScript name
"Launch", // Launch external applications
"SubmitForm", // Form submission (possible data exfiltration)
"ImportData" // Import external data
};
// -----------------------------------------------------------------
// IFileStreamSecurityAnalyzer implementation
// -----------------------------------------------------------------
public bool ShouldHandle(Stream fileStream)
{
// Quick header check – PDF files start with "%PDF-"
if (!fileFormatInspector.IsPdf(fileStream))
return false;
// Reset stream position for later processing
fileStream.Position = 0;
return true;
}
public bool IsConsideredSafe(Stream fileStream)
{
fileStream.Position = 0;
var options = new ParsingOptions { UseLenientParsing = true };
try
{
using var document = PdfDocument.Open(fileStream, options);
var visited = new HashSet<IndirectReference>();
// Start scanning from the catalog (root of the PDF structure)
if (ContainsSuspiciousContent(document.Structure.Catalog.CatalogDictionary, document, visited))
return false; // Unsafe content found
return true; // No suspicious keys detected
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to parse PDF for security analysis");
return false; // Parsing failure → treat as unsafe
}
}
// -----------------------------------------------------------------
// Recursive PDF token inspection
// -----------------------------------------------------------------
private bool ContainsSuspiciousContent(IToken token, PdfDocument document, HashSet<IndirectReference> visited)
{
return token switch
{
null => fals
Source: …
e,
DictionaryToken dict => CheckDictionary(dict, document, visited),
ArrayToken array => CheckArray(array, document, visited),
IndirectReferenceToken refToken => CheckIndirectReference(refToken, document, visited),
_ => false
};
}
private bool CheckDictionary(DictionaryToken dict, PdfDocument document, HashSet<IndirectReference> visited)
{
foreach (var kvp in dict.Data)
{
// If the key itself is suspicious, reject immediately
if (_suspiciousKeys.Contains(kvp.Key.Value))
return true;
// Recursively inspect the value token
if (ContainsSuspiciousContent(kvp.Value, document, visited))
return true;
}
return false;
}
private bool CheckArray(ArrayToken array, PdfDocument document, HashSet<IndirectReference> visited)
{
foreach (var item in array.Data)
{
if (ContainsSuspiciousContent(item, document, visited))
return true;
}
return false;
}
private bool CheckIndirectReference(IndirectReferenceToken refToken, PdfDocument document, HashSet<IndirectReference> visited)
{
var reference = new IndirectReference(refToken.ObjectNumber, refToken.GenerationNumber);
if (visited.Contains(reference))
return false; // Prevent infinite loops on circular references
visited.Add(reference);
var resolved = document.GetObject(reference);
return ContainsSuspiciousContent(resolved, document, visited);
}
}
工作原理
ShouldHandle– 使用FileSignatures验证文件确实是 PDF(检查%PDF-头部)。IsConsideredSafe–- 使用 PdfPig 打开 PDF(宽容的解析以避免误报)。
- 遍历整个 PDF 对象图,查找
_suspiciousKeys中列出的 可疑字典键。 - 只要发现任意此类键,方法即返回
false(拒绝上传)。 - 任何解析异常也会导致拒绝,以安全为先。
最终思考
- 此CVE并不代表真实的Umbraco漏洞;这是一种对浏览器如何对PDF JavaScript进行沙箱隔离的误解。
- 尽管如此,防御恶意上传是最佳实践。
IFileStreamSecurityAnalyzer接口为您提供了一种干净、可测试的方式来强制执行该策略。 - 上面的示例代码可以直接放入任何 Umbraco 项目,通过 DI 注册后,会自动审查上传的 PDF 是否包含嵌入的 JavaScript 或其他风险操作。
参考文献
- CWE‑434 – 不受限制的危险类型文件上传
- Chromium FAQ – “在 PDF 文件中执行 JavaScript 是否意味着存在 XSS 漏洞?”
- PDF Viewer Design Doc (Chromium) – 有关沙箱隔离和允许的 JavaScript API 的详细信息。
PDF 安全分析器 – 整理后的 Markdown
下面是代码及其说明的整洁版本。
保留了原始结构和内容,只对格式和少量措辞进行了改进。
分析器实现
// Main entry point – decides whether a token contains suspicious content
private bool ContainsSuspiciousContent(
PdfToken token,
PdfDocument document,
HashSet<IndirectReference> visited)
{
return token switch
{
StreamToken stream => ContainsSuspiciousContent(stream.StreamDictionary, document, visited),
ObjectToken objToken => ContainsSuspiciousContent(objToken.Data, document, visited),
_ => false
};
}
// ---------------------------------------------------------------------
// Dictionary handling
private bool CheckDictionary(
DictionaryToken dict,
PdfDocument document,
HashSet<IndirectReference> visited)
{
// Look for suspicious keys
foreach (var keyName in dict.Data.Keys)
{
if (_suspiciousKeys.Contains(keyName))
return true;
// Subtype check: /S /JavaScript
if (keyName == "S" &&
dict.TryGet(NameToken.S, out NameToken subtype) &&
subtype.Data == "JavaScript")
{
return true;
}
}
// Recurse into all dictionary values
return dict.Data.Values.Any(value => ContainsSuspiciousContent(value, document, visited));
}
// ---------------------------------------------------------------------
// Array handling
private bool CheckArray(
ArrayToken array,
PdfDocument document,
HashSet<IndirectReference> visited)
{
return array.Data.Any(item => ContainsSuspiciousContent(item, document, visited));
}
// ---------------------------------------------------------------------
// Indirect reference handling
private bool CheckIndirectReference(
IndirectReferenceToken refToken,
PdfDocument document,
HashSet<IndirectReference> visited)
{
if (visited.Contains(refToken.Data))
return false;
visited.Add(refToken.Data);
try
{
var actualToken = document.Structure.GetObject(refToken.Data);
return ContainsSuspiciousContent(actualToken, document, visited);
}
catch
{
// If the object cannot be resolved we treat it as non‑suspicious
return false;
}
}
// ---------------------------------------------------------------------
// File‑type detection
public bool ShouldHandle(Stream fileStream)
{
var format = fileFormatInspector.DetermineFileFormat(fileStream);
return format is Pdf;
}
注册分析器
public class RegisterFileStreamSecurityAnalysers : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
// Limit the inspector to PDF files for better performance
var recognised = FileFormatLocator.GetFormats().OfType<Pdf>();
var inspector = new FileFormatInspector(recognised);
builder.Services.AddSingleton(inspector);
// Register the PDF security analyzer
builder.Services.AddSingleton<IFileStreamSecurityAnalyzer, PdfSecurityAnalyzer>();
}
}
注意:
FileFormatInspector被配置为仅查找 PDF 文件,这可以提升性能。
你可以添加其他文件类型,或让它扫描库支持的所有格式——详情请参阅 GitHub 仓库。
如何测试
- 在项目中实现上述代码。
- 上传包含 JavaScript 的 PDF(例如原讨论中链接的良性示例)。
- 分析器将拒绝该文件,并返回类似原教程中展示的错误信息。
警告:
可能会出现一些误报;本示例的目的是演示如何检测并处理这些情况。