CVE-2025-67288을 Umbraco 13에서 완화하기 (필요하다고 생각되면)
Source: Dev.to
위에 제공된 소스 링크 외에 번역할 텍스트가 포함되어 있지 않습니다. 번역을 원하는 본문을 제공해 주시면 한국어로 번역해 드리겠습니다.
개요
이 게시물은 최근 발표된 CVE에 대한 응답이며, Umbraco에서 심각한 취약점이 있다고 주장합니다. 아래는 해당 주장이 대부분 부정확한 이유에 대한 자세한 분석과 Umbraco 프로젝트에 적용할 수 있는 실용적인 완화 전략을 제공합니다.
요약
- CVE는 서버가 아닌 브라우저 내부에서 JavaScript를 실행하는 **PDF‑기반 증명 개념(PoC)**에 기반합니다.
- Umbraco는 PDF를 처리하여 사이트 자체에서 원격 코드 실행(RCE) 또는 **교차 사이트 스크립팅(XSS)**이 발생하도록 하지 않습니다.
- 이 문제는 실제로 브라우저‑수준의 우려사항(PDF 뷰어 샌드박싱)이며, Umbraco 결함이 아닙니다.
- 여전히 악성 업로드(예: 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에 있는 것이 아닙니다. |
Note: Chromium은 “PDF 파일에서 JavaScript를 실행한다는 것이 XSS 취약점을 의미하나요?” 라는 FAQ를 가지고 있으며, 답은 아니오입니다.
실제 위험
- 사회공학 / 피싱: 조작된 PDF가 사용자를 속여 악성 링크를 클릭하게 할 수 있지만, 이는 일반적인 PDF 뷰어 문제이며 Umbraco 결함이 아닙니다.
Source: …
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
```csharp
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에 대한 세부 정보.
Source: …
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>();
}
}
Note:
FileFormatInspector는 PDF만 찾도록 구성되어 있어 성능이 향상됩니다.
추가 파일 형식을 넣거나 라이브러리가 지원하는 모든 형식을 스캔하도록 할 수 있습니다 – 자세한 내용은 GitHub 저장소를 참고하세요.
테스트 방법
- 위 코드를 프로젝트에 구현합니다.
- JavaScript가 포함된 PDF(예: 원래 토론에 링크된 무해한 예시)를 업로드합니다.
- 분석기가 파일을 거부하고 원본 튜토리얼에 표시된 것과 유사한 오류 메시지를 반환합니다.
Caveat:
일부 오탐이 발생할 수 있습니다; 이 예제는 그러한 상황을 감지하고 처리하는 방법을 보여주기 위한 것입니다.