ILIAS  trunk Revision v11.0_alpha-1715-g7fc467680fb
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
SVGBlacklistPreProcessor.php
Go to the documentation of this file.
1 <?php
2 
20 
24 
32 final class SVGBlacklistPreProcessor implements PreProcessor
33 {
35 
39  private const SVG_MIME_TYPE = 'image/svg+xml';
43  private const REGEX_SCRIPT = '/<script/m';
47  private const REGEX_BASE64 = '/data:.*;base64/m';
51  private const SVG = 'svg';
52  private string $rejection_message = 'The SVG file contains possibily malicious code.';
53  private string $rejection_message_script;
54  private string $rejection_message_base64;
56  private string $ok_message = 'SVG OK';
60  private array $svg_event_lists = [
61  "onbegin",
62  "onend",
63  "onrepeat",
64  "onabort",
65  "onerror",
66  "onresize",
67  "onscroll",
68  "onunload",
69  "onabort",
70  "onerror",
71  "onresize",
72  "onscroll",
73  "onunload",
74  "oncancel",
75  "oncanplay",
76  "oncanplaythrough",
77  "onchange",
78  "onclick",
79  "onclose",
80  "oncuechange",
81  "ondblclick",
82  "ondrag",
83  "ondragend",
84  "ondragenter",
85  "ondragleave",
86  "ondragover",
87  "ondragstart",
88  "ondrop",
89  "ondurationchange",
90  "onemptied",
91  "onended",
92  "onerror",
93  "onfocus",
94  "oninput",
95  "oninvalid",
96  "onkeydown",
97  "onkeypress",
98  "onkeyup",
99  "onload",
100  "onloadeddata",
101  "onloadedmetadata",
102  "onloadstart",
103  "onmousedown",
104  "onmouseenter",
105  "onmouseleave",
106  "onmousemove",
107  "onmouseout",
108  "onmouseover",
109  "onmouseup",
110  "onmousewheel",
111  "onpause",
112  "onplay",
113  "onplaying",
114  "onprogress",
115  "onratechange",
116  "onreset",
117  "onresize",
118  "onscroll",
119  "onseeked",
120  "onseeking",
121  "onselect",
122  "onshow",
123  "onstalled",
124  "onsubmit",
125  "onsuspend",
126  "ontimeupdate",
127  "ontoggle",
128  "onvolumechange",
129  "onwaiting",
130  "onactivate",
131  "onfocusin",
132  "onfocusout"
133  ];
134 
135  public function __construct(
136  ?string $rejection_message = null,
137  ?string $additional_message_script = null,
138  ?string $additional_message_base64 = null,
139  ?string $additional_message_elements = null,
140  ) {
141  $this->rejection_message = $rejection_message ?? $this->rejection_message;
142  $this->rejection_message_script = $additional_message_script ?? 'contains script tags';
143  $this->rejection_message_base64 = $additional_message_base64 ?? 'contains base64 encoded content';
144  $this->rejection_message_elements = $additional_message_elements ?? 'contains not allowed or unknown elements or attributes';
145  }
146 
147  private function isSVG(Metadata $metadata): bool
148  {
149  return $this->isMimeTypeOrExtension(
150  $metadata,
151  self::SVG,
152  [self::SVG_MIME_TYPE]
153  );
154  }
155 
156  public function process(FileStream $stream, Metadata $metadata): ProcessingStatus
157  {
158  if ($this->isSVG($metadata) && !$this->checkStream($stream)) {
159  return new ProcessingStatus(ProcessingStatus::DENIED, $this->rejection_message);
160  }
161  return new ProcessingStatus(ProcessingStatus::OK, $this->ok_message);
162  }
163 
164  public function getDomDocument(string $raw_svg_content): ?\DOMDocument
165  {
166  $dom = new \DOMDocument();
167  try {
168  $dom->loadXML($raw_svg_content, LIBXML_NOWARNING | LIBXML_NOERROR);
169  } catch (\Exception) {
170  return null;
171  }
172  $errors = libxml_get_errors();
173  if ($errors !== []) {
174  return null;
175  }
176  return $dom;
177  }
178 
179  protected function checkStream(FileStream $stream): bool
180  {
181  $raw_svg_content = (string) $stream;
182  if (false === $raw_svg_content) {
183  return false;
184  }
185 
186  // Check for script tags directly
187  if ($this->hasContentScriptTag($raw_svg_content)) {
188  $this->rejection_message = $this->rejection_message;
189  return false;
190  }
191 
192  // Analyze the SVG
193  $dom = $this->getDomDocument($raw_svg_content);
194  if ($dom === null) {
195  return false;
196  }
197 
198  // loop through all attributes of elements recursively and check for event attributes
199  $looper = $this->getDOMAttributesLooper();
200  $prohibited_attributes = (fn(string $name): bool => in_array(strtolower($name), $this->svg_event_lists, true));
201  return $looper($dom, $prohibited_attributes) !== false;
202  }
203 
204  private function hasContentScriptTag(string $raw_svg_content): bool
205  {
206  // Check for Base64 encoded Content
207  if (preg_match(self::REGEX_BASE64, $raw_svg_content)) {
208  $this->rejection_message .= ' ' . $this->rejection_message_base64;
209  return true;
210  }
211 
212  // Check for script tags directly
213  if (preg_match(self::REGEX_SCRIPT, $raw_svg_content)) {
214  $this->rejection_message .= ' ' . $this->rejection_message_script;
215  return true;
216  }
217 
218  return false;
219  }
220 
221  protected function getDOMAttributesLooper(): \Closure
222  {
223  return function (\DOMDocument $dom, \Closure $closure): bool {
224  $attributes_looper = function (\DOMNode $node, \Closure $closure) use (&$attributes_looper): bool {
225  foreach ($node->attributes as $attribute) {
226  if ($closure($attribute->name)) {
227  $this->rejection_message .= sprintf(
228  $this->rejection_message_elements . ' (%s)',
229  $attribute->name
230  );
231  return false;
232  }
233  }
234  foreach ($node->childNodes as $child) {
235  if ($child instanceof \DOMElement && !$attributes_looper($child, $closure)) {
236  return false;
237  }
238  }
239  return true;
240  };
241  foreach ($dom->getElementsByTagName("*") as $i => $element) {
242  if ($attributes_looper($element, $closure) === false) {
243  return false;
244  }
245  }
246  return true;
247  };
248  }
249 }
process(FileStream $stream, Metadata $metadata)
This method gets invoked by the file upload service to process the file with the help of the processo...
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
__construct(?string $rejection_message=null, ?string $additional_message_script=null, ?string $additional_message_base64=null, ?string $additional_message_elements=null,)