[PATCH 2 of 2] Xslt: xml_external_entities directive

Maxim Dounin mdounin at mdounin.ru
Tue Nov 4 02:42:26 UTC 2025


# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1762216523 -10800
#      Tue Nov 04 03:35:23 2025 +0300
# Node ID 4940e420aef6c1caed8da846a6bc885a7eb9f9c0
# Parent  11dfa80270f8358caaaca66363f4f8a3446b43b5
Xslt: xml_external_entities directive.

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.

diff --git a/src/http/modules/ngx_http_xslt_filter_module.c b/src/http/modules/ngx_http_xslt_filter_module.c
--- a/src/http/modules/ngx_http_xslt_filter_module.c
+++ b/src/http/modules/ngx_http_xslt_filter_module.c
@@ -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>
@@ -21,6 +22,10 @@
 #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
 
 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_filt
       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
                                 |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 *
 }
 
 
+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, ...)
 {
@@ -1090,6 +1165,7 @@ ngx_http_xslt_filter_create_conf(ngx_con
      */
 
     conf->last_modified = NGX_CONF_UNSET;
+    conf->external_entities = NGX_CONF_UNSET;
 
     return conf;
 }
@@ -1122,6 +1198,7 @@ ngx_http_xslt_filter_merge_conf(ngx_conf
     }
 
     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;
 }
@@ -1136,6 +1213,10 @@ ngx_http_xslt_filter_preconfiguration(ng
     exsltRegisterAll();
 #endif
 
+#if (defined LIBXML_CATALOG_ENABLED && LIBXML_VERSION < 21400)
+    xmlCatalogSetDefaults(XML_CATA_ALLOW_GLOBAL);
+#endif
+
     return NGX_OK;
 }
 



More information about the nginx-devel mailing list