Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Reader\Security; 4 5 use PhpOffice\PhpSpreadsheet\Reader; 6 use PhpOffice\PhpSpreadsheet\Settings; 7 8 class XmlScanner 9 { 10 /** 11 * String used to identify risky xml elements. 12 * 13 * @var string 14 */ 15 private $pattern; 16 17 private $callback; 18 19 private static $libxmlDisableEntityLoaderValue; 20 21 public function __construct($pattern = '<!DOCTYPE') 22 { 23 $this->pattern = $pattern; 24 25 $this->disableEntityLoaderCheck(); 26 27 // A fatal error will bypass the destructor, so we register a shutdown here 28 register_shutdown_function([__CLASS__, 'shutdown']); 29 } 30 31 public static function getInstance(Reader\IReader $reader) 32 { 33 switch (true) { 34 case $reader instanceof Reader\Html: 35 return new self('<!ENTITY'); 36 case $reader instanceof Reader\Xlsx: 37 case $reader instanceof Reader\Xml: 38 case $reader instanceof Reader\Ods: 39 case $reader instanceof Reader\Gnumeric: 40 return new self('<!DOCTYPE'); 41 default: 42 return new self('<!DOCTYPE'); 43 } 44 } 45 46 public static function threadSafeLibxmlDisableEntityLoaderAvailability() 47 { 48 if (PHP_MAJOR_VERSION == 7) { 49 switch (PHP_MINOR_VERSION) { 50 case 2: 51 return PHP_RELEASE_VERSION >= 1; 52 case 1: 53 return PHP_RELEASE_VERSION >= 13; 54 case 0: 55 return PHP_RELEASE_VERSION >= 27; 56 } 57 58 return true; 59 } 60 61 return false; 62 } 63 64 private function disableEntityLoaderCheck() 65 { 66 if (Settings::getLibXmlDisableEntityLoader()) { 67 $libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true); 68 69 if (self::$libxmlDisableEntityLoaderValue === null) { 70 self::$libxmlDisableEntityLoaderValue = $libxmlDisableEntityLoaderValue; 71 } 72 } 73 } 74 75 public static function shutdown() 76 { 77 if (self::$libxmlDisableEntityLoaderValue !== null) { 78 libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue); 79 self::$libxmlDisableEntityLoaderValue = null; 80 } 81 } 82 83 public function __destruct() 84 { 85 self::shutdown(); 86 } 87 88 public function setAdditionalCallback(callable $callback) 89 { 90 $this->callback = $callback; 91 } 92 93 private function toUtf8($xml) 94 { 95 $pattern = '/encoding="(.*?)"/'; 96 $result = preg_match($pattern, $xml, $matches); 97 $charset = strtoupper($result ? $matches[1] : 'UTF-8'); 98 99 if ($charset !== 'UTF-8') { 100 $xml = mb_convert_encoding($xml, 'UTF-8', $charset); 101 102 $result = preg_match($pattern, $xml, $matches); 103 $charset = strtoupper($result ? $matches[1] : 'UTF-8'); 104 if ($charset !== 'UTF-8') { 105 throw new Reader\Exception('Suspicious Double-encoded XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); 106 } 107 } 108 109 return $xml; 110 } 111 112 /** 113 * Scan the XML for use of <!ENTITY to prevent XXE/XEE attacks. 114 * 115 * @param mixed $xml 116 * 117 * @throws Reader\Exception 118 * 119 * @return string 120 */ 121 public function scan($xml) 122 { 123 $this->disableEntityLoaderCheck(); 124 125 $xml = $this->toUtf8($xml); 126 127 // Don't rely purely on libxml_disable_entity_loader() 128 $pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/'; 129 130 if (preg_match($pattern, $xml)) { 131 throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); 132 } 133 134 if ($this->callback !== null && is_callable($this->callback)) { 135 $xml = call_user_func($this->callback, $xml); 136 } 137 138 return $xml; 139 } 140 141 /** 142 * Scan theXML for use of <!ENTITY to prevent XXE/XEE attacks. 143 * 144 * @param string $filestream 145 * 146 * @throws Reader\Exception 147 * 148 * @return string 149 */ 150 public function scanFile($filestream) 151 { 152 return $this->scan(file_get_contents($filestream)); 153 } 154 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body