CVE-2019-11813: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: An issue was discovered in app/View/Elements/Events/View/value_field.ctp in MISP before 2.4.107. There is persistent XSS via link type attributes with javascript:// links.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2019-05-08T13:29Z
Repository: https://github.com/MISP/MISP
Language: Other
Files changed: app/View/Elements/Events/View/value_field.ctp
Commit date: 2019-05-07 09:00:33 +0200
Commit message: fix: [security] Fix persistent xss via link type attributes containing javascript:// links

- low impact as it requires user interaction to trigger

- as reported by João Lucas Melo Brasio from Elytron Security S.A. (https://elytronsecurity.com)

Diff: diff --git a/app/View/Elements/Events/View/value_field.ctp b/app/View/Elements/Events/View/value_field.ctp
index 9b748d0832..845a3d7959 100644
--- a/app/View/Elements/Events/View/value_field.ctp
+++ b/app/View/Elements/Events/View/value_field.ctp
@@ -1,58 +1,60 @@
 <?php
-  $sigDisplay = $object['value'];
-  if ('attachment' == $object['type'] || 'malware-sample' == $object['type'] ) {
-    if ($object['type'] == 'attachment' && isset($object['image'])) {
-        if (extension_loaded('gd')) {
-            $img = '<it class="fa fa-spin fa-spinner" style="font-size: large; left: 50%; top: 50%;"></it>';
-            $img .= '<img class="screenshot screenshot-collapsed useCursorPointer img-rounded hidden" src="' . $baseurl . '/attributes/viewPicture/' . h($object['id']) . '/1' . '" title="' . h($object['value']) . '" onload="$(this).show(200); $(this).parent().find(\'.fa-spinner\').remove();"/>';
-            echo $img;
+    $sigDisplay = $object['value'];
+    if ('attachment' == $object['type'] || 'malware-sample' == $object['type'] ) {
+        if ($object['type'] == 'attachment' && isset($object['image'])) {
+            if (extension_loaded('gd')) {
+                $img = '<it class="fa fa-spin fa-spinner" style="font-size: large; left: 50%; top: 50%;"></it>';
+                $img .= '<img class="screenshot screenshot-collapsed useCursorPointer img-rounded hidden" src="' . $baseurl . '/attributes/viewPicture/' . h($object['id']) . '/1' . '" title="' . h($object['value']) . '" onload="$(this).show(200); $(this).parent().find(\'.fa-spinner\').remove();"/>';
+                echo $img;
+            } else {
+                $extension = explode('.', $object['value']);
+                $extension = end($extension);
+                $uri = 'data:image/' . strtolower(h($extension)) . ';base64,' . h($object['image']);
+                echo '<img class="screenshot screenshot-collapsed useCursorPointer" src="' . $uri . '" title="' . h($object['value']) . '" />';
+            }
         } else {
-            $extension = explode('.', $object['value']);
-            $extension = end($extension);
-            $uri = 'data:image/' . strtolower(h($extension)) . ';base64,' . h($object['image']);
-            echo '<img class="screenshot screenshot-collapsed useCursorPointer" src="' . $uri . '" title="' . h($object['value']) . '" />';
+            $filenameHash = explode('|', h($object['value']));
+            if (strrpos($filenameHash[0], '\\')) {
+                $filepath = substr($filenameHash[0], 0, strrpos($filenameHash[0], '\\'));
+                $filename = substr($filenameHash[0], strrpos($filenameHash[0], '\\'));
+                echo h($filepath);
+                echo '<a href="' . $baseurl . '/attributes/download/' . h($object['id']) . '" class="' . $linkClass . '">' . h($filename) . '</a>';
+            } else {
+                echo '<a href="' . $baseurl . '/attributes/download/' . h($object['id']) . '" class="' . $linkClass . '">' . h($filenameHash[0]) . '</a>';
+            }
+            if (isset($filenameHash[1])) echo '<br />' . $filenameHash[1];
         }
+    } else if (strpos($object['type'], '|') !== false) {
+        $separator = in_array($object['type'], array('ip-dst|port', 'ip-src|port')) ? ':' : '<br />';
+        $value_pieces = explode('|', $object['value']);
+        foreach ($value_pieces as $k => $v) {
+            $value_pieces[$k] = h($v);
+        }
+        $object['value'] = implode($separator, $value_pieces);
+        echo ($object['value']);
+    } else if ('vulnerability' == $object['type']) {
+        $cveUrl = (is_null(Configure::read('MISP.cveurl'))) ? "http://www.google.com/search?q=" : Configure::read('MISP.cveurl');
+        echo $this->Html->link($sigDisplay, $cveUrl . $sigDisplay, array('target' => '_blank', 'class' => $linkClass));
+    } else if ('link' == $object['type'] && (substr($object['value'], 0, 7) === 'http://' || substr($object['value'], 0, 8) === 'https://')) {
+        echo $this->Html->link($sigDisplay, $sigDisplay, array('class' => $linkClass));
+    } else if ('cortex' == $object['type']) {
+        echo '<div class="cortex-json" data-cortex-json="' . h($object['value']) . '">Cortex object</div>';
+    } else if ('text' == $object['type']) {
+        if (($object['category'] == 'Internal reference' || $object['category'] == 'External analysis') && preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i', $object['value'])) {
+            echo '<a href="' . $baseurl . '/events/view/' . h($object['value']) . '" class="' . $linkClass . '">' . h($object['value']) . '</a>';
+        } else {
+            $sigDisplay = str_replace("\r", '', h($sigDisplay));
+            $sigDisplay = str_replace(" ", '&nbsp;', $sigDisplay);
+            echo $sigDisplay;
+        }
+    } else if ('hex' == $object['type']) {
+        $sigDisplay = str_replace("\r", '', $sigDisplay);
+        echo '<span class="hex-value" title="' . __('Hexadecimal representation') . '">' . h($sigDisplay) . '</span>&nbsp;<span role="button" tabindex="0" aria-label="' . __('Switch to binary representation') . '" class="icon-repeat hex-value-convert useCursorPointer" title="' . __('Switch to binary representation') . '"></span>';
     } else {
-      $filenameHash = explode('|', h($object['value']));
-      if (strrpos($filenameHash[0], '\\')) {
-        $filepath = substr($filenameHash[0], 0, strrpos($filenameHash[0], '\\'));
-        $filename = substr($filenameHash[0], strrpos($filenameHash[0], '\\'));
-        echo h($filepath);
-        echo '<a href="' . $baseurl . '/attributes/download/' . h($object['id']) . '" class="' . $linkClass . '">' . h($filename) . '</a>';
-      } else {
-        echo '<a href="' . $baseurl . '/attributes/download/' . h($object['id']) . '" class="' . $linkClass . '">' . h($filenameHash[0]) . '</a>';
-      }
-      if (isset($filenameHash[1])) echo '<br />' . $filenameHash[1];
+        $sigDisplay = str_replace("\r", '', $sigDisplay);
+        echo h($sigDisplay);
     }
-  } else if (strpos($object['type'], '|') !== false) {
-    $separator = in_array($object['type'], array('ip-dst|port', 'ip-src|port')) ? ':' : '<br />';
-    $value_pieces = explode('|', $object['value']);
-    foreach ($value_pieces as $k => $v) {
-      $value_pieces[$k] = h($v);
-    }
-    $object['value'] = implode($separator, $value_pieces);
-    echo ($object['value']);
-  } else if ('vulnerability' == $object['type']) {
-    $cveUrl = (is_null(Configure::read('MISP.cveurl'))) ? "http://www.google.com/search?q=" : Configure::read('MISP.cveurl');
-    echo $this->Html->link($sigDisplay, $cveUrl . $sigDisplay, array('target' => '_blank', 'class' => $linkClass));
-  } else if ('link' == $object['type']) {
-    echo $this->Html->link($sigDisplay, $sigDisplay, array('class' => $linkClass));
-  } else if ('cortex' == $object['type']) {
-    echo '<div class="cortex-json" data-cortex-json="' . h($object['value']) . '">Cortex object</div>';
-  } else if ('text' == $object['type']) {
-    if (($object['category'] == 'Internal reference' || $object['category'] == 'External analysis') && preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i', $object['value'])) {
-      echo '<a href="' . $baseurl . '/events/view/' . h($object['value']) . '" class="' . $linkClass . '">' . h($object['value']) . '</a>';
-    } else {
-      $sigDisplay = str_replace("\r", '', h($sigDisplay));
-      $sigDisplay = str_replace(" ", '&nbsp;', $sigDisplay);
-      echo $sigDisplay;
+    if (isset($object['validationIssue'])) {
+        echo ' <span class="fa fa-exclamation-triangle" title="' . __('Warning, this doesn\'t seem to be a legitimate ') . strtoupper(h($object['type'])) . __(' value') . '">&nbsp;</span>';
     }
-  } else if ('hex' == $object['type']) {
-    $sigDisplay = str_replace("\r", '', $sigDisplay);
-    echo '<span class="hex-value" title="' . __('Hexadecimal representation') . '">' . h($sigDisplay) . '</span>&nbsp;<span role="button" tabindex="0" aria-label="' . __('Switch to binary representation') . '" class="icon-repeat hex-value-convert useCursorPointer" title="' . __('Switch to binary representation') . '"></span>';
-  } else {
-    $sigDisplay = str_replace("\r", '', $sigDisplay);
-    echo h($sigDisplay);
-  }
-  if (isset($object['validationIssue'])) echo ' <span class="fa fa-exclamation-triangle" title="' . __('Warning, this doesn\'t seem to be a legitimate ') . strtoupper(h($object['type'])) . __(' value') . '">&nbsp;</span>';
 ?>

Vulnerable code:   $sigDisplay = $object['value'];
  if ('attachment' == $object['type'] || 'malware-sample' == $object['type'] ) {
    if ($object['type'] == 'attachment' && isset($object['image'])) {
        if (extension_loaded('gd')) {
            $img = '<it class="fa fa-spin fa-spinner" style="font-size: large; left: 50%; top: 50%;"></it>';
            $img .= '<img class="screenshot screenshot-collapsed useCursorPointer img-rounded hidden" src="' . $baseurl . '/attributes/viewPicture/' . h($object['id']) . '/1' . '" title="' . h($object['value']) . '" onload="$(this).show(200); $(this).parent().find(\'.fa-spinner\').remove();"/>';
            echo $img;
            $extension = explode('.', $object['value']);
            $extension = end($extension);
            $uri = 'data:image/' . strtolower(h($extension)) . ';base64,' . h($object['image']);
            echo '<img class="screenshot screenshot-collapsed useCursorPointer" src="' . $uri . '" title="' . h($object['value']) . '" />';
      $filenameHash = explode('|', h($object['value']));
      if (strrpos($filenameHash[0], '\\')) {
        $filepath = substr($filenameHash[0], 0, strrpos($filenameHash[0], '\\'));
        $filename = substr($filenameHash[0], strrpos($filenameHash[0], '\\'));
        echo h($filepath);
        echo '<a href="' . $baseurl . '/attributes/download/' . h($object['id']) . '" class="' . $linkClass . '">' . h($filename) . '</a>';
      } else {
        echo '<a href="' . $baseurl . '/attributes/download/' . h($object['id']) . '" class="' . $linkClass . '">' . h($filenameHash[0]) . '</a>';
      }
      if (isset($filenameHash[1])) echo '<br />' . $filenameHash[1];
  } else if (strpos($object['type'], '|') !== false) {
    $separator = in_array($object['type'], array('ip-dst|port', 'ip-src|port')) ? ':' : '<br />';
    $value_pieces = explode('|', $object['value']);
    foreach ($value_pieces as $k => $v) {
      $value_pieces[$k] = h($v);
    }
    $object['value'] = implode($separator, $value_pieces);
    echo ($object['value']);
  } else if ('vulnerability' == $object['type']) {
    $cveUrl = (is_null(Configure::read('MISP.cveurl'))) ? "http://www.google.com/search?q=" : Configure::read('MISP.cveurl');
    echo $this->Html->link($sigDisplay, $cveUrl . $sigDisplay, array('target' => '_blank', 'class' => $linkClass));
  } else if ('link' == $object['type']) {
    echo $this->Html->link($sigDisplay, $sigDisplay, array('class' => $linkClass));
  } else if ('cortex' == $object['type']) {
    echo '<div class="cortex-json" data-cortex-json="' . h($object['value']) . '">Cortex object</div>';
  } else if ('text' == $object['type']) {
    if (($object['category'] == 'Internal reference' || $object['category'] == 'External analysis') && preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i', $object['value'])) {
      echo '<a href="' . $baseurl . '/events/view/' . h($object['value']) . '" class="' . $linkClass . '">' . h($object['value']) . '</a>';
    } else {
      $sigDisplay = str_replace("\r", '', h($sigDisplay));
      $sigDisplay = str_replace(" ", '&nbsp;', $sigDisplay);
      echo $sigDisplay;
  } else if ('hex' == $object['type']) {
    $sigDisplay = str_replace("\r", '', $sigDisplay);
    echo '<span class="hex-value" title="' . __('Hexadecimal representation') . '">' . h($sigDisplay) . '</span>&nbsp;<span role="button" tabindex="0" aria-label="' . __('Switch to binary representation') . '" class="icon-repeat hex-value-convert useCursorPointer" title="' . __('Switch to binary representation') . '"></span>';
  } else {
    $sigDisplay = str_replace("\r", '', $sigDisplay);
    echo h($sigDisplay);
  }
  if (isset($object['validationIssue'])) echo ' <span class="fa fa-exclamation-triangle" title="' . __('Warning, this doesn\'t seem to be a legitimate ') . strtoupper(h($object['type'])) . __(' value') . '">&nbsp;</span>';
Fixed code:     $sigDisplay = $object['value'];
    if ('attachment' == $object['type'] || 'malware-sample' == $object['type'] ) {
        if ($object['type'] == 'attachment' && isset($object['image'])) {
            if (extension_loaded('gd')) {
                $img = '<it class="fa fa-spin fa-spinner" style="font-size: large; left: 50%; top: 50%;"></it>';
                $img .= '<img class="screenshot screenshot-collapsed useCursorPointer img-rounded hidden" src="' . $baseurl . '/attributes/viewPicture/' . h($object['id']) . '/1' . '" title="' . h($object['value']) . '" onload="$(this).show(200); $(this).parent().find(\'.fa-spinner\').remove();"/>';
                echo $img;
            } else {
                $extension = explode('.', $object['value']);
                $extension = end($extension);
                $uri = 'data:image/' . strtolower(h($extension)) . ';base64,' . h($object['image']);
                echo '<img class="screenshot screenshot-collapsed useCursorPointer" src="' . $uri . '" title="' . h($object['value']) . '" />';
            }
            $filenameHash = explode('|', h($object['value']));
            if (strrpos($filenameHash[0], '\\')) {
                $filepath = substr($filenameHash[0], 0, strrpos($filenameHash[0], '\\'));
                $filename = substr($filenameHash[0], strrpos($filenameHash[0], '\\'));
                echo h($filepath);
                echo '<a href="' . $baseurl . '/attributes/download/' . h($object['id']) . '" class="' . $linkClass . '">' . h($filename) . '</a>';
            } else {
                echo '<a href="' . $baseurl . '/attributes/download/' . h($object['id']) . '" class="' . $linkClass . '">' . h($filenameHash[0]) . '</a>';
            }
            if (isset($filenameHash[1])) echo '<br />' . $filenameHash[1];
    } else if (strpos($object['type'], '|') !== false) {
        $separator = in_array($object['type'], array('ip-dst|port', 'ip-src|port')) ? ':' : '<br />';
        $value_pieces = explode('|', $object['value']);
        foreach ($value_pieces as $k => $v) {
            $value_pieces[$k] = h($v);
        }
        $object['value'] = implode($separator, $value_pieces);
        echo ($object['value']);
    } else if ('vulnerability' == $object['type']) {
        $cveUrl = (is_null(Configure::read('MISP.cveurl'))) ? "http://www.google.com/search?q=" : Configure::read('MISP.cveurl');
        echo $this->Html->link($sigDisplay, $cveUrl . $sigDisplay, array('target' => '_blank', 'class' => $linkClass));
    } else if ('link' == $object['type'] && (substr($object['value'], 0, 7) === 'http://' || substr($object['value'], 0, 8) === 'https://')) {
        echo $this->Html->link($sigDisplay, $sigDisplay, array('class' => $linkClass));
    } else if ('cortex' == $object['type']) {
        echo '<div class="cortex-json" data-cortex-json="' . h($object['value']) . '">Cortex object</div>';
    } else if ('text' == $object['type']) {
        if (($object['category'] == 'Internal reference' || $object['category'] == 'External analysis') && preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i', $object['value'])) {
            echo '<a href="' . $baseurl . '/events/view/' . h($object['value']) . '" class="' . $linkClass . '">' . h($object['value']) . '</a>';
        } else {
            $sigDisplay = str_replace("\r", '', h($sigDisplay));
            $sigDisplay = str_replace(" ", '&nbsp;', $sigDisplay);
            echo $sigDisplay;
        }
    } else if ('hex' == $object['type']) {
        $sigDisplay = str_replace("\r", '', $sigDisplay);
        echo '<span class="hex-value" title="' . __('Hexadecimal representation') . '">' . h($sigDisplay) . '</span>&nbsp;<span role="button" tabindex="0" aria-label="' . __('Switch to binary representation') . '" class="icon-repeat hex-value-convert useCursorPointer" title="' . __('Switch to binary representation') . '"></span>';
        $sigDisplay = str_replace("\r", '', $sigDisplay);
        echo h($sigDisplay);
    if (isset($object['validationIssue'])) {
        echo ' <span class="fa fa-exclamation-triangle" title="' . __('Warning, this doesn\'t seem to be a legitimate ') . strtoupper(h($object['type'])) . __(' value') . '">&nbsp;</span>';