Fix #3572: CI_Security::_remove_evil_attributes()
diff --git a/system/core/Security.php b/system/core/Security.php
index ccb1412..216f0e9 100644
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -784,30 +784,15 @@
 		}
 
 		do {
-			$count = 0;
-			$attribs = array();
+			$count = $temp_count = 0;
 
-			// find occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes)
-			preg_match_all('/(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER);
-
-			foreach ($matches as $attr)
-			{
-				$attribs[] = preg_quote($attr[0], '/');
-			}
+			// replace occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes)
+			$str = preg_replace('/(<[^>]+)(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', '$1[removed]', $str, -1, $temp_count);
+			$count += $temp_count;
 
 			// find occurrences of illegal attribute strings without quotes
-			preg_match_all('/(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);
-
-			foreach ($matches as $attr)
-			{
-				$attribs[] = preg_quote($attr[0], '/');
-			}
-
-			// replace illegal attribute strings that are inside an html tag
-			if (count($attribs) > 0)
-			{
-				$str = preg_replace('/(<?)(\/?[^><]+?)([^A-Za-z<>\-])(.*?)('.implode('|', $attribs).')(.*?)([\s><]?)([><]*)/i', '$1$2 $4$6$7$8', $str, -1, $count);
-			}
+			$str = preg_replace('/(<[^>]+)(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', '$1[removed]', $str, -1, $temp_count);
+			$count += $temp_count;
 		}
 		while ($count);
 
diff --git a/tests/codeigniter/core/Security_test.php b/tests/codeigniter/core/Security_test.php
index 402422f..d967613 100644
--- a/tests/codeigniter/core/Security_test.php
+++ b/tests/codeigniter/core/Security_test.php
@@ -79,6 +79,18 @@
 
 	// --------------------------------------------------------------------
 
+	public function test_remove_evil_attributes()
+	{
+		$this->assertEquals('<foo [removed]>', $this->security->remove_evil_attributes('<foo onAttribute="bar">', false));
+		$this->assertEquals('<foo [removed]>', $this->security->remove_evil_attributes('<foo onAttributeNoQuotes=bar>', false));
+		$this->assertEquals('<foo [removed]>', $this->security->remove_evil_attributes('<foo onAttributeWithSpaces = bar>', false));
+		$this->assertEquals('<foo prefixOnAttribute="bar">', $this->security->remove_evil_attributes('<foo prefixOnAttribute="bar">', false));
+		$this->assertEquals('<foo>onOutsideOfTag=test</foo>', $this->security->remove_evil_attributes('<foo>onOutsideOfTag=test</foo>', false));
+		$this->assertEquals('onNoTagAtAll = true', $this->security->remove_evil_attributes('onNoTagAtAll = true', false));
+	}
+
+	// --------------------------------------------------------------------
+
 	public function test_xss_hash()
 	{
 		$this->assertEmpty($this->security->xss_hash);
diff --git a/tests/mocks/core/security.php b/tests/mocks/core/security.php
index a21fc5c..6cff858 100644
--- a/tests/mocks/core/security.php
+++ b/tests/mocks/core/security.php
@@ -16,6 +16,11 @@
 		return isset($this->{'_'.$property}) ? $this->{'_'.$property} : NULL;
 	}
 
+	public function remove_evil_attributes($str, $is_image)
+	{
+		return $this->_remove_evil_attributes($str, $is_image);
+	}
+
 	// Override inaccessible protected method
 	public function __call($method, $params)
 	{