diff --git a/public/main/admin/user_import.php b/public/main/admin/user_import.php index 03085553a3d..f1aa2a353a1 100644 --- a/public/main/admin/user_import.php +++ b/public/main/admin/user_import.php @@ -451,9 +451,7 @@ function parse_csv_data($users, $fileName, $sendEmail = 0, $checkUniqueEmail = t */ function parse_xml_data($file) { - $crawler = new \Symfony\Component\DomCrawler\Crawler(); - $crawler->addXmlContent(file_get_contents($file)); - $crawler = $crawler->filter('Contacts > Contact '); + $crawler = Import::xml($file)->filter('Contacts > Contact '); $array = []; foreach ($crawler as $domElement) { $row = []; diff --git a/public/main/admin/user_update_import.php b/public/main/admin/user_update_import.php index 22138dba1bf..3a210473831 100644 --- a/public/main/admin/user_update_import.php +++ b/public/main/admin/user_update_import.php @@ -8,7 +8,6 @@ use Chamilo\CoreBundle\Entity\UserAuthSource; use Chamilo\CoreBundle\Framework\Container; -use Symfony\Component\DomCrawler\Crawler; $cidReset = true; require_once __DIR__.'/../inc/global.inc.php'; @@ -247,9 +246,7 @@ function parse_csv_data($file) function parse_xml_data($file) { - $crawler = new Crawler(); - $crawler->addXmlContent(file_get_contents($file)); - $crawler = $crawler->filter('Contacts > Contact '); + $crawler = Import::xml($file)->filter('Contacts > Contact '); $array = []; foreach ($crawler as $domElement) { $row = []; diff --git a/public/main/exercise/export/exercise_import.inc.php b/public/main/exercise/export/exercise_import.inc.php index fedb196b321..839cfd5682c 100644 --- a/public/main/exercise/export/exercise_import.inc.php +++ b/public/main/exercise/export/exercise_import.inc.php @@ -4,7 +4,6 @@ use Chamilo\CoreBundle\Helpers\ChamiloHelper; use PhpZip\ZipFile; -use Symfony\Component\DomCrawler\Crawler; /** * @copyright (c) 2001-2006 Universite catholique de Louvain (UCL) @@ -422,7 +421,7 @@ function parseQti2($xmlData) global $questionTempDir; global $resourcesLinks; - $crawler = new Crawler($xmlData); + $crawler = Import::xmlFromString($xmlData); $nodes = $crawler->filter('*'); $currentQuestionIdent = ''; diff --git a/public/main/inc/lib/import.lib.php b/public/main/inc/lib/import.lib.php index 426ec1b045f..cef72b419a3 100644 --- a/public/main/inc/lib/import.lib.php +++ b/public/main/inc/lib/import.lib.php @@ -4,6 +4,7 @@ use League\Csv\Reader; use PhpOffice\PhpSpreadsheet\Reader\Xls; +use Symfony\Component\DomCrawler\Crawler; /** * Class Import @@ -172,4 +173,33 @@ public static function csvColumnToArray($filename, $columnIndex = 0): array return $values; } + + /** + * Builds a DomCrawler from an XML file, hardened against XXE. + * + * @param string $file Path to the XML file + */ + public static function xml(string $file): Crawler + { + return self::xmlFromString(file_get_contents($file)); + } + + /** + * Builds a DomCrawler from an XML string with XXE hardening: external + * entity loading is blocked regardless of the libxml runtime default, + * and the default loader is restored afterwards. + */ + public static function xmlFromString(string $contents): Crawler + { + libxml_set_external_entity_loader(static fn () => null); + + try { + $crawler = new Crawler(); + $crawler->addXmlContent($contents); + + return $crawler; + } finally { + libxml_set_external_entity_loader(null); + } + } } diff --git a/public/main/inc/lib/myspace.lib.php b/public/main/inc/lib/myspace.lib.php index 53a5e3d64a8..9cad548c1c4 100644 --- a/public/main/inc/lib/myspace.lib.php +++ b/public/main/inc/lib/myspace.lib.php @@ -3106,9 +3106,7 @@ public static function parse_csv_data($file) */ public static function parse_xml_data($file) { - $crawler = new \Symfony\Component\DomCrawler\Crawler(); - $crawler->addXmlContent(file_get_contents($file)); - $crawler = $crawler->filter('Contacts > Contact '); + $crawler = Import::xml($file)->filter('Contacts > Contact '); $array = []; foreach ($crawler as $domElement) { $row = []; diff --git a/public/main/lp/scorm.class.php b/public/main/lp/scorm.class.php index 20456089d57..acfafbfbafd 100644 --- a/public/main/lp/scorm.class.php +++ b/public/main/lp/scorm.class.php @@ -9,7 +9,6 @@ use Chamilo\CourseBundle\Entity\CLp; use Chamilo\CourseBundle\Entity\CLpItem; use PhpZip\ZipFile; -use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\Store\FlockStore; @@ -98,8 +97,7 @@ public function parse_manifest() // UTF-8 is supported by DOMDocument class, this is for sure. $xml = api_utf8_encode_xml($xml, $this->manifest_encoding); - $crawler = new Crawler(); - $crawler->addXmlContent($xml); + $crawler = Import::xmlFromString($xml); $xmlErrors = libxml_get_errors(); if (!empty($xmlErrors)) { diff --git a/src/CourseBundle/Component/CourseCopy/CommonCartridge/Import/Imscc13Import.php b/src/CourseBundle/Component/CourseCopy/CommonCartridge/Import/Imscc13Import.php index a2f2bb4afe9..4111d1d5c41 100644 --- a/src/CourseBundle/Component/CourseCopy/CommonCartridge/Import/Imscc13Import.php +++ b/src/CourseBundle/Component/CourseCopy/CommonCartridge/Import/Imscc13Import.php @@ -167,7 +167,7 @@ private static function makeManifestValidationCopy(string $manifestPath): string $dom->formatOutput = false; // Load as XML (NOT HTML); suppress warnings but we control edits - if (!@$dom->loadXML($xml)) { + if (!@$dom->loadXML($xml, LIBXML_NONET)) { // If DOM fails, just write normalized string to a temp file return self::writeTempValidatedCopy($xml); } diff --git a/src/CourseBundle/Component/CourseCopy/Moodle/Builder/MoodleImport.php b/src/CourseBundle/Component/CourseCopy/Moodle/Builder/MoodleImport.php index 59a57c7537d..b1f6b3105f4 100644 --- a/src/CourseBundle/Component/CourseCopy/Moodle/Builder/MoodleImport.php +++ b/src/CourseBundle/Component/CourseCopy/Moodle/Builder/MoodleImport.php @@ -935,7 +935,7 @@ private function loadXml(string $path): DOMDocument } $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; - if (!@$doc->loadXML($xml)) { + if (!@$doc->loadXML($xml, LIBXML_NONET)) { throw new RuntimeException('Invalid XML: '.$path); }