💡 전체 작동 예제가 GitHub에 있습니다:
서명 시 외부 리소스 건너뛰기 - .NET
소개
Word 문서에는 파일에 포함되지 않은 그림이 들어 있을 수 있습니다. 문서는 주소를 보관하고, 이를 여는 프로그램이 해당 주소를 가져옵니다. 데스크톱에서는 이 기능이 유용합니다—소스가 바뀌면 이미지도 업데이트됩니다. 그러나 업로드를 허용하는 서버에서는 파일을 보낸 사람이 인프라가 요청할 URL을 결정한다는 의미가 됩니다.
안전한 문서 로딩은 .NET용 GroupDocs.Signature 동작으로, 이러한 요청을 거부합니다. 버전 26.9부터 LoadOptions.SkipExternalResources 기본값이 true로 설정됩니다. 이 문서는 동일한 문서에 대해 세 가지 로드 모드를 비교하고, 모든 호스트를 허용하지 않고 하나의 호스트만 허용하는 방법을 보여주며, 신뢰할 수 없는 파일에 서명할 때 네트워크 접근이 전혀 필요하지 않은 이유를 설명합니다.
왜 이것이 겉보다는 더 중요한가
이 공격은 서버 측 요청 위조(SSRF) 라는 이름을 가지고 있으며, 구체적인 형태가 세 가지 있습니다.
- 인터넷에서는 접근할 수 없지만 서버에서는 접근 가능한 내부 주소가 있어, 조작된 문서가
http://169.254.169.254/혹은 로컬호스트의 관리자 엔드포인트를 가져오게 할 수 있습니다. 결과를 어떻게 처리하느냐에 따라 정보가 유출될 수 있습니다. - 문서에 포함된 UNC 경로가 Windows 호스트가 외부 인증을 시도하도록 만들 수 있어, 공격자가 제어하는 서버에 자격 증명이 전달됩니다.
- 응답이 전혀 없는 호스트에 대한 링크는 로드 스레드를 타임아웃될 때까지 잡아두어, 워커 풀을 소모하는 저비용 방법이 됩니다.
처음엔 이 문제가 이론적인 것이라고 생각했지만, 실제 테스트 문서가 전혀 외부 요청을 할 필요가 없는 서비스에서 이미지를 가져오는 모습을 보았습니다. 이 문제는 문서 라이브러리의 버그가 아니라, 포맷 자체가 링크를 따라가도록 요구하기 때문에 발생합니다. 질문은 서버가 이를 따라야 하는가 여부뿐입니다.
방법 1 - 새로운 기본값
LoadOptions를 전혀 사용하지 않을 때:
using var signature = new Signature(sourcePath);
return SavePagePreview(signature, previewPath);
아무것도 가져오지 않습니다. 프리뷰는 링크된 그림이 있을 자리에는 빈 자리 표시자를 표시하고, PNG 파일 크기도 원래보다 작아집니다. 이 크기 차이가 요청이 전혀 발생하지 않았다는 가장 직관적인 증거입니다.
외부 리소스로 간주되는 항목은 무엇인가요? 임베드되지 않은 링크된 그림, INCLUDEPICTURE 필드, 프레젠테이션 및 스프레드시트의 링크된 그림, SVG가 참조하는 이미지와 스타일시트가 해당됩니다. 임베드된 콘텐츠는 파일에 이미 포함되어 있으므로 영향을 받지 않습니다.
방법 2 - 하나의 주소 화이트리스트
많은 문서가 합법적인 곳을 가리킵니다: 회사 CDN, 내부 이미지 서버, 템플릿 저장소 등. 이를 허용하고 그 외는 차단합니다:
var loadOptions = new LoadOptions
{
WhitelistedResources = new List<string> { trustedAddress }
};
using var signature = new Signature(sourcePath, loadOptions);
매칭 규칙에 주의가 필요합니다. 이는 리소스 주소에 대해 대소문자를 구분하지 않는 부분 문자열 검사이므로, 짧은 조각은 위험합니다: github는 github.attacker.example/payload.png을 의도한 호스트와 동일하게 매치됩니다. 스킴, 호스트, 경로 전체를 사용하세요—예제에서는 raw.githubusercontent.com/groupdocs-signature/를 화이트리스트에 추가합니다.
방법 3 - 모두 허용
버전 26.9 이전 동작으로, 아직 사용할 수 있습니다:
var loadOptions = new LoadOptions { SkipExternalResources = false };
자체 애플리케이션이 만든 문서에 적합합니다. 주의할 점은 오래된 LoadExternalResources 속성이 반대 의미를 가진다는 것입니다. 따라서 SkipExternalResources = false는 LoadExternalResources = true와 동일합니다. 오래된 속성에서 값을 복사하면 보안 설정이 뒤바뀌지만 오류가 표시되지 않습니다.
세 가지 방법 비교: 언제 사용해야 할까
| 모드 | 가장 적합한 경우 | 주요 장점 | 제한 사항 |
|---|---|---|---|
| 기본 (skip) | 사용자 업로드, 이메일, 파트너 파일 | 외부 요청이 불가능 | 링크된 그림이 자리 표시자로 표시 |
| 화이트리스트 | 귀하가 소유한 호스트에 링크된 문서 | 정상적인 링크가 작동 유지 | 부분 문자열 매칭에 긴, 구체적인 조각 필요 |
| 모두 허용 | 귀하의 시스템이 생성한 파일 | 미리보기가 이전과 동일하게 표시 | 기본값이 제거한 SSRF 노출을 복원 |
서명에 리소스가 필요한가?
필요하지 않으며, 이것이 실제적인 이점입니다. QR 코드 서명은 기본 로드 설정으로 적용되며, 문서를 로드·서명·저장하는 동안 외부 리소스가 요청되지 않습니다:
var options = new QrCodeSignOptions("Approved by GroupDocs.Signature")
{
EncodeType = QrCodeTypes.QR,
Left = 400,
Top = 50,
Width = 120,
Height = 120
};
SignResult result = signature.Sign(outputPath, options);
서명된 출력 파일은 링크를 그대로 유지하므로, 사용자가 나중에 문서를 열면 자신의 머신에서 그림이 해석됩니다. 스킵은 서버 측 정책이며, 문서 자체를 수정하는 것이 아니기 때문에 타인의 파일을 다룰 때 안전하게 적용할 수 있습니다.
업그레이드 시 변경되는 점
대부분의 서비스에서는 눈에 띄는 변화가 없으며, 이는 보안 기본값이 전역 동작을 바꾸는 경우 업그레이드 검토를 통과하기 어렵기 때문에 명시적으로 언급할 가치가 있습니다. 예외는 미리보기나 썸네일이 이전에는 링크된 그림을 보여주었지만 이제는 자리 표시자를 보여주는 경우입니다. 이는 의도된 동작이며, 호스트가 귀하의 것이라면 화이트리스트 항목을 추가하거나, 외부 문서라면 그대로 수용하면 됩니다.
샘플이 사용하는 가장 확실한 확인 방법은 동일한 문서를 세 가지 모드 모두에서 렌더링하고 출력 파일 크기를 비교하는 것입니다. 기본 모드와 화이트리스트 모드의 프리뷰 크기가 동일하면 어느 경우에도 리소스를 가져오지 않은 것이며, 이는 보통 해당 호스트가 머신에서 접근 불가능함을 의미합니다. 샘플은 바로 그 점을 알려주는 힌트를 출력합니다.
프리뷰 헬퍼, 명확하지 않을 때
위 세 가지 모드 중 두 개는 작은 헬퍼를 호출합니다. PreviewOptions가 경로를 받지 않기 때문에 보여줍니다:
var previewOptions = new PreviewOptions(
pageData => File.Create(previewPath),
(pageData, pageStream) => pageStream.Dispose())
{
PreviewFormat = PreviewOptions.PreviewFormats.PNG
};
signature.GeneratePreview(previewOptions);
두 개의 스트림 팩터리를 받습니다—페이지당 스트림을 생성하는 팩터리와 이를 해제하는 팩터리. 샘플 문서는 한 페이지만 있으므로 하나의 파일이 작성됩니다; 다중 페이지 입력의 경우 파일 이름에 페이지 번호를 넣지 않으면 마지막 페이지가 이전 파일을 덮어씁니다.
모범 사례
- 파트너가 제공한 파일을 포함해, 직접 생성하지 않은 모든 파일을 신뢰하지 않는 것으로 취급합니다.
- 화이트리스트 조각은 모호함이 없도록 충분히 길게 만들고, CDN이 변경될 때 검토합니다.
LoadExternalResources에 할당하던 값을SkipExternalResources에 직접 설정하지 마세요.- 설정값만으로 판단하지 말고 출력 파일 크기로 검증합니다; 설정이 올바르게 보이더라도 실제 요청이 발생했는지는 별개의 주장입니다.
SVG에 대한 고찰
별도로 언급할 가치가 있습니다. SVG는 일반적인 업로드 형식이면서 SSRF 벡터이기도 합니다. SVG는 URL을 통해 이미지와 스타일시트를 참조할 수 있으며, 이러한 참조도 외부 리소스로 간주됩니다—기본적으로 건너뛰고, 화이트리스트에 추가하거나 복원할 수 있습니다. 서버 측에서 SVG 아바타나 로고를 받아 렌더링하는 서비스는 바로 이 변경으로 보호됩니다.
파이프라인이 사용자로부터 SVG를 받는다면, 기본값(SkipExternalResources = true)이 바로 원하는 설정이며, 자체 템플릿이 자체 호스트에서 공유 스타일시트를 가져오는 경우에만 화이트리스트를 사용하면 됩니다.
결론
기본값이 바뀌어 위험한 동작은 명시적인 결정이 필요하고, 안전한 동작은 아무 설정도 필요하지 않게 되었습니다. 신뢰할 수 없는 입력에는 기본값을 유지하고, 자체 호스트가 관여하는 경우에만 좁게 화이트리스트를 적용하며, 서명 자체는 네트워크가 전혀 필요하지 않다는 점을 기억하세요. 샘플을 자체 문서에 한 번 실행해 보면 1분 정도 걸리며, 세 개의 파일 크기로 서비스가 어떤 리소스를 가져왔는지 정확히 알려줍니다.