Skip to content

Commit afc5d4f

Browse files
authored
Merge pull request #7 from NielBuys/tasks/issue6252_xss_clean_fix
Fix xss_clean issue %
2 parents e6936c1 + 59017eb commit afc5d4f

File tree

3 files changed

+98
-43
lines changed

3 files changed

+98
-43
lines changed

contributing.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,7 @@ If you are using command-line you can do the following:
8888
3. `git push origin develop`
8989

9090
Now your fork is up to date. This should be done regularly, or before you send a pull request at least.
91+
92+
## PHP Unit test command
93+
1. `composer install --dev`
94+
2. `./vendor/bin/phpunit --color=always --coverage-text --configuration tests/phpunit.xml`

system/core/Security.php

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -398,17 +398,33 @@ public function xss_clean($str, $is_image = FALSE)
398398
*
399399
* Note: Use rawurldecode() so it does not remove plus signs
400400
*/
401-
if (stripos($str, '%') !== false)
402-
{
403-
do
404-
{
405-
$oldstr = $str;
406-
$str = rawurldecode($str);
407-
$str = preg_replace_callback('#%(?:\s*[0-9a-f]){2,}#i', array($this, '_urldecodespaces'), $str);
401+
// List of known malicious encoded patterns (hexadecimal)
402+
$malicious_encodings = [
403+
'%3C' => '<', // <
404+
'%3E' => '>', // >
405+
'%22' => '"', // "
406+
'%27' => "'", // '
407+
'%3D' => '=', // =
408+
'%28' => '(', // (
409+
'%29' => ')', // )
410+
'%2F' => '/', // /
411+
'%5C' => '\\', // \
412+
'%3B' => ';', // ;
413+
];
414+
415+
// Normalize optionally spaced encodings like "% 3C"
416+
$str = preg_replace_callback('/%[\s]*[0-9a-fA-F]{2}/', function ($m) use ($malicious_encodings) {
417+
// Normalize encoding: remove spaces
418+
$clean = strtoupper(preg_replace('/\s+/', '', $m[0])); // e.g., "% 3C" → "%3C"
419+
420+
// If the encoding is a known malicious encoding, replace it
421+
if (isset($malicious_encodings[$clean])) {
422+
return $malicious_encodings[$clean];
408423
}
409-
while ($oldstr !== $str);
410-
unset($oldstr);
411-
}
424+
425+
// Return as-is if not malicious (e.g., malformed encodings like %2G, %3)
426+
return $m[0];
427+
}, $str);
412428

413429
/*
414430
* Convert character entities to ASCII
@@ -814,24 +830,6 @@ public function strip_image_tags($str)
814830

815831
// ----------------------------------------------------------------
816832

817-
/**
818-
* URL-decode taking spaces into account
819-
*
820-
* @see https://github.com/bcit-ci/CodeIgniter/issues/4877
821-
* @param array $matches
822-
* @return string
823-
*/
824-
protected function _urldecodespaces($matches)
825-
{
826-
$input = $matches[0];
827-
$nospaces = preg_replace('#\s+#', '', $input);
828-
return ($nospaces === $input)
829-
? $input
830-
: rawurldecode($nospaces);
831-
}
832-
833-
// ----------------------------------------------------------------
834-
835833
/**
836834
* Compact Exploded Words
837835
*

tests/codeigniter/core/Security_test.php

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ public function test_csrf_verify()
2828

2929
public function test_csrf_verify_invalid()
3030
{
31-
// Without issuing $_POST[csrf_token_name], this request will triggering CSRF error
3231
$_SERVER['REQUEST_METHOD'] = 'POST';
33-
34-
$this->setExpectedException('RuntimeException', 'CI Error: The action you have requested is not allowed');
35-
32+
33+
$this->expectException('RuntimeException');
34+
$this->expectExceptionMessage('CI Error: The action you have requested is not allowed');
35+
3636
$this->security->csrf_verify();
3737
}
3838

@@ -64,11 +64,16 @@ public function test_get_csrf_token_name()
6464

6565
public function test_xss_clean()
6666
{
67+
// Test case 1: Injecting a harmful script
6768
$harm_string = "Hello, i try to <script>alert('Hack');</script> your site";
68-
6969
$harmless_string = $this->security->xss_clean($harm_string);
70-
7170
$this->assertEquals("Hello, i try to [removed]alert&#40;'Hack'&#41;;[removed] your site", $harmless_string);
71+
72+
// Test case 2: Make sure "60% acqua" is preserved (check if % encoding is not altered)
73+
$input_string = "60% acqua";
74+
$preserved_string = $this->security->xss_clean($input_string);
75+
$this->assertEquals("60% acqua", $preserved_string);
76+
7277
}
7378

7479
// --------------------------------------------------------------------
@@ -93,10 +98,8 @@ public function test_xss_clean_string_array()
9398
public function test_xss_clean_image_valid()
9499
{
95100
$harm_string = '<img src="test.png">';
96-
97101
$xss_clean_return = $this->security->xss_clean($harm_string, TRUE);
98-
99-
// $this->assertTrue($xss_clean_return);
102+
$this->assertTrue($xss_clean_return);
100103
}
101104

102105
// --------------------------------------------------------------------
@@ -248,14 +251,11 @@ public function test_naughty_html_plus_evil_attributes()
248251
public function test_xss_hash()
249252
{
250253
$this->assertEmpty($this->security->xss_hash);
251-
254+
252255
// Perform hash
253256
$this->security->xss_hash();
254-
255-
$assertRegExp = method_exists($this, 'assertMatchesRegularExpression')
256-
? 'assertMatchesRegularExpression'
257-
: 'assertRegExp';
258-
$this->$assertRegExp('#^[0-9a-f]{32}$#iS', $this->security->xss_hash);
257+
258+
$this->assertMatchesRegularExpression('#^[0-9a-f]{32}$#iS', $this->security->xss_hash);
259259
}
260260

261261
// --------------------------------------------------------------------
@@ -353,4 +353,57 @@ public function test_csrf_set_hash()
353353

354354
$this->assertNotEmpty($this->security->get_csrf_hash());
355355
}
356+
357+
public function test_xss_clean_with_malicious_and_malformed_percent_encoding()
358+
{
359+
// Malicious and malformed encoded characters
360+
$input_string = "This is a test with malicious and characters, malformed %3, %2G and % 3C encodings, %22double quotes%22 and %27single quotes%27.";
361+
362+
// Apply XSS cleaning
363+
$output_string = $this->security->xss_clean($input_string);
364+
365+
// Updated expectation to match HTML entity encoding
366+
$this->assertEquals("This is a test with malicious and characters, malformed %3, %2G and &lt; encodings, \"double quotes\" and 'single quotes'.", $output_string);
367+
}
368+
369+
public function test_xss_clean_double_encoded_percent()
370+
{
371+
// Double-encoded characters (e.g., %25 = '%')
372+
$input_string = "This is a double encoded %25 percent symbol.";
373+
$output_string = $this->security->xss_clean($input_string);
374+
375+
// Update the expected output, leaving %25 as it is (since it's not malicious)
376+
$this->assertEquals("This is a double encoded %25 percent symbol.", $output_string);
377+
}
378+
379+
public function test_xss_clean_case_sensitive_percent_encoding()
380+
{
381+
// Uppercase and lowercase hex encoding + malicious encoding (%3C, %3E)
382+
$input_string = "Hex encoded characters: %7A, %7a, %41, %61, %3C, %3E.";
383+
$output_string = $this->security->xss_clean($input_string);
384+
385+
// The expected output should have < and > encoded as HTML entities (&lt; and &gt;)
386+
$this->assertEquals("Hex encoded characters: %7A, %7a, %41, %61, &lt;, >.", $output_string);
387+
}
388+
389+
public function test_xss_clean_non_alphanumeric_encoding()
390+
{
391+
// Non-alphanumeric characters encoded
392+
$input_string = "Symbols like %40at, %23hash, %3Clt%3E, %26gt are encoded.";
393+
$output_string = $this->security->xss_clean($input_string);
394+
395+
// Update expected output to match the behavior of the xss_clean method
396+
$this->assertEquals("Symbols like %40at, %23hash, <lt>, %26gt are encoded.", $output_string);
397+
}
398+
399+
400+
public function test_xss_clean_multiple_encoded_sequences()
401+
{
402+
// Multiple encodings in sequence, including malicious encodings
403+
$input_string = "This is a test string with %20spaces, %23hashes, %3Clt%3E, %26gt multiple times: %20 and %3Clt%3E.";
404+
$output_string = $this->security->xss_clean($input_string);
405+
406+
// The expected output should preserve the encoded characters like <lt> and %26gt
407+
$this->assertEquals("This is a test string with %20spaces, %23hashes, <lt>, %26gt multiple times: %20 and <lt>.", $output_string);
408+
}
356409
}

0 commit comments

Comments
 (0)