diff --git a/config/func.inc.php b/config/func.inc.php
index a5d718bee..6bd06971f 100644
--- a/config/func.inc.php
+++ b/config/func.inc.php
@@ -5,7 +5,7 @@
* @brief function library files for convenience
**/
- if(!defined('__ZBXE__')) exit();
+ if(!defined('__XE__')) exit();
/**
* @brief define clone for php5
@@ -29,7 +29,6 @@
');
}
-
// time zone
$time_zone = array(
'-1200' => '[GMT -12:00] Baker Island Time',
@@ -653,13 +652,13 @@
**/
function removeHackTag($content) {
// change the specific tags to the common texts
- $content = preg_replace('/<(\/?)(iframe|script|meta|style|applet|link|base|html|body)/is', '<$1$2', $content);
+ $content = preg_replace('@<(\/?(?:html|body|head|title|meta|base|link|script|style|applet|iframe)[\s>])@i', '<$1', $content);
/**
* Remove codes to abuse the admin session in src by tags of imaages and video postings
* - Issue reported by Sangwon Kim
**/
- $content = preg_replace_callback("!<(/?)([a-z]+)(.*?)>!is", removeSrcHack, $content);
+ $content = preg_replace_callback('@<(/?)([a-z]+[0-9]?)([^>]*?\b(?:on[a-z]+|data|style|background|href|(?:dyn|low)?src)\s*=[\s\S]*?)(/?)>@i', 'removeSrcHack', $content);
// xmp tag 확인 및 추가
$content = checkXmpTag($content);
@@ -671,6 +670,8 @@
* @brief xmp tag 확인 및 닫히지 않은 경우 추가
**/
function checkXmpTag($content) {
+ $content = preg_replace('@<(/?)xmp.*?>@i', '<\1xmp>', $content);
+
if(($start_xmp = strrpos($content, '
')) !==false) {
if(($close_xmp = strrpos($content, '')) === false) $content .= '';
else if($close_xmp < $start_xmp) $content .= '';
@@ -679,106 +680,41 @@
return $content;
}
- function removeSrcHack($matches) {
- $tag = strtolower(trim($matches[2]));
-
+ function removeSrcHack($match) {
+ $tag = strtolower($match[2]);
+
// xmp tag 정리
- if($tag=='xmp') return '<'.$matches[1].'xmp>';
- if($matches[1]=='/') return $matches[0];
+ if($tag=='xmp') return "<{$match[1]}xmp>";
+ if($match[1]) return $match[0];
+ if($match[4]) $match[4] = ' '.$match[4];
- //$buff = trim(preg_replace('/(\/>|>)/','/>',$matches[0]));
- $buff = $matches[0];
- $buff = str_replace(array('&','&'),array('&','&'),$buff);
- $buff = preg_replace_callback('/([^=^"^ ]*)=([^ ^>]*)/i', 'fixQuotation', $buff);
+ $attrs = array();
+ if(preg_match_all('/([\w:-]+)\s*=(?:\s*(["\']))?(?(2)(.*?)\2|([^ ]+))/s', $match[3], $m)) {
+ foreach($m[1] as $idx=>$name){
+ if(substr($name,0,2) == 'on') continue;
- $oXmlParser = new XmlParser();
- $xml_doc = $oXmlParser->parse($buff);
- if(!$xml_doc) return sprintf("<%s>", $tag);
+ $val = preg_replace('/(?:x([a-fA-F0-9]+)|0*(\d+));/e','chr("\\1"?0x00\\1:\\2+0)',$m[3][$idx].$m[4][$idx]);
+ $val = preg_replace('/^\s+|[\t\n\r]+/', '', $val);
- // invalidate the value if src value is module = admin.
- $src = $xml_doc->attrs->src;
- $dynsrc = $xml_doc->attrs->dynsrc;
- $lowsrc = $xml_doc->attrs->lowsrc;
- $href = $xml_doc->attrs->href;
- $data = $xml_doc->attrs->data;
- $background = $xml_doc->attrs->background;
- $style = $xml_doc->attrs->style;
- if($style) {
- $url = preg_match_all('/url\s*\(([^\)]+)\)/is', $style, $matches2);
- if(count($matches2[0]))
- {
- foreach($matches2[1] as $target)
- {
- if(_isHackedSrc($target)) return sprintf("<%s>",$tag);
- }
+ if(preg_match('/^[a-z]+script:/i', $val)) continue;
+
+ $attrs[$name] = $val;
}
}
- if(_isHackedSrc($src) || _isHackedSrc($dynsrc) || _isHackedSrc($lowsrc) || _isHackedSrc($href) || _isHackedSrc($data) || _isHackedSrc($background) || _isHackedSrcExp($style)) return sprintf("<%s>",$tag);
- if($tag=='param' && $xml_doc->attrs->value && preg_match('/^javascript:/i',$xml_doc->attrs->value)) return sprintf("<%s>",$tag);
- if($tag=='object' && $xml_doc->attrs->data && preg_match('/^javascript:/i',$xml_doc->attrs->data)) return sprintf("<%s>",$tag);
-
- return $buff;
- }
-
- function _isHackedSrcExp($style) {
- if(!$style) return false;
- if(preg_match('/((\/\*)|(\*\/)|(\\n)|(expression))/i', $style)) return true;
- return false;
- }
-
- function _isHackedSrc($src) {
- if(!$src) return false;
- if($src) {
- $target = trim($src);
- if(preg_match('/(\s|(\&\#)|(script:))/i', $target)) return true;
- if(preg_match('/data:/i', $target)) return true;
-
- $url_info = parse_url($src);
- $query = $url_info['query'];
- if(!trim($query)) return false;
- $query = str_replace("&","&",$query);
- $queries = explode('&', $query);
- $cnt = count($queries);
- for($i=0;$i<$cnt;$i++) {
- $tmp_str = strtolower(trim($queries[$i]));
- $pos = strpos($tmp_str,'=');
- if($pos === false) continue;
- $key = strtolower(trim(substr($tmp_str, 0, $pos)));
- $val = strtolower(trim(substr($tmp_str,$pos+1)));
- if( ($key=='module'&&$val=='admin') || ($key=='act'&&preg_match('/admin/i',$val)) ) return true;
- }
- }
- return false;
- }
-
- /**
- * @brief function to enclose attribute values to double quotes(")
- **/
- function fixQuotation($matches) {
- $key = $matches[1];
- $val = trim($matches[2]);
-
- $close_tag = false;
- if(substr($val,-1)=='/') {
- $close_tag = true;
- $val = rtrim(substr($val,0,-1));
+ if(isset($attrs['style']) && preg_match('@(?:/\*|\*/|\n|:\s*expression\s*\()@i', $attrs['style'])) {
+ unset($attrs['style']);
}
- if($val{0}=="'" && substr($val,-1)=="'")
- {
- $val = sprintf('"%s"', substr($val,1,-1));
+ $attr = array();
+ foreach($attrs as $name=>$val) {
+ $attr[] = $name."=\"{$val}\"";
}
+ $attr = count($attr)?' '.implode(' ',$attr):'';
- if($close_tag) $val .= ' /';
-
- // attribute on* remove
- if(preg_match('/^on([a-z]+)/i',preg_replace('/[^a-zA-Z_]/','',$key))) return '';
-
- $output = sprintf('%s=%s', $key, $val);
-
- return $output;
+ return "<{$match[1]}{$tag}{$attr}{$match[4]}>";
}
+
// convert hexa value to RGB
if(!function_exists('hexrgb')) {
function hexrgb($hexstr) {
@@ -788,7 +724,6 @@
'green' => 0xFF & ($int >> 0x8),
'blue' => 0xFF & $int);
}
-
}
/**
diff --git a/tests/FuncIncTest.class.php b/tests/FuncIncTest.class.php
new file mode 100644
index 000000000..7179f083a
--- /dev/null
+++ b/tests/FuncIncTest.class.php
@@ -0,0 +1,48 @@
+IFrame
',
+ '<iframe src="path/to/file.html"></iframe>
IFrame
'
+ ),
+ // expression
+ array(
+ '',
+ '
'
+ ),
+ // no quotes and no semicolon - http://ha.ckers.org/xss.html
+ array(
+ '
)
',
+ '
![]()
'
+ ),
+ // embedded encoded tab to break up XSS - http://ha.ckers.org/xss.html
+ array(
+ '
;)
',
+ '
![]()
'
+ ),
+ // issue 178
+ array(
+ "
\")
",
+ '

'
+ )
+ );
+ }
+
+ /**
+ * @dataProvider xssProvider
+ */
+ public function testXSS($source, $expected)
+ {
+ $result = removeHackTag($source);
+ $this->assertEquals($result, $expected);
+ }
+}