💡 Ejemplo completo en funcionamiento disponible en GitHub:
skip-external-resources-when-signing-dotnet
Introducción
Un documento de Word puede contener una imagen que no está en el archivo. El documento guarda una dirección, y quien lo abre recupera esa dirección. En un escritorio esto es una característica: la imagen se actualiza cuando la fuente lo hace. En un servidor que acepta cargas, significa que la persona que te envió el archivo decide qué URLs solicita tu infraestructura.
La carga segura de documentos es un comportamiento de GroupDocs.Signature para .NET que rechaza esas solicitudes. A partir de la versión 26.9, LoadOptions.SkipExternalResources tiene como valor predeterminado true. Este artículo compara los tres modos de carga con el mismo documento, muestra cómo permitir un host sin permitir todos, y explica por qué firmar un archivo no confiable no necesita acceso a la red en absoluto.
Por Qué Esto Importa Más de lo Que Parece
El ataque tiene un nombre: falsificación de solicitudes del lado del servidor (SSRF) y tres formas concretas.
Una dirección interna que es inalcanzable desde internet es alcanzable desde tu servidor, de modo que un documento manipulado puede hacer que tu servicio recupere http://169.254.169.254/ o un punto final de administración en localhost y, según lo que hagas con el resultado, filtrarlo. Una ruta UNC en un documento puede provocar que un host Windows se autentique saliente, entregando credenciales a un servidor controlado por el atacante. Y un enlace a un host que simplemente nunca responde mantiene ocupado el hilo de carga hasta que se agota el tiempo de espera, lo que es una forma barata de agotar un grupo de trabajadores.
Supuse que esto era una preocupación teórica hasta que vi un documento de prueba extraer una imagen a través de un servicio que no tenía ninguna razón para hacer solicitudes salientes. Ninguno de estos casos requiere un error en la biblioteca de documentos. Seguir un enlace es lo que el formato solicita; la pregunta es solo si tu servidor debe obedecerlo.
Método 1 – El Nuevo Predeterminado
Sin LoadOptions en absoluto:
using var signature = new Signature(sourcePath);
return SavePagePreview(signature, previewPath);
No se recupera nada. La vista previa muestra un marcador de posición vacío donde estaría la imagen vinculada, y el PNG es más pequeño de lo que sería de otro modo. Esa diferencia de tamaño es la prueba más conveniente disponible de que no se realizó ninguna solicitud fuera de la máquina.
¿Qué características cuentan como externas? Imágenes vinculadas en lugar de incrustadas, campos INCLUDEPICTURE, imágenes vinculadas en presentaciones y hojas de cálculo, y las imágenes y hojas de estilo que referencia un SVG. El contenido incrustado no se toca; ya está en el archivo.
Método 2 – Lista Blanca de una Dirección
Muchos documentos enlazan a algo legítimo: un CDN de la empresa, un servidor de imágenes interno, una tienda de plantillas. Permite eso y nada más:
var loadOptions = new LoadOptions
{
WhitelistedResources = new List<string> { trustedAddress }
};
using var signature = new Signature(sourcePath, loadOptions);
La regla de coincidencia merece atención. Es una prueba de subcadena insensible a mayúsculas contra la dirección del recurso, lo que significa que un fragmento corto es peligroso: github coincide con github.attacker.example/payload.png tan fácilmente como con el host que pretendías. Usa un esquema, un host y una ruta; el ejemplo incluye en la lista blanca raw.githubusercontent.com/groupdocs-signature/.
Método 3 – Permitir Todo
El comportamiento anterior a la 26.9, todavía disponible:
var loadOptions = new LoadOptions { SkipExternalResources = false };
Razonable para documentos que tu propia aplicación generó. Una trampa que vale la pena señalar: la propiedad obsoleta LoadExternalResources tiene la polaridad opuesta, por lo que SkipExternalResources = false es lo que reemplaza a LoadExternalResources = true. Copiar un valor de la propiedad antigua invierte tu postura de seguridad sin que aparezca ningún error que lo indique.
Comparación de los Tres: Cuándo Usar Cada Uno
| Modo | Mejor Para | Ventajas Clave | Limitaciones |
|---|---|---|---|
| Predeterminado (omitir) | cargas de usuarios, correo electrónico, archivos de socios | no es posible ninguna solicitud saliente | las imágenes vinculadas se muestran como marcadores de posición |
| Lista Blanca | documentos que enlazan a un host que posees | mantiene los enlaces legítimos funcionando | la coincidencia por subcadena necesita un fragmento largo y específico |
| Permitir todo | archivos generados por tus propios sistemas | las vistas previas se ven exactamente como antes | restaura la exposición SSRF que el predeterminado eliminó |
¿Qué pasa con la firma? ¿Necesita los recursos?
No, y este es el beneficio práctico. Una firma de código QR se aplica con la configuración de carga predeterminada y no se solicita ningún recurso externo mientras el documento se carga, firma o guarda:
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);
La salida firmada conserva su enlace, de modo que un usuario que abra el documento más tarde aún verá la imagen resuelta en su propia máquina. Omitir es una política del lado del servidor, no una edición del documento, lo que lo hace seguro para aplicarlo a archivos que manejas en nombre de otra persona.
Qué Cambia al Actualizar
Para la mayoría de los servicios, nada visible a primera vista, y eso vale la pena decirlo claramente porque un cambio de seguridad predeterminado que modifica el comportamiento en todas partes no sobreviviría a una revisión de actualización. La excepción es cualquier vista previa o miniatura que antes mostraba una imagen vinculada y ahora muestra un marcador de posición; ese es el cambio cumpliendo su función, y la solución es una entrada en la lista blanca si el host es tuyo, o aceptar la situación si el documento proviene de fuera.
La forma honesta de comprobarlo es la que usa el ejemplo: renderizar el mismo documento bajo los tres modos y comparar los tamaños de salida. Si las vistas previas predeterminada y con lista blanca son idénticas en tamaño, no se recuperó nada en ninguno de los casos, lo que normalmente indica que el host es inalcanzable desde esa máquina más que que la lista blanca haya fallado, y el ejemplo imprime una pista diciendo exactamente eso.
El Asistente de Vista Previa, Ya que No es Obvio
Dos de los tres modos anteriores llaman a un pequeño asistente, y vale la pena mostrarlo porque PreviewOptions no recibe una ruta:
var previewOptions = new PreviewOptions(
pageData => File.Create(previewPath),
(pageData, pageStream) => pageStream.Dispose())
{
PreviewFormat = PreviewOptions.PreviewFormats.PNG
};
signature.GeneratePreview(previewOptions);
Acepta dos fábricas de streams: una para crear un stream por página y otra para liberarlo. El documento de ejemplo tiene una sola página, por lo que se escribe un archivo; para entradas de varias páginas, incluye el número de página en el nombre del archivo o cada página sobrescribirá la anterior.
Mejores Prácticas
- Trata cualquier cosa que no hayas generado como no confiable, incluidos los archivos de socios con buenas posturas de seguridad.
- Haz que los fragmentos de la lista blanca sean lo suficientemente largos para ser inequívocos y revísalos cuando un CDN cambie.
- Nunca establezcas
SkipExternalResourcesa partir de un valor que antes se asignaba aLoadExternalResources. - Verifica con los tamaños de salida en lugar de con la configuración; una configuración que parece correcta y una solicitud que no ocurrió son afirmaciones diferentes.
Qué Sucede con SVG
Vale la pena destacarlo por separado, porque SVG es tanto un formato de carga común como un vector SSRF frecuente. Un SVG puede referenciar imágenes y hojas de estilo mediante URL, y esas referencias son recursos externos bajo la misma regla: omitidos por defecto, listables en lista blanca, restaurables. Un servicio que acepta avatares o logotipos SVG y los renderiza del lado del servidor era exactamente el tipo de sistema que este cambio protege.
Si tu canal acepta SVG de usuarios, el predeterminado es la configuración que deseas, y la lista blanca sirve para el caso en que tus propias plantillas obtengan una hoja de estilo compartida de un host que administras.
Conclusión
El predeterminado cambió de modo que el comportamiento arriesgado requiere una decisión explícita y el seguro no necesita nada. Mantén el predeterminado para entradas no confiables, usa listas blancas de forma estrecha donde intervengan tus propios hosts, y recuerda que la firma en sí nunca necesitó la red. Ejecutar el ejemplo contra uno de tus propios documentos lleva un minuto y te dice, en tres tamaños de archivo, exactamente qué está recuperando tu servicio.