diff --git a/htdocs/admin/system/security.php b/htdocs/admin/system/security.php index 0b2f2678521..c4140cfe416 100644 --- a/htdocs/admin/system/security.php +++ b/htdocs/admin/system/security.php @@ -251,7 +251,7 @@ print '
'; if (empty($conf->global->SECURITY_DISABLE_TEST_ON_OBFUSCATED_CONF)) { print '$dolibarr_main_db_pass: '; if (!empty($dolibarr_main_db_pass) && empty($dolibarr_main_db_encrypted_pass)) { - print img_picto('', 'warning').' '.$langs->trans("DatabasePasswordNotObfuscated").'   ('.$langs->trans("Recommanded").': '.$langs->trans("SetOptionTo", $langs->transnoentitiesnoconv("MainDbPasswordFileConfEncrypted"), yn(1)).')'; + print img_picto('', 'warning').' '.$langs->trans("DatabasePasswordNotObfuscated").'   ('.$langs->trans("Recommended").': '.$langs->trans("SetOptionTo", $langs->transnoentitiesnoconv("MainDbPasswordFileConfEncrypted"), yn(1)).')'; //print ' ('.$langs->trans("RecommendedValueIs", $langs->transnoentitiesnoconv("IPsOfUsers")).')'; } else { print img_picto('', 'tick').' '.$langs->trans("DatabasePasswordObfuscated"); @@ -267,49 +267,14 @@ if (empty($conf->global->SECURITY_DISABLE_TEST_ON_OBFUSCATED_CONF)) { print '
'; print '
'; print '
'; -print load_fiche_titre($langs->trans("Menu").' '.$langs->trans("SecuritySetup").' + '.$langs->trans("OtherSetup"), '', 'folder'); -//print ''.$langs->trans("PasswordEncryption").': '; -print 'MAIN_SECURITY_HASH_ALGO = '.(empty($conf->global->MAIN_SECURITY_HASH_ALGO) ? ''.$langs->trans("Undefined").'' : $conf->global->MAIN_SECURITY_HASH_ALGO)."   "; -if (empty($conf->global->MAIN_SECURITY_HASH_ALGO)) { - print '     If unset: \'md5\''; -} -if ($conf->global->MAIN_SECURITY_HASH_ALGO != 'password_hash') { - print '
MAIN_SECURITY_SALT = '.(empty($conf->global->MAIN_SECURITY_SALT) ? ''.$langs->trans("Undefined").'' : $conf->global->MAIN_SECURITY_SALT).'
'; -} else { - print '('.$langs->trans("Recommanded").': password_hash)'; - print '
'; -} -if ($conf->global->MAIN_SECURITY_HASH_ALGO != 'password_hash') { - print '
The recommanded value for MAIN_SECURITY_HASH_ALGO is now \'password_hash\' but setting it now will make ALL existing passwords of all users not valid, so update is not possible.
'; - print 'If you really want to switch, you must:
'; - print '- Go on home - setup - other and add constant MAIN_SECURITY_HASH_ALGO to value \'password_hash\'
'; - print '- In same session, WITHOUT LOGGING OUT, go into your admin user record and set a new password
'; - print '- You can now logout and login with this new password. You must now reset password of all other users.
'; - print '

'; -} +print load_fiche_titre($langs->trans("Menu").' '.$langs->trans("SecuritySetup"), '', 'folder'); + + +print ''.$langs->trans("UseCaptchaCode").': '; +print empty($conf->global->MAIN_SECURITY_ENABLECAPTCHA) ? '' : img_picto('', 'tick').' '; +print yn(empty($conf->global->MAIN_SECURITY_ENABLECAPTCHA) ? 0 : 1); print '
'; - - -print 'MAIN_SECURITY_ANTI_SSRF_SERVER_IP = '.(empty($conf->global->MAIN_SECURITY_ANTI_SSRF_SERVER_IP) ? ''.$langs->trans("Undefined").'   ('.$langs->trans("Example").': static-ips-of-server - '.$langs->trans("Note").': common loopback ip like 127.*.*.*, [::1] are already added)' : $conf->global->MAIN_SECURITY_ANTI_SSRF_SERVER_IP)."
"; -print '
'; - -print 'MAIN_ALLOW_SVG_FILES_AS_IMAGES = '.(empty($conf->global->MAIN_ALLOW_SVG_FILES_AS_IMAGES) ? '0   ('.$langs->trans("Recommanded").': 0)' : $conf->global->MAIN_ALLOW_SVG_FILES_AS_IMAGES)."
"; -print '
'; - -print 'MAIN_EXEC_USE_POPEN = '; -if (empty($conf->global->MAIN_EXEC_USE_POPEN)) { - print ''.$langs->trans("Undefined").''; -} else { - print $conf->global->MAIN_EXEC_USE_POPEN; -} -if ($execmethod == 1) { - print '   ("exec" PHP method will be used for shell commands)'; -} -if ($execmethod == 2) { - print '   ("popen" PHP method will be used for shell commands)'; -} -print "
"; print '
'; @@ -354,6 +319,62 @@ if (empty($out)) { } print '
'; +print '
'; +print '
'; +print '
'; + + +print load_fiche_titre($langs->trans("OtherSetup").' ('.$langs->trans("Experimental").')', '', 'folder'); + + +//print ''.$langs->trans("PasswordEncryption").': '; +print 'MAIN_SECURITY_HASH_ALGO = '.(empty($conf->global->MAIN_SECURITY_HASH_ALGO) ? ''.$langs->trans("Undefined").'' : $conf->global->MAIN_SECURITY_HASH_ALGO)."   "; +if (empty($conf->global->MAIN_SECURITY_HASH_ALGO)) { + print '     If unset: \'md5\''; +} +if ($conf->global->MAIN_SECURITY_HASH_ALGO != 'password_hash') { + print '
MAIN_SECURITY_SALT = '.(empty($conf->global->MAIN_SECURITY_SALT) ? ''.$langs->trans("Undefined").'' : $conf->global->MAIN_SECURITY_SALT).'
'; +} else { + print '('.$langs->trans("Recommanded").': password_hash)'; + print '
'; +} +if ($conf->global->MAIN_SECURITY_HASH_ALGO != 'password_hash') { + print '
The recommanded value for MAIN_SECURITY_HASH_ALGO is now \'password_hash\' but setting it now will make ALL existing passwords of all users not valid, so update is not possible.
'; + print 'If you really want to switch, you must:
'; + print '- Go on home - setup - other and add constant MAIN_SECURITY_HASH_ALGO to value \'password_hash\'
'; + print '- In same session, WITHOUT LOGGING OUT, go into your admin user record and set a new password
'; + print '- You can now logout and login with this new password. You must now reset password of all other users.
'; + print '

'; +} +print '
'; + +print 'MAIN_SECURITY_ANTI_SSRF_SERVER_IP = '.(empty($conf->global->MAIN_SECURITY_ANTI_SSRF_SERVER_IP) ? ''.$langs->trans("Undefined").'   ('.$langs->trans("Example").': static-ips-of-server - '.$langs->trans("Note").': common loopback ip like 127.*.*.*, [::1] are already added)' : $conf->global->MAIN_SECURITY_ANTI_SSRF_SERVER_IP)."
"; +print '
'; + +print 'MAIN_ALLOW_SVG_FILES_AS_IMAGES = '.(empty($conf->global->MAIN_ALLOW_SVG_FILES_AS_IMAGES) ? '0   ('.$langs->trans("Recommanded").': 0)' : $conf->global->MAIN_ALLOW_SVG_FILES_AS_IMAGES)."
"; +print '
'; + +print 'MAIN_RESTRICTHTML_ONLY_VALID_HTML = '.(empty($conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML) ? ''.$langs->trans("Undefined").'   ('.$langs->trans("Recommanded").': 1)' : '')."
"; +print '
'; + +print 'MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES = '.(empty($conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES) ? ''.$langs->trans("Undefined").'   ('.$langs->trans("Recommanded").': 1)' : '')."
"; +print '
'; + +print 'MAIN_EXEC_USE_POPEN = '; +if (empty($conf->global->MAIN_EXEC_USE_POPEN)) { + print ''.$langs->trans("Undefined").''; +} else { + print $conf->global->MAIN_EXEC_USE_POPEN; +} +if ($execmethod == 1) { + print '   ("exec" PHP method will be used for shell commands)'; +} +if ($execmethod == 2) { + print '   ("popen" PHP method will be used for shell commands)'; +} +print "
"; +print '
'; + // Modules/Applications @@ -405,7 +426,7 @@ if (empty($conf->api->enabled) && empty($conf->webservices->enabled)) { print '
'; } if (!empty($conf->api->enabled)) { - print 'API_ENDPOINT_RULES = '.(empty($conf->global->API_ENDPOINT_RULES) ? ''.$langs->trans("Undefined").'' : $conf->global->API_ENDPOINT_RULES)."
\n"; + print 'API_ENDPOINT_RULES = '.(empty($conf->global->API_ENDPOINT_RULES) ? ''.$langs->trans("Undefined").'   ('.$langs->trans("Example").': endpoint1:1,endpoint2:1,...)' : $conf->global->API_ENDPOINT_RULES)."
\n"; print '
'; } } diff --git a/htdocs/comm/mailing/card.php b/htdocs/comm/mailing/card.php index d801b4429c7..e03d13c29e8 100644 --- a/htdocs/comm/mailing/card.php +++ b/htdocs/comm/mailing/card.php @@ -577,7 +577,6 @@ if (empty($reshook)) { if (!$isupload) { $mesgs = array(); - $object->sujet = (string) GETPOST("sujet"); $object->body = (string) GETPOST("bodyemail", 'restricthtml'); $object->bgcolor = (string) GETPOST("bgcolor"); @@ -744,7 +743,7 @@ if ($action == 'create') { print '
'; // wysiwyg editor require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; - $doleditor = new DolEditor('bodyemail', GETPOST('bodyemail', 'restricthtml'), '', 600, 'dolibarr_mailings', '', true, true, $conf->global->FCKEDITOR_ENABLE_MAILING, 20, '90%'); + $doleditor = new DolEditor('bodyemail', GETPOST('bodyemail', 'restricthtmlallowunvalid'), '', 600, 'dolibarr_mailings', '', true, true, $conf->global->FCKEDITOR_ENABLE_MAILING, 20, '90%'); $doleditor->Create(); print '
'; diff --git a/htdocs/comm/mailing/class/mailing.class.php b/htdocs/comm/mailing/class/mailing.class.php index 04d2f7c17d8..d281fcb1a3a 100644 --- a/htdocs/comm/mailing/class/mailing.class.php +++ b/htdocs/comm/mailing/class/mailing.class.php @@ -208,6 +208,12 @@ class Mailing extends CommonObject { global $conf, $langs; + // Check properties + if ($this->body === 'InvalidHTMLString') { + $this->error = 'InvalidHTMLString'; + return -1; + } + $this->db->begin(); $this->title = trim($this->title); @@ -257,6 +263,12 @@ class Mailing extends CommonObject */ public function update($user) { + // Check properties + if ($this->body === 'InvalidHTMLString') { + $this->error = 'InvalidHTMLString'; + return -1; + } + $sql = "UPDATE ".MAIN_DB_PREFIX."mailing "; $sql .= " SET titre = '".$this->db->escape($this->title)."'"; $sql .= ", sujet = '".$this->db->escape($this->sujet)."'"; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 5dc77e6f6ae..a51fcda945c 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -775,9 +775,22 @@ function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = } break; case 'restricthtml': // Recommended for most html textarea + case 'restricthtmlallowunvalid': do { $oldstringtoclean = $out; + if (!empty($conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML) && $check != 'restricthtmlallowunvalid') { + try { + $dom = new DOMDocument; + $dom->loadHTML($out, LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL); + } catch (Exception $e) { + //print $e->getMessage(); + return 'InvalidHTMLString'; + } + $out = $dom->saveHTML(); + } + //var_dump($oldstringtoclean);var_dump($out); + // Ckeditor use the numeric entitic for apostrophe so we force it to text entity (all other special chars are correctly // encoded using text entities). This is a fix for CKeditor (CKeditor still encode in HTML4 instead of HTML5). $out = preg_replace('/'/i', ''', $out); @@ -794,7 +807,8 @@ function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = // We should also exclude non expected attributes if (!empty($conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES)) { - $out = dol_string_onlythesehtmlattributes($out); + // 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 = trim(dol_string_onlythesehtmlattributes($out)); } } while ($oldstringtoclean != $out); break; @@ -6311,7 +6325,7 @@ function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $stringtoclean = preg_replace('/:|�+58|:/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...' $stringtoclean = preg_replace('/javascript\s*:/i', '', $stringtoclean); - $temp = strip_tags($stringtoclean, $allowed_tags_string); + $temp = strip_tags($stringtoclean, $allowed_tags_string); // Warning: This remove also undesired changing string obfuscated with that pass injection detection into harmfull string if ($cleanalsosomestyles) { // Clean for remaining html tags $temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/i', '', $temp); // Note: If hacker try to introduce css comment into string to bypass this regex, the string must also be encoded by the dol_htmlentitiesbr during output so it become harmless @@ -6361,8 +6375,8 @@ function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes } $return = $dom->saveHTML(); - //$return = 'aaaa

bb

ssdd

'."\n

aaa

aa

bb

"; + $return = preg_replace('/^/', '', $return); $return = preg_replace('/<\/body><\/html>$/', '', $return); return $return; diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 53e67eba2cb..81f03a9aee6 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -2135,7 +2135,8 @@ IfCLINotRequiredYouShouldDisablePHPFunctions=Except if you need to run system co PHPFunctionsRequiredForCLI=For shell purpose (like scheduled job backup or running an anitivurs program), you must keep PHP functions NoWritableFilesFoundIntoRootDir=No writable files or directories of the common programs were found into your root directory (Good) RecommendedValueIs=Recommended: %s -NotRecommended=Not recommanded +Recommended=Recommended +NotRecommended=Not recommended ARestrictedPath=A restricted path CheckForModuleUpdate=Check for external modules updates CheckForModuleUpdateHelp=This action will connect to editors of external modules to check if a new version is available. diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php index 16cc8438c91..44b391148f5 100644 --- a/test/phpunit/SecurityTest.php +++ b/test/phpunit/SecurityTest.php @@ -340,6 +340,10 @@ class SecurityTest extends PHPUnit\Framework\TestCase $langs=$this->savlangs; $db=$this->savdb; + // Force default mode + $conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML = 0; + $conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES = 0; + $_COOKIE["id"]=111; $_GET["param1"]="222"; $_POST["param1"]="333"; @@ -363,6 +367,7 @@ class SecurityTest extends PHPUnit\Framework\TestCase $_POST["param13"]='n n > < " XSS'; $_POST["param13b"]='n n > < " XSS'; $_POST["param14"]="Text with ' encoded with the numeric html entity converted into text entity ' (like when submited by CKEditor)"; + $_POST["param15"]=" src=>0xbeefed"; //$_POST["param13"]='javascript%26colon%26%23x3B%3Balert(1)'; //$_POST["param14"]='javascripT&javascript#x3a alert(1)'; @@ -480,7 +485,7 @@ class SecurityTest extends PHPUnit\Framework\TestCase // Test with restricthtml we must remove html open/close tag and content but not htmlentities (we can decode html entities for ascii chars like n) $result=GETPOST("param6", 'restricthtml'); - print __METHOD__." result=".$result."\n"; + print __METHOD__." result param6=".$result."\n"; $this->assertEquals('">', $result); $result=GETPOST("param7", 'restricthtml'); @@ -503,6 +508,29 @@ class SecurityTest extends PHPUnit\Framework\TestCase print __METHOD__." result=".$result."\n"; $this->assertEquals("Text with ' encoded with the numeric html entity converted into text entity ' (like when submited by CKEditor)", $result, 'Test 14'); + $result=GETPOST("param15", 'restricthtml'); // src=>0xbeefed + print __METHOD__." result=".$result."\n"; + $this->assertEquals("0xbeefed", $result, 'Test 15a'); // The GETPOST return a harmull string + + // Test with restricthtml + MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES to test disabling of bad atrributes + $conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML = 1; + + $result=GETPOST("param15", 'restricthtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals('InvalidHTMLString', $result, 'Test 15b'); + + unset($conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML); + + // Test with restricthtml + MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES to test disabling of bad atrributes + $conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES = 1; + + $result=GETPOST("param15", 'restricthtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals('0xbeefed', $result, 'Test 15b'); + + unset($conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES); + + // Special test for GETPOST of backtopage, backtolist or backtourl parameter $_POST["backtopage"]='//www.google.com';