From 1b249517781dbb49aa19040d7bb6d446d325bf8e Mon Sep 17 00:00:00 2001 From: Zachary Durber Date: Mon, 5 Jan 2015 11:01:34 +0800 Subject: [PATCH] MDL-38466 filters: ReDoS protection for text to URL conversion. --- filter/urltolink/filter.php | 37 +++++++++++++++++++++++--- filter/urltolink/tests/filter_test.php | 11 ++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/filter/urltolink/filter.php b/filter/urltolink/filter.php index 538d03e8e4f..b9e5e14c54d 100644 --- a/filter/urltolink/filter.php +++ b/filter/urltolink/filter.php @@ -134,10 +134,9 @@ class filter_urltolink extends moodle_text_filter { // Lookbehind assertions. // Is not HTML attribute or CSS URL property. Unfortunately legit text like "url(http://...)" will not be a link. - $lookbehindstart = "(?$0', $text); + // Locate any HTML tags. + $matches = preg_split('/(<[^>]*>)/i', $text, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $lookingforendstyle = false; + + // Iterate through the tokenized text to handle chunks (html and content). + foreach ($matches as $idx => $chunk) { + // Nothing to do. We skip completely any html chunk. + if (strpos($chunk, '<') !== false) { + continue; + } + + // Nothing to do. We skip any content chunk having any of these attributes. + if (preg_match('#(background=")|(action=")|(style="background)|(href=")|(src=")|(url [(])#', $chunk)) { + continue; + } + + // Arrived here, we want to process every word in this chunk. + $text = $chunk; + $words = explode(' ', $text); + + foreach ($words as $idx2 => $word) { + // ReDoS protection. Stop processing if a word is too large. + if (strlen($word) < 4096) { + $words[$idx2] = preg_replace($regex, '$0', $word); + } + } + $text = implode(' ', $words); + + // Copy the result back to the array. + $matches[$idx] = $text; + } + + $text = implode('', $matches); if (!empty($ignoretags)) { $ignoretags = array_reverse($ignoretags); /// Reversed so "progressive" str_replace() will solve some nesting problems. diff --git a/filter/urltolink/tests/filter_test.php b/filter/urltolink/tests/filter_test.php index 58bd4a3528d..ee9c2c4b3c6 100644 --- a/filter/urltolink/tests/filter_test.php +++ b/filter/urltolink/tests/filter_test.php @@ -32,6 +32,10 @@ require_once($CFG->dirroot . '/filter/urltolink/filter.php'); // Include the cod class filter_urltolink_testcase extends basic_testcase { function get_convert_urls_into_links_test_cases() { + // Create a 4095 and 4096 long URLs. + $superlong4095 = str_pad('http://www.superlong4095.com?this=something', 4095, 'a'); + $superlong4096 = str_pad('http://www.superlong4096.com?this=something', 4096, 'a'); + $texts = array ( //just a url 'http://moodle.org - URL' => 'http://moodle.org - URL', @@ -130,6 +134,7 @@ class filter_urltolink_testcase extends basic_testcase { ' ' => ' ', ' ' => ' ', '
'=>'', + '' => '', ' ' => ' ', // CSS URLs. '' => '
', @@ -148,6 +153,12 @@ class filter_urltolink_testcase extends basic_testcase { //Encoded URLs in the query 'URL: http://127.0.0.1/path/to?param=value_with%28parenthesis%29¶m2=1' => 'URL: http://127.0.0.1/path/to?param=value_with%28parenthesis%29¶m2=1', 'URL: www.localhost.com/path/to?param=value_with%28parenthesis%29¶m2=1' => 'URL: www.localhost.com/path/to?param=value_with%28parenthesis%29¶m2=1', + // Test URL less than 4096 characters in size is converted to link. + 'URL: ' . $superlong4095 => 'URL: ' . $superlong4095 . '', + // Test URL equal to or greater than 4096 characters in size is not converted to link. + 'URL: ' . $superlong4096 => 'URL: ' . $superlong4096, + // Testing URL within a span tag. + 'URL: my link to http://google.com ' => 'URL: my link to http://google.com ', //URLs in Javascript. Commented out as part of MDL-21183 //'var url="http://moodle.org";'=>'var url="http://moodle.org";', //'var url = "http://moodle.org";'=>'var url = "http://moodle.org";',