[nginx] Xslt: xml_external_entities directive.

Maxim Dounin mdounin at mdounin.ru
Sun Nov 9 09:59:21 UTC 2025


details:   http://freenginx.org/hg/nginx/rev/94dae9ab1018
branches:  
changeset: 9435:94dae9ab1018
user:      Maxim Dounin <mdounin at mdounin.ru>
date:      Sun Nov 09 12:01:38 2025 +0300
description:
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.

diffstat:

 src/http/modules/ngx_http_xslt_filter_module.c |  81 ++++++++++++++++++++++++++
 1 files changed, 81 insertions(+), 0 deletions(-)

diffs (154 lines):

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