From adc97d03b92fef50608c21059f0509fa97d314f6 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 14 Aug 2012 18:28:29 -0400
Subject: [PATCH] Prevent access to external files/URLs via contrib/xml2's
 xslt_process().

libxslt offers the ability to read and write both files and URLs through
stylesheet commands, thus allowing unprivileged database users to both read
and write data with the privileges of the database server.  Disable that
through proper use of libxslt's security options.

Also, remove xslt_process()'s ability to fetch documents and stylesheets
from external files/URLs.  While this was a documented "feature", it was
long regarded as a terrible idea.  The fix for CVE-2012-3489 broke that
capability, and rather than expend effort on trying to fix it, we're just
going to summarily remove it.

While the ability to write as well as read makes this security hole
considerably worse than CVE-2012-3489, the problem is mitigated by the fact
that xslt_process() is not available unless contrib/xml2 is installed,
and the longstanding warnings about security risks from that should have
discouraged prudent DBAs from installing it in security-exposed databases.

Reported and fixed by Peter Eisentraut.

Security: CVE-2012-3488
---
 contrib/xml2/expected/xml2.out   | 15 +++++++
 contrib/xml2/expected/xml2_1.out | 15 +++++++
 contrib/xml2/sql/xml2.sql        | 15 +++++++
 contrib/xml2/xslt_proc.c         | 70 ++++++++++++++++++++++++--------
 doc/src/sgml/xml2.sgml           |  8 ----
 5 files changed, 97 insertions(+), 26 deletions(-)

diff --git a/contrib/xml2/expected/xml2.out b/contrib/xml2/expected/xml2.out
index 3bf676fb400..eba6ae60364 100644
--- a/contrib/xml2/expected/xml2.out
+++ b/contrib/xml2/expected/xml2.out
@@ -207,3 +207,18 @@ SELECT xslt_process('<employee><name>cim</name><age>30</age><pay>400</pay></empl
  
 (1 row)
 
+-- possible security exploit
+SELECT xslt_process('<xml><foo>Hello from XML</foo></xml>',
+$$<xsl:stylesheet version="1.0"
+      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+      xmlns:sax="http://icl.com/saxon"
+      extension-element-prefixes="sax">
+
+  <xsl:template match="//foo">
+    <sax:output href="0wn3d.txt" method="text">
+      <xsl:value-of select="'0wn3d via xml2 extension and libxslt'"/>
+      <xsl:apply-templates/>
+    </sax:output>
+  </xsl:template>
+</xsl:stylesheet>$$);
+ERROR:  failed to apply stylesheet
diff --git a/contrib/xml2/expected/xml2_1.out b/contrib/xml2/expected/xml2_1.out
index fda626e08c7..bac90e5a2a9 100644
--- a/contrib/xml2/expected/xml2_1.out
+++ b/contrib/xml2/expected/xml2_1.out
@@ -151,3 +151,18 @@ SELECT xslt_process('<employee><name>cim</name><age>30</age><pay>400</pay></empl
   </xsl:template>
 </xsl:stylesheet>$$::text, 'n1="v1",n2="v2",n3="v3",n4="v4",n5="v5",n6="v6",n7="v7",n8="v8",n9="v9",n10="v10",n11="v11",n12="v12"'::text);
 ERROR:  xslt_process() is not available without libxslt
+-- possible security exploit
+SELECT xslt_process('<xml><foo>Hello from XML</foo></xml>',
+$$<xsl:stylesheet version="1.0"
+      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+      xmlns:sax="http://icl.com/saxon"
+      extension-element-prefixes="sax">
+
+  <xsl:template match="//foo">
+    <sax:output href="0wn3d.txt" method="text">
+      <xsl:value-of select="'0wn3d via xml2 extension and libxslt'"/>
+      <xsl:apply-templates/>
+    </sax:output>
+  </xsl:template>
+</xsl:stylesheet>$$);
+ERROR:  xslt_process() is not available without libxslt
diff --git a/contrib/xml2/sql/xml2.sql b/contrib/xml2/sql/xml2.sql
index 4a996af7167..ac49cfa7c52 100644
--- a/contrib/xml2/sql/xml2.sql
+++ b/contrib/xml2/sql/xml2.sql
@@ -122,3 +122,18 @@ SELECT xslt_process('<employee><name>cim</name><age>30</age><pay>400</pay></empl
     </xsl:element>
   </xsl:template>
 </xsl:stylesheet>$$::text, 'n1="v1",n2="v2",n3="v3",n4="v4",n5="v5",n6="v6",n7="v7",n8="v8",n9="v9",n10="v10",n11="v11",n12="v12"'::text);
+
+-- possible security exploit
+SELECT xslt_process('<xml><foo>Hello from XML</foo></xml>',
+$$<xsl:stylesheet version="1.0"
+      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+      xmlns:sax="http://icl.com/saxon"
+      extension-element-prefixes="sax">
+
+  <xsl:template match="//foo">
+    <sax:output href="0wn3d.txt" method="text">
+      <xsl:value-of select="'0wn3d via xml2 extension and libxslt'"/>
+      <xsl:apply-templates/>
+    </sax:output>
+  </xsl:template>
+</xsl:stylesheet>$$);
diff --git a/contrib/xml2/xslt_proc.c b/contrib/xml2/xslt_proc.c
index a93931d2618..2f24b39bcc0 100644
--- a/contrib/xml2/xslt_proc.c
+++ b/contrib/xml2/xslt_proc.c
@@ -26,6 +26,7 @@
 
 #include <libxslt/xslt.h>
 #include <libxslt/xsltInternals.h>
+#include <libxslt/security.h>
 #include <libxslt/transform.h>
 #include <libxslt/xsltutils.h>
 #endif   /* USE_LIBXSLT */
@@ -61,7 +62,8 @@ xslt_process(PG_FUNCTION_ARGS)
 	volatile xsltStylesheetPtr stylesheet = NULL;
 	volatile xmlDocPtr doctree = NULL;
 	volatile xmlDocPtr restree = NULL;
-	volatile xmlDocPtr ssdoc = NULL;
+	volatile xsltSecurityPrefsPtr xslt_sec_prefs = NULL;
+	volatile xsltTransformContextPtr xslt_ctxt = NULL;
 	volatile int resstat = -1;
 	xmlChar    *resstr = NULL;
 	int			reslen = 0;
@@ -83,36 +85,62 @@ xslt_process(PG_FUNCTION_ARGS)
 
 	PG_TRY();
 	{
-		/* Check to see if document is a file or a literal */
+		xmlDocPtr	ssdoc;
+		bool		xslt_sec_prefs_error;
 
-		if (VARDATA(doct)[0] == '<')
-			doctree = xmlParseMemory((char *) VARDATA(doct), VARSIZE(doct) - VARHDRSZ);
-		else
-			doctree = xmlParseFile(text_to_cstring(doct));
+		/* Parse document */
+		doctree = xmlParseMemory((char *) VARDATA(doct),
+								 VARSIZE(doct) - VARHDRSZ);
 
 		if (doctree == NULL)
 			xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
 						"error parsing XML document");
 
 		/* Same for stylesheet */
-		if (VARDATA(ssheet)[0] == '<')
-		{
-			ssdoc = xmlParseMemory((char *) VARDATA(ssheet),
-								   VARSIZE(ssheet) - VARHDRSZ);
-			if (ssdoc == NULL)
-				xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
-							"error parsing stylesheet as XML document");
+		ssdoc = xmlParseMemory((char *) VARDATA(ssheet),
+							   VARSIZE(ssheet) - VARHDRSZ);
 
-			stylesheet = xsltParseStylesheetDoc(ssdoc);
-		}
-		else
-			stylesheet = xsltParseStylesheetFile((xmlChar *) text_to_cstring(ssheet));
+		if (ssdoc == NULL)
+			xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
+						"error parsing stylesheet as XML document");
+
+		/* After this call we need not free ssdoc separately */
+		stylesheet = xsltParseStylesheetDoc(ssdoc);
 
 		if (stylesheet == NULL)
 			xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
 						"failed to parse stylesheet");
 
