From 8a367b93e50cc759d9f678d649b3c505fc8fc098 Mon Sep 17 00:00:00 2001 From: csteipp Date: Tue, 1 May 2012 10:29:11 -0700 Subject: [PATCH] (bug 29296) Expanded Blacklist for SVG Files Expanded detection of scripts inside SVG Files. Catches known hostile files with minimal false-positives. Disallows SVG Files that include remote resources, to protect reader's privacy. Change-Id: I5ba75ad621cdb0355351c8a9a0f1dbea552c258f --- includes/upload/UploadBase.php | 84 ++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index f6078dcb4e..83ffed9cf3 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -972,24 +972,100 @@ abstract class UploadBase { * @return bool */ public function checkSvgScriptCallback( $element, $attribs ) { - $stripped = $this->stripXmlNamespace( $element ); + $strippedElement = $this->stripXmlNamespace( $element ); - if( $stripped == 'script' ) { + /* + * check for elements that can contain javascript + */ + if( $strippedElement == 'script' ) { wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" ); return true; } + # e.g., alert(1) + if( $strippedElement == 'handler' ) { + wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" ); + return true; + } + + # SVG reported in Feb '12 that used xml:stylesheet to generate javascript block + if( $strippedElement == 'stylesheet' ) { + wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" ); + return true; + } + foreach( $attribs as $attrib => $value ) { $stripped = $this->stripXmlNamespace( $attrib ); + $value = strtolower($value); + if( substr( $stripped, 0, 2 ) == 'on' ) { - wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" ); + wfDebug( __METHOD__ . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" ); return true; } + + # href with javascript target if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) { - wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" ); + wfDebug( __METHOD__ . ": Found script in href attribute '$attrib'='$value' in uploaded file.\n" ); + return true; + } + + # href with embeded svg as target + if( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) { + wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" ); + return true; + } + + # href with embeded (text/xml) svg as target + if( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) { + wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" ); + return true; + } + + # use set/animate to add event-handler attribute to parent + if( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) { + wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" ); return true; } + + # use set to add href attribute to parent element + if( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) { + wfDebug( __METHOD__ . ": Found svg setting href attibute '$value' in uploaded file.\n" ); + return true; + } + + # use set to add a remote / data / script target to an element + if( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) { + wfDebug( __METHOD__ . ": Found svg setting attibute to '$value' in uploaded file.\n" ); + return true; + } + + + # use handler attribute with remote / data / script + if( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) { + wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script '$attrib'='$value' in uploaded file.\n" ); + return true; + } + + # use CSS styles to bring in remote code + # catch url("http:..., url('http:..., url(http:..., but not url("#..., url('#..., url(#.... + if( $stripped == 'style' && preg_match_all( '!((?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim', $value, $matches ) ) { + foreach ($matches[1] as $match) { + if (!preg_match( '!(?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*(#|\'#|"#)!sim', $match ) ) { + wfDebug( __METHOD__ . ": Found svg setting a style with remote url '$attrib'='$value' in uploaded file.\n" ); + return true; + } + } + } + + # image filters can pull in url, which could be svg that executes scripts + if( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) { + wfDebug( __METHOD__ . ": Found image filter with url: \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" ); + return true; + } + } + + return false; //No scripts detected } private function stripXmlNamespace( $name ) { -- 2.20.1