CVE-2014-1439 (published 2014-02-05T19:55Z)

The libxml_disable_entity_loader function in runtime/ext/ext_simplexml.cpp in HipHop Virtual Machine for PHP (HHVM) before 2.4.0 and 2.3.x before 2.3.3 does not properly disable a certain libxml handler, which allows remote attackers to conduct XML External Entity (XXE) attacks.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:P/I:N/A:N)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/facebook/hhvm
commit_details: repo_url: https://github.com/facebook/hhvm
author: Scott MacVicar
author_date: 2014-01-04 01:39:10-08:00
msg: Fix libxml_disable_entity_loader()

This wasn't calling requestInit and setting the libxml handler no null.
So the first time an error came along it would reset the handler from
no-op to reading again.

This is a much better fix, we set our custom handler in requestInit and
when libxml_disable_entity_loader we store that state as a member bool
ensuring requestInit is always called to set our own handler.

If the handler isn't inserted then the behavious is as before. The only
time this could go pear shaped is say we wanted to make the default be
off. In that case we'd need a global requestInit that is always called
since there are libxml references everywhere.

Reviewed By: @jdelong

Differential Revision: D1116686
repository: repo_url: https://github.com/facebook/hhvm
repo_name: facebook/hhvm
description: A virtual machine for executing programs written in Hack.
repo_language: Hack
owner: facebook
file_changes: filename: ext_simplexml.cpp
change_type: ModificationType.MODIFY
diff: @@ -1246,6 +1246,9 @@ void c_LibXMLError::t___construct() {
 ///////////////////////////////////////////////////////////////////////////////
 // libxml
 
+static xmlParserInputBufferPtr
+hphp_libxml_input_buffer(const char *URI, xmlCharEncoding enc);
+
 class xmlErrorVec : public std::vector<xmlError> {
 public:
   ~xmlErrorVec() {
@@ -1265,13 +1268,15 @@ public:
   virtual void requestInit() {
     m_use_error = false;
     m_errors.reset();
-    xmlParserInputBufferCreateFilenameDefault(nullptr);
+    m_entity_loader_disabled = false;
+    xmlParserInputBufferCreateFilenameDefault(hphp_libxml_input_buffer);
   }
   virtual void requestShutdown() {
     m_use_error = false;
     m_errors.reset();
   }
 
+  bool m_entity_loader_disabled;
   bool m_use_error;
   xmlErrorVec m_errors;
 };
@@ -1374,19 +1379,19 @@ bool f_libxml_use_internal_errors(CVarRef use_errors /* = null_variant */) {
 }
 
 static xmlParserInputBufferPtr
-hphp_libxml_input_buffer_noload(const char *URI, xmlCharEncoding enc) {
-  return nullptr;
+hphp_libxml_input_buffer(const char *URI, xmlCharEncoding enc) {
+  if (s_libxml_errors->m_entity_loader_disabled) {
+    return nullptr;
+  }
+  return __xmlParserInputBufferCreateFilename(URI, enc);
 }
 
 bool f_libxml_disable_entity_loader(bool disable /* = true */) {
-  xmlParserInputBufferCreateFilenameFunc old;
+  bool old = s_libxml_errors->m_entity_loader_disabled;
 
-  if (disable) {
-    old = xmlParserInputBufferCreateFilenameDefault(hphp_libxml_input_buffer_noload);
-  } else {
-    old = xmlParserInputBufferCreateFilenameDefault(nullptr);
-  }
-  return (old == hphp_libxml_input_buffer_noload);
+  s_libxml_errors->m_entity_loader_disabled = disable;
+
+  return old;
 }
 
 ///////////////////////////////////////////////////////////////////////////////

programming_language: C++
filename: libxml_disable_entity_loader.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,25 @@
+<?php
+
+$xml = <<<EOT
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE test [<!ENTITY xxe SYSTEM "XXE_URI">]>
+<foo>&xxe;</foo>
+EOT;
+
+$xml = str_replace('XXE_URI', __DIR__ . '/libxml_disable_entity_loader_payload.txt', $xml);
+
+function parseXML($xml) {
+  $doc = new DOMDocument();
+  $doc->resolveExternals = true;
+  $doc->substituteEntities = true;
+  $doc->validateOnParse = false;
+  $doc->loadXML($xml, 0);
+  return $doc->saveXML();
+}
+
+var_dump(strpos(parseXML($xml), 'SECRET_DATA') !== false);
+var_dump(libxml_disable_entity_loader(true));
+var_dump(strpos(parseXML($xml), 'SECRET_DATA') === false);
+
+echo "Done\n";
+?>

programming_language: PHP
filename: libxml_disable_entity_loader.php.expectf
change_type: ModificationType.ADD
diff: @@ -0,0 +1,7 @@
+bool(true)
+bool(false)
+I/O warning : failed to load external entity "%s"
+HipHop Warning: %s
+HipHop Warning: %s
+bool(true)
+Done

programming_language: Batchfile
filename: libxml_disable_entity_loader.php.skipif
change_type: ModificationType.ADD
diff: @@ -0,0 +1 @@
+<?php if (!extension_loaded('simplexml') || !extension_loaded('dom') || defined('PHP_WINDOWS_VERSION_MAJOR')) die('skip'); ?>

programming_language: PHP
filename: libxml_disable_entity_loader_payload.txt
change_type: ModificationType.ADD
diff: @@ -0,0 +1 @@
+SECRET_DATA
\ No newline at end of file

programming_language: SQL