-		restree = xsltApplyStylesheet(stylesheet, doctree, params);
+		xslt_ctxt = xsltNewTransformContext(stylesheet, doctree);
+
+		xslt_sec_prefs_error = false;
+		if ((xslt_sec_prefs = xsltNewSecurityPrefs()) == NULL)
+			xslt_sec_prefs_error = true;
+
+		if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_READ_FILE,
+								 xsltSecurityForbid) != 0)
+			xslt_sec_prefs_error = true;
+		if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_WRITE_FILE,
+								 xsltSecurityForbid) != 0)
+			xslt_sec_prefs_error = true;
+		if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_CREATE_DIRECTORY,
+								 xsltSecurityForbid) != 0)
+			xslt_sec_prefs_error = true;
+		if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_READ_NETWORK,
+								 xsltSecurityForbid) != 0)
+			xslt_sec_prefs_error = true;
+		if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_WRITE_NETWORK,
+								 xsltSecurityForbid) != 0)
+			xslt_sec_prefs_error = true;
+		if (xsltSetCtxtSecurityPrefs(xslt_sec_prefs, xslt_ctxt) != 0)
+			xslt_sec_prefs_error = true;
+
+		if (xslt_sec_prefs_error)
+			ereport(ERROR,
+					(errmsg("could not set libxslt security preferences")));
+
+		restree = xsltApplyStylesheetUser(stylesheet, doctree, params,
+										  NULL, NULL, xslt_ctxt);
 
 		if (restree == NULL)
 			xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
@@ -128,6 +156,10 @@ xslt_process(PG_FUNCTION_ARGS)
 			xmlFreeDoc(restree);
 		if (doctree != NULL)
 			xmlFreeDoc(doctree);
+		if (xslt_sec_prefs != NULL)
+			xsltFreeSecurityPrefs(xslt_sec_prefs);
+		if (xslt_ctxt != NULL)
+			xsltFreeTransformContext(xslt_ctxt);
 		xsltCleanupGlobals();
 
 		pg_xml_done(xmlerrcxt, true);
@@ -139,6 +171,8 @@ xslt_process(PG_FUNCTION_ARGS)
 	xsltFreeStylesheet(stylesheet);
 	xmlFreeDoc(restree);
 	xmlFreeDoc(doctree);
+	xsltFreeSecurityPrefs(xslt_sec_prefs);
+	xsltFreeTransformContext(xslt_ctxt);
 	xsltCleanupGlobals();
 
 	pg_xml_done(xmlerrcxt, false);
diff --git a/doc/src/sgml/xml2.sgml b/doc/src/sgml/xml2.sgml
index 47bac31f0c7..ce5a2f0645b 100644
--- a/doc/src/sgml/xml2.sgml
+++ b/doc/src/sgml/xml2.sgml
@@ -436,14 +436,6 @@ xslt_process(text document, text stylesheet, text paramlist) returns text
     contain commas!
    </para>
 
-   <para>
-    Also note that if either the document or stylesheet values do not
-    begin with a &lt; then they will be treated as URLs and libxslt will
-    fetch them. It follows that you can use <function>xslt_process</> as a
-    means to fetch the contents of URLs &mdash; you should be aware of the
-    security implications of this.
-   </para>
-
    <para>
     There is also a two-parameter version of <function>xslt_process</> which
     does not pass any parameters to the transformation.
-- 
GitLab