]> git.kaiwu.me - nginx.git/commitdiff
Xslt: xml_external_entities directive
authorMaxim Dounin <mdounin@mdounin.ru>
Sun, 9 Nov 2025 09:01:38 +0000 (12:01 +0300)
committerVadimZhestikov <108960056+VadimZhestikov@users.noreply.github.com>
Mon, 13 Jul 2026 17:49:14 +0000 (10:49 -0700)
Loading of external entities defined in the internal DTD subset,
that is, in the XML document itself, is now disabled by default, and
can be re-enabled with "xml_external_entities on;".  This makes
processing of untrusted XML responses with the xslt module slightly
safer (though still not recommended unless you thoughtfully considered
risks).

To prevent loading we intercept entities defined in the internal
subset via the entityDecl callback, and remove system identifiers from
entities.  This ensures that entities cannot be loaded directly, but
still allows using of public entities with appropriate system XML
catalog.

Additionally, since libxml2 before 2.14.0 (Mar 27 2025) accepts
in-document catalogs by default, these are explicitly disabled.

Signed-off-by: Vadim Zhestikov <v.zhestikov@f5.com>
Origin: https://freenginx.org/hg/nginx/rev/94dae9ab1018

src/http/modules/ngx_http_xslt_filter_module.c

index 1e46be5ae9236b38d3b9583b828a52de66107aff..f0586f867e8f1deffbf7e679a0adaf7fc15345a9 100644 (file)
@@ -11,6 +11,7 @@
 
 #include <libxml/parser.h>
 #include <libxml/tree.h>
+#include <libxml/xmlversion.h>
 #include <libxslt/xslt.h>
 #include <libxslt/xsltInternals.h>
 #include <libxslt/transform.h>
 #include <libexslt/exslt.h>
 #endif
 
+#if (defined LIBXML_CATALOG_ENABLED && LIBXML_VERSION < 21400)
+#include <libxml/catalog.h>
+#endif
+
 
 #ifndef NGX_HTTP_XSLT_REUSE_DTD
 #define NGX_HTTP_XSLT_REUSE_DTD  1
@@ -59,6 +64,7 @@ typedef struct {
     ngx_array_t               *types_keys;
     ngx_array_t               *params;       /* ngx_http_xslt_param_t */
     ngx_flag_t                 last_modified;
+    ngx_flag_t                 external_entities;
 } ngx_http_xslt_filter_loc_conf_t;
 
 
@@ -81,6 +87,9 @@ static ngx_int_t ngx_http_xslt_add_chunk(ngx_http_request_t *r,
 
 static void ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
     const xmlChar *externalId, const xmlChar *systemId);
+static void ngx_http_xslt_sax_entity_decl(void *data, const xmlChar *name,
+    int type, const xmlChar *publicId, const xmlChar *systemId,
+    xmlChar *content);
 static void ngx_cdecl ngx_http_xslt_sax_error(void *data, const char *msg, ...);
 
 
@@ -124,6 +133,13 @@ static ngx_command_t  ngx_http_xslt_filter_commands[] = {
       0,
       NULL },
 
+    { ngx_string("xml_external_entities"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_xslt_filter_loc_conf_t, external_entities),
+      NULL },
+
     { ngx_string("xslt_stylesheet"),
       NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
       ngx_http_xslt_stylesheet,
@@ -385,6 +401,7 @@ ngx_http_xslt_add_chunk(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
                                 |XML_PARSE_NONET|XML_PARSE_NOWARNING);
 
         ctxt->sax->externalSubset = ngx_http_xslt_sax_external_subset;
+        ctxt->sax->entityDecl = ngx_http_xslt_sax_entity_decl;
         ctxt->sax->setDocumentLocator = NULL;
         ctxt->sax->error = ngx_http_xslt_sax_error;
         ctxt->sax->fatalError = ngx_http_xslt_sax_error;
@@ -460,6 +477,64 @@ ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
 }
 
 
+static void
+ngx_http_xslt_sax_entity_decl(void *data, const xmlChar *name, int type,
+    const xmlChar *publicId, const xmlChar *systemId, xmlChar *content)
+{
+    xmlParserCtxtPtr ctxt = data;
+
+    ngx_http_request_t               *r;
+    ngx_http_xslt_filter_ctx_t       *ctx;
+    ngx_http_xslt_filter_loc_conf_t  *conf;
+
+    ctx = ctxt->sax->_private;
+    r = ctx->request;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "xslt filter entityDecl: \"%s\" \"%s\" \"%s\"",
+                   name ? name : (xmlChar *) "",
+                   publicId ? publicId : (xmlChar *) "",
+                   systemId ? systemId : (xmlChar *) "");
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+    if (systemId && !conf->external_entities) {
+
+        /*
+         * If external entiries in the internal DTD subset are disabled,
+         * we remove system identifiers from such entities.  This makes sure
+         * that external entities cannot be used to directly request arbitrary
+         * files, but still can be used with public identifiers, assuming these
+         * are included into XML catalogs on the system.
+         */
+
+        ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+                      "xslt filter external entity ignored: "
+                      "\"%s\" \"%s\" \"%s\"",
+                      name ? name : (xmlChar *) "",
+                      publicId ? publicId : (xmlChar *) "",
+                      systemId ? systemId : (xmlChar *) "");
+
+        if (publicId) {
+            xmlSAX2EntityDecl(data, name, type, publicId, (xmlChar *) "",
+                              content);
+
+        } else if (type == XML_EXTERNAL_GENERAL_PARSED_ENTITY) {
+            xmlSAX2EntityDecl(data, name, XML_INTERNAL_GENERAL_ENTITY,
+                              NULL, NULL, (xmlChar *) "");
+
+        } else if (type == XML_EXTERNAL_PARAMETER_ENTITY) {
+            xmlSAX2EntityDecl(data, name, XML_INTERNAL_PARAMETER_ENTITY,
+                              NULL, NULL, (xmlChar *) "");
+        }
+
+        return;
+    }
+
+    xmlSAX2EntityDecl(data, name, type, publicId, systemId, content);
+}
+
+
 static void ngx_cdecl
 ngx_http_xslt_sax_error(void *data, const char *msg, ...)
 {
@@ -1096,6 +1171,7 @@ ngx_http_xslt_filter_create_conf(ngx_conf_t *cf)
      */
 
     conf->last_modified = NGX_CONF_UNSET;
+    conf->external_entities = NGX_CONF_UNSET;
 
     return conf;
 }
@@ -1128,6 +1204,7 @@ ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
     }
 
     ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
+    ngx_conf_merge_value(conf->external_entities, prev->external_entities, 0);
 
     return NGX_CONF_OK;
 }
@@ -1142,6 +1219,10 @@ ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf)
     exsltRegisterAll();
 #endif
 
+#if (defined LIBXML_CATALOG_ENABLED && LIBXML_VERSION < 21400)
+    xmlCatalogSetDefaults(XML_CATA_ALLOW_GLOBAL);
+#endif
+
     return NGX_OK;
 }