Skip to content

Commit 01aa901

Browse files
thomas-Ngreldy
andauthored
18.0 fix CVE 2024 40137 (#34762)
* Sec: Remove all functions that accept callable params - CVE-2024-40137 * FIX #34746 - More complete fix for CVE-2024-40137 --------- Co-authored-by: ldestailleur <eldy@destailleur.fr>
1 parent 96a74c4 commit 01aa901

File tree

5 files changed

+145
-21
lines changed

5 files changed

+145
-21
lines changed

htdocs/core/lib/functions.lib.php

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7251,8 +7251,23 @@ function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1,
72517251
*/
72527252
function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = array("allow", "allowfullscreen", "alt", "class", "contenteditable", "data-html", "frameborder", "height", "href", "id", "name", "src", "style", "target", "title", "width"))
72537253
{
7254+
if (is_null($allowed_attributes)) {
7255+
$allowed_attributes = array(
7256+
"allow", "allowfullscreen", "alt", "async", "class", "contenteditable", "crossorigin", "data-html", "frameborder", "height", "href", "id", "name", "property", "rel", "src", "style", "target", "title", "type", "width",
7257+
// HTML5
7258+
"header", "footer", "nav", "section", "menu", "menuitem"
7259+
);
7260+
}
7261+
// Always add content and http-equiv for meta tags, required to force encoding and keep html content in utf8 by load/saveHTML functions.
7262+
if (!in_array("content", $allowed_attributes)) {
7263+
$allowed_attributes[] = "content";
7264+
}
7265+
if (!in_array("http-equiv", $allowed_attributes)) {
7266+
$allowed_attributes[] = "http-equiv";
7267+
}
7268+
72547269
if (class_exists('DOMDocument') && !empty($stringtoclean)) {
7255-
$stringtoclean = '<?xml encoding="UTF-8"><html><body>'.$stringtoclean.'</body></html>';
7270+
$stringtoclean = '<?xml encoding="UTF-8"><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$stringtoclean.'</body></html>';
72567271

72577272
$dom = new DOMDocument(null, 'UTF-8');
72587273
$dom->loadHTML($stringtoclean, LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL);
@@ -7293,12 +7308,15 @@ function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes
72937308
}
72947309
}
72957310

7311+
$dom->encoding = 'UTF-8';
7312+
72967313
$return = $dom->saveHTML(); // This may add a LF at end of lines, so we will trim later
72977314
//$return = '<html><body>aaaa</p>bb<p>ssdd</p>'."\n<p>aaa</p>aa<p>bb</p>";
72987315

72997316
$return = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $return);
7300-
$return = preg_replace('/^'.preg_quote('<html><body>', '/').'/', '', $return);
7301-
$return = preg_replace('/'.preg_quote('</body></html>', '/').'$/', '', $return);
7317+
$return = preg_replace('/^'.preg_quote('<html><head><', '/').'[^<>]*'.preg_quote('></head><body>', '/').'/', '', $return);
7318+
$return = preg_replace('/'.preg_quote('</body></html>', '/').'$/', '', trim($return));
7319+
73027320
return trim($return);
73037321
} else {
73047322
return $stringtoclean;
@@ -7448,13 +7466,26 @@ function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = '
74487466
// Add a trick to solve pb with text without parent tag
74497467
// like '<h1>Foo</h1><p>bar</p>' that wrongly ends up, without the trick, with '<h1>Foo<p>bar</p></h1>'
74507468
// like 'abc' that wrongly ends up, without the trick, with '<p>abc</p>'
7451-
$out = '<div class="tricktoremove">'.$out.'</div>';
7452-
$dom->loadHTML($out, LIBXML_HTML_NODEFDTD|LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL);
7469+
7470+
if (dol_textishtml($out)) {
7471+
$out = '<?xml encoding="UTF-8"><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body><div class="tricktoremove">'.$out.'</div></body></html>';
7472+
} else {
7473+
$out = '<?xml encoding="UTF-8"><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body><div class="tricktoremove">'.dol_nl2br($out).'</div></body></html>';
7474+
}
7475+
7476+
$dom->loadHTML($out, LIBXML_HTML_NODEFDTD | LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOXMLDECL);
7477+
7478+
$dom->encoding = 'UTF-8';
7479+
74537480
$out = trim($dom->saveHTML());
74547481

7455-
// Remove the trick added to solve pb with text without parent tag
7456-
$out = preg_replace('/^<div class="tricktoremove">/', '', $out);
7457-
$out = preg_replace('/<\/div>$/', '', $out);
7482+
// Remove the trick added to solve pb with text in utf8 and text without parent tag
7483+
$out = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $out);
7484+
$out = preg_replace('/^'.preg_quote('<html><head><', '/').'[^<>]+'.preg_quote('></head><body><div class="tricktoremove">', '/').'/', '', $out);
7485+
$out = preg_replace('/'.preg_quote('</div></body></html>', '/').'$/', '', trim($out));
7486+
// $out = preg_replace('/^<\?xml encoding="UTF-8"><div class="tricktoremove">/', '', $out);
7487+
// $out = preg_replace('/<\/div>$/', '', $out);
7488+
// var_dump('rrrrrrrrrrrrrrrrrrrrrrrrrrrrr'.$out);
74587489
} catch (Exception $e) {
74597490
// If error, invalid HTML string with no way to clean it
74607491
//print $e->getMessage();
@@ -7492,7 +7523,7 @@ function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = '
74927523
} while ($oldstringtoclean != $out);
74937524

74947525
// Check the limit of external links that are automatically executed in a Rich text content. We count:
7495-
// '<img' to avoid <img src="http...">
7526+
// '<img' to avoid <img src="http...">, we can only accept "<img src="data:..."
74967527
// 'url(' to avoid inline style like background: url(http...
74977528
// '<link' to avoid <link href="http...">
74987529
$reg = array();
@@ -9228,7 +9259,7 @@ function verifCond($strToEvaluate)
92289259

92299260
/**
92309261
* Replace eval function to add more security.
9231-
* This function is called by verifCond() or trans() and transnoentitiesnoconv().
9262+
* This function is called by verifCond() for example.
92329263
*
92339264
* @param string $s String to evaluate
92349265
* @param int $returnvalue 0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
@@ -9319,14 +9350,32 @@ function dol_eval($s, $returnvalue = 0, $hideerrors = 1, $onlysimplestring = '1'
93199350
$forbiddenphpstrings = array('$$');
93209351
$forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_POST', '_REQUEST', 'ReflectionFunction'));
93219352

9322-
$forbiddenphpfunctions = array("exec", "passthru", "shell_exec", "system", "proc_open", "popen");
9323-
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "executeCLI", "verifCond")); // native dolibarr functions
9324-
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64_decode", "rawurldecode", "urldecode", "str_rot13", "hex2bin")); // decode string functions used to obfuscated function name
9325-
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "require", "include", "mkdir", "rmdir", "symlink", "touch", "unlink", "umask"));
9353+
// We list all forbidden function as keywords we don't want to see (we don't mind it if is "kewyord(" or just "keyword", we don't want "keyword" at all)
9354+
// We must exclude all functions that allow to execute another function. This includes all function that has a parameter with type "callable" to avoid things
9355+
// like we can do with array_map and its callable parameter: dol_eval('json_encode(array_map(implode("",["ex","ec"]), ["id"]))', 1, 1, '0')
9356+
$forbiddenphpfunctions = array();
9357+
// @phpcs:ignore
9358+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64"."_"."decode", "rawurl"."decode", "url"."decode", "str"."_rot13", "hex"."2bin")); // name of forbidden functions are split to avoid false positive
9359+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
93269360
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
9327-
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func"));
9361+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func", "call_user_func_array"));
9362+
9363+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("array_all", "array_any", "array_diff_ukey", "array_filter", "array_find", "array_find_key", "array_map", "array_reduce", "array_intersect_uassoc", "array_intersect_ukey", "array_walk", "array_walk_recursive"));
9364+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort", "preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
9365+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function"));
9366+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
9367+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
9368+
9369+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
9370+
93289371
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
9329-
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
9372+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
9373+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "executeCLI", "verifCond")); // native dolibarr functions
9374+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace", "mb_ereg_replace_callback")); // function with eval capabilities
9375+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
9376+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions
9377+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "mkdir", "rmdir", "symlink", "touch", "unlink", "umask"));
9378+
$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include"));
93309379

93319380
$forbiddenphpmethods = array('invoke', 'invokeArgs'); // Method of ReflectionFunction to execute a function
93329381

htdocs/install/upgrade.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838

3939
define('ALLOWED_IF_UPGRADE_UNLOCK_FOUND', 1);
4040
include_once 'inc.php';
41+
42+
/**
43+
* @var string $conffile
44+
*/
45+
4146
if (!file_exists($conffile)) {
4247
print 'Error: Dolibarr config file was not found. This may means that Dolibarr is not installed yet. Please call the page "/install/index.php" instead of "/install/upgrade.php").';
4348
}
@@ -46,6 +51,11 @@
4651

4752
global $langs;
4853

54+
/**
55+
* @var Conf $conf
56+
* @var Translate $langs
57+
*/
58+
4959
$grant_query = '';
5060
$step = 2;
5161
$ok = 0;

0 commit comments

Comments
 (0)