From 310ef11dac60dd1a2bd2ac600438ef5ac1180d06 Mon Sep 17 00:00:00 2001
From: Laurent Destailleur
Date: Sun, 13 Aug 2023 15:45:45 +0200
Subject: [PATCH] FIX WAF
---
htdocs/comm/mailing/class/mailing.class.php | 8 +++---
htdocs/core/class/html.form.class.php | 9 +++---
htdocs/core/lib/functions.lib.php | 23 ++++++++-------
htdocs/main.inc.php | 32 ++++++++++-----------
htdocs/website/index.php | 4 +--
test/phpunit/SecurityTest.php | 10 +++++--
6 files changed, 46 insertions(+), 40 deletions(-)
diff --git a/htdocs/comm/mailing/class/mailing.class.php b/htdocs/comm/mailing/class/mailing.class.php
index 4371848c522..b6c542fb4a8 100644
--- a/htdocs/comm/mailing/class/mailing.class.php
+++ b/htdocs/comm/mailing/class/mailing.class.php
@@ -237,8 +237,8 @@ class Mailing extends CommonObject
global $conf, $langs;
// Check properties
- if ($this->body === 'InvalidHTMLString') {
- $this->error = 'InvalidHTMLString';
+ if ($this->body === 'InvalidHTMLStringCantBeCleaned') {
+ $this->error = 'InvalidHTMLStringCantBeCleaned';
return -1;
}
@@ -306,8 +306,8 @@ class Mailing extends CommonObject
public function update($user, $notrigger = 0)
{
// Check properties
- if ($this->body === 'InvalidHTMLString') {
- $this->error = 'InvalidHTMLString';
+ if ($this->body === 'InvalidHTMLStringCantBeCleaned') {
+ $this->error = 'InvalidHTMLStringCantBeCleaned';
return -1;
}
diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php
index 2b1031cef49..0b45ba263ff 100644
--- a/htdocs/core/class/html.form.class.php
+++ b/htdocs/core/class/html.form.class.php
@@ -247,7 +247,7 @@ class Form
$editaction = GETPOST('action', 'aZ09');
}
$editmode = ($editaction == 'edit' . $htmlname);
- if ($editmode) {
+ if ($editmode) { // edit mode
$ret .= "\n";
$ret .= '' . "\n";
- } else {
+ } else { // view mode
if (preg_match('/^email/', $typeofdata)) {
$ret .= dol_print_email($value, 0, 0, 0, 0, 1);
} elseif (preg_match('/^phone/', $typeofdata)) {
@@ -353,9 +352,9 @@ class Form
$tmp = explode(':', $typeofdata);
$ret .= '';
} elseif (preg_match('/^text/', $typeofdata) || preg_match('/^note/', $typeofdata)) {
- $ret .= dol_string_onlythesehtmltags(dol_htmlentitiesbr($value), 1, 1, 1);
+ $ret .= dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($value), 1, 1, 1));
} elseif (preg_match('/^(safehtmlstring|restricthtml)/', $typeofdata)) { // 'restricthtml' is not an allowed type for editfieldval. Value is 'safehtmlstring'
- $ret .= dol_string_onlythesehtmltags($value);
+ $ret .= dol_htmlwithnojs(dol_string_onlythesehtmltags($value));
} elseif ($typeofdata == 'day' || $typeofdata == 'datepicker') {
$ret .= '' . dol_print_date($value, 'day', $gm) . '';
} elseif ($typeofdata == 'dayhour' || $typeofdata == 'datehourpicker') {
diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php
index 0b90a5f757c..36053b4b1d5 100644
--- a/htdocs/core/lib/functions.lib.php
+++ b/htdocs/core/lib/functions.lib.php
@@ -7113,7 +7113,7 @@ function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto =
* @param int $allowlink Allow link tags.
* @return string String cleaned
*
- * @see dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_neverthesehtmltags()
+ * @see dol_htmlwithnojs() dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_neverthesehtmltags()
*/
function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0, $allowed_tags = array(), $allowlink = 0)
{
@@ -7175,7 +7175,7 @@ function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1,
/**
* Clean a string from some undesirable HTML tags.
* Note: Complementary to dol_string_onlythesehtmltags().
- * This method is used for example when option MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES is set to 1.
+ * This method is used for example by dol_htmlwithnojs() when option MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES is set to 1.
*
* @param string $stringtoclean String to clean
* @param array $allowed_attributes Array of tags not allowed
@@ -7227,7 +7227,7 @@ function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes
}
}
- $return = $dom->saveHTML();
+ $return = $dom->saveHTML(); // This may add a LF at end of lines, so we will trim later
//$return = 'aaaa
bb
ssdd
'."\n
aaa
aa
bb
";
$return = preg_replace('/^'.preg_quote('', '/').'/', '', $return);
@@ -7241,7 +7241,7 @@ function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes
/**
* Clean a string from some undesirable HTML tags.
- * Note. Not as secured as dol_string_onlythesehtmltags().
+ * Note: You should use instead dol_string_onlythesehtmltags() that is more secured if you can.
*
* @param string $stringtoclean String to clean
* @param array $disallowed_tags Array of tags not allowed
@@ -7354,7 +7354,7 @@ function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
* Sanitize a HTML to remove js and dangerous content
*
* @param string $stringtoencode String to encode
- * @param int $nouseofiframesandbox Allow use of option MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS for html sanitizing
+ * @param int $nouseofiframesandbox 0=Default, 1=Allow use of option MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS for html sanitizing (not yet working)
* @param string $check 'restricthtmlnolink' or 'restricthtml' or 'restricthtmlallowclass' or 'restricthtmlallowunvalid'
* @return string HTML sanitized
*/
@@ -7374,10 +7374,10 @@ function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = '
do {
$oldstringtoclean = $out;
- libxml_use_internal_errors(false); // Avoid to fill memory with xml errors
-
if (!empty($out) && !empty($conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML) && $check != 'restricthtmlallowunvalid') {
try {
+ libxml_use_internal_errors(false); // Avoid to fill memory with xml errors
+
$dom = new DOMDocument;
// Add a trick to solve pb with text without parent tag
// like '
Foo
bar
' that wrongly ends up, without the trick, with '
Foo
bar
'
@@ -7392,7 +7392,7 @@ function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = '
} catch (Exception $e) {
// If error, invalid HTML string with no way to clean it
//print $e->getMessage();
- $out = 'InvalidHTMLString';
+ $out = 'InvalidHTMLStringCantBeCleaned';
}
}
@@ -7416,9 +7416,8 @@ function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = '
// Keep only some html tags and remove also some 'javascript:' strings
$out = dol_string_onlythesehtmltags($out, 0, ($check == 'restricthtmlallowclass' ? 0 : 1), 1);
- // We should also exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
+ // Keep only some html attributes and exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
if (!empty($conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES)) {
- // Warning, the function may add a LF so we are forced to trim to compare with old $out without having always a difference and an infinit loop.
$out = dol_string_onlythesehtmlattributes($out);
}
@@ -7457,7 +7456,9 @@ function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = '
* - MultiCell -> param must not be encoded into HTML.
* Because writeHTMLCell convert also \n into , if function
* is used to build PDF, nl2brmode must be 1.
- * When we output string on pages, we use dol_string_onlythesehtmltags(dol_htmlentitiesbr()) for notes, and use dol_escape_htmltag() for simple labels.
+ * Note: When we output string on pages, we should use
+ * - dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr(), 1, 1, 1)) for notes,
+ * - dol_escape_htmltag() for simple labels.
*
* @param string $stringtoencode String to encode
* @param int $nl2brmode 0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php
index 756dda74347..72868df0e9f 100644
--- a/htdocs/main.inc.php
+++ b/htdocs/main.inc.php
@@ -168,27 +168,27 @@ function testSqlAndScriptInject($val, $type)
}
$inj += preg_match('/base\s+href/si', $val);
$inj += preg_match('/=data:/si', $val);
- // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp and https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers
- $inj += preg_match('/on(mouse|drag|key|load|touch|pointer|select|transition)([a-z]*)\s*=/i', $val); // onmousexxx can be set on img or any html tag like
- $inj += preg_match('/on(abort|afterprint|animation|auxclick|beforecopy|beforecut|beforeprint|beforeunload|blur|cancel|canplay|canplaythrough|change|click|close|contextmenu|cuechange|copy|cut)\s*=/i', $val);
- $inj += preg_match('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus|focusin|focusout|formdata|gotpointercapture|hashchange|input|invalid)\s*=/i', $val);
- $inj += preg_match('/on(lostpointercapture|offline|online|pagehide|pageshow)\s*=/i', $val);
- $inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|reset|resize|scroll|search|seeked|seeking|show|stalled|start|submit|suspend)\s*=/i', $val);
- $inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting|wheel)\s*=/i', $val);
+ // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp and https://developer.mozilla.org/en-US/docs/Web/Events
+ $inj += preg_match('/on(mouse|drag|key|load|touch|pointer|select|transition)[a-z]*\s*=/i', $val); // onmousexxx can be set on img or any html tag like
+ $inj += preg_match('/on(abort|after|animation|auxclick|before|blur|cancel|canplay|canplaythrough|change|click|close|contextmenu|cuechange|copy|cut)[a-z]*\s*=/i', $val);
+ $inj += preg_match('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus|focusin|focusout|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', $val);
+ $inj += preg_match('/on(lostpointercapture|offline|online|pagehide|pageshow)[a-z]*\s*=/i', $val);
+ $inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|reset|resize|scroll|search|seeked|seeking|show|stalled|start|submit|suspend)[a-z]*\s*=/i', $val);
+ $inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', $val);
// More not into the previous list
- $inj += preg_match('/on(repeat|begin|finish|beforeinput)\s*=/i', $val);
+ $inj += preg_match('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', $val);
// We refuse html into html because some hacks try to obfuscate evil strings by inserting HTML into HTML. Example: error=alert(1) to bypass test on onerror
$tmpval = preg_replace('/<[^<]+>/', '', $val);
- // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp and https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers
- $inj += preg_match('/on(mouse|drag|key|load|touch|pointer|select|transition)([a-z]*)\s*=/i', $tmpval); // onmousexxx can be set on img or any html tag like
- $inj += preg_match('/on(abort|afterprint|animation|auxclick|beforecopy|beforecut|beforeprint|beforeunload|blur|cancel|canplay|canplaythrough|change|click|close|contextmenu|cuechange|copy|cut)\s*=/i', $tmpval);
- $inj += preg_match('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus|focusin|focusout|formdata|gotpointercapture|hashchange|input|invalid)\s*=/i', $tmpval);
- $inj += preg_match('/on(lostpointercapture|offline|online|pagehide|pageshow)\s*=/i', $tmpval);
- $inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|reset|resize|scroll|search|seeked|seeking|show|stalled|start|submit|suspend)\s*=/i', $tmpval);
- $inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting|wheel)\s*=/i', $tmpval);
+ // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp and https://developer.mozilla.org/en-US/docs/Web/Events
+ $inj += preg_match('/on(mouse|drag|key|load|touch|pointer|select|transition)[a-z]*\s*=/i', $tmpval); // onmousexxx can be set on img or any html tag like
+ $inj += preg_match('/on(abort|after|animation|auxclick|before|blur|cancel|canplay|canplaythrough|change|click|close|contextmenu|cuechange|copy|cut)[a-z]*\s*=/i', $tmpval);
+ $inj += preg_match('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus|focusin|focusout|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', $tmpval);
+ $inj += preg_match('/on(lostpointercapture|offline|online|pagehide|pageshow)[a-z]*\s*=/i', $tmpval);
+ $inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|reset|resize|scroll|search|seeked|seeking|show|stalled|start|submit|suspend)[a-z]*\s*=/i', $tmpval);
+ $inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', $tmpval);
// More not into the previous list
- $inj += preg_match('/on(repeat|begin|finish|beforeinput)\s*=/i', $tmpval);
+ $inj += preg_match('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', $tmpval);
//$inj += preg_match('/on[A-Z][a-z]+\*=/', $val); // To lock event handlers onAbort(), ...
$inj += preg_match('/:|:|:/i', $val); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...'
diff --git a/htdocs/website/index.php b/htdocs/website/index.php
index 356255eef15..fba3f080d95 100644
--- a/htdocs/website/index.php
+++ b/htdocs/website/index.php
@@ -2237,11 +2237,11 @@ if ($usercanedit && (($action == 'updatesource' || $action == 'updatecontent' ||
$phpfullcodestringold = dolKeepOnlyPhpCode($objectpage->content);
- $objectpage->content = GETPOST('PAGE_CONTENT', 'none');
+ $objectpage->content = GETPOST('PAGE_CONTENT', 'none'); // any HTML content allowed
$phpfullcodestring = dolKeepOnlyPhpCode($objectpage->content);
- // Security analysis
+ // Security analysis (check PHP content and check permission website->writephp if php content is modified)
$error = checkPHPCode($phpfullcodestringold, $phpfullcodestring);
if ($error) {
diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php
index 148adb20808..545306f5734 100644
--- a/test/phpunit/SecurityTest.php
+++ b/test/phpunit/SecurityTest.php
@@ -341,6 +341,12 @@ class SecurityTest extends PHPUnit\Framework\TestCase
$result=testSqlAndScriptInject($test, 0);
$this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject lll');
+ $test='
test'; // Add the char %F6 into the variable
+ $result=testSqlAndScriptInject($test, 0);
+ //print "test=".$test." result=".$result."\n";
+ $this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject mmm');
+
+
$test="Text with ' encoded with the numeric html entity converted into text entity ' (like when submited by CKEditor)";
$result=testSqlAndScriptInject($test, 0); // result must be 0
$this->assertEquals(0, $result, 'Error on testSqlAndScriptInject mmm, result should be 0 and is not');
@@ -567,8 +573,8 @@ class SecurityTest extends PHPUnit\Framework\TestCase
$result=GETPOST("param15", 'restricthtml'); // param15 = src=>0xbeefed that is a dangerous string
print __METHOD__." result=".$result."\n";
- $this->assertEquals('InvalidHTMLString', $result, 'Test 15b'); // With some PHP and libxml version, we got this when parsong invalid HTML
- //$this->assertEquals(' src=>0xbeefed', $result, 'Test 15b'); // On other we got a HTML that has been cleaned
+ $this->assertEquals('InvalidHTMLStringCantBeCleaned', $result, 'Test 15b'); // With some PHP and libxml version, we got this result when parsing invalid HTML, but ...
+ //$this->assertEquals(' src=>0xbeefed', $result, 'Test 15b'); // ... on other PHP and libxml versions, we got a HTML that has been cleaned
unset($conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML);