From 01075081cbcd9130a72115cdb50ee61fc394edc1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 25 Apr 2019 22:25:19 +0200 Subject: [PATCH 1/2] FIX CVE-2019-11200 --- htdocs/admin/tools/dolibarr_export.php | 2 +- htdocs/admin/tools/export.php | 3 +- htdocs/core/class/utils.class.php | 38 ++++++++++++++------------ 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/htdocs/admin/tools/dolibarr_export.php b/htdocs/admin/tools/dolibarr_export.php index 55be556afb9..231699dc636 100644 --- a/htdocs/admin/tools/dolibarr_export.php +++ b/htdocs/admin/tools/dolibarr_export.php @@ -50,7 +50,7 @@ if (! $user->admin) if ($action == 'delete') { - $file=$conf->admin->dir_output.'/'.GETPOST('urlfile'); + $file=$conf->admin->dir_output.'/backup/'.basename(GETPOST('urlfile', 'alpha')); $ret=dol_delete_file($file, 1); if ($ret) setEventMessages($langs->trans("FileWasRemoved", GETPOST('urlfile')), null, 'mesgs'); else setEventMessages($langs->trans("ErrorFailToDeleteFile", GETPOST('urlfile')), null, 'errors'); diff --git a/htdocs/admin/tools/export.php b/htdocs/admin/tools/export.php index 5bfacc6c4f0..5697e48e07f 100644 --- a/htdocs/admin/tools/export.php +++ b/htdocs/admin/tools/export.php @@ -133,7 +133,8 @@ if ($what == 'mysql') dol_syslog("Command are restricted to ".$dolibarr_main_restrict_os_commands.". We check that one of this command is inside ".$cmddump); foreach($arrayofallowedcommand as $allowedcommand) { - if (preg_match('/'.preg_quote($allowedcommand,'/').'/', $cmddump)) + $basenamecmddump=basename($cmddump); + if (preg_match('/^'.preg_quote($allowedcommand,'/').'$/', $basenamecmddump)) // the provided command $cmddump must be an allowed command { $ok=1; break; diff --git a/htdocs/core/class/utils.class.php b/htdocs/core/class/utils.class.php index 8c051c85b33..3d43a319e51 100644 --- a/htdocs/core/class/utils.class.php +++ b/htdocs/core/class/utils.class.php @@ -239,36 +239,37 @@ class Utils dol_mkdir($conf->admin->dir_output.'/backup'); // Parameteres execution - $command=$cmddump; - if (preg_match("/\s/",$command)) $command=escapeshellarg($command); // Use quotes on command + $command = $cmddump; + $command = preg_replace('/(\$|%)/', '', $command); // We removed chars that can be used to inject vars that contains space inside path of command without seeing there is a space to bypass the escapeshellarg. + if (preg_match("/\s/",$command)) $command=escapeshellarg($command); // If there is spaces, we add quotes on command to be sure $command is only a program and not a program+parameters //$param=escapeshellarg($dolibarr_main_db_name)." -h ".escapeshellarg($dolibarr_main_db_host)." -u ".escapeshellarg($dolibarr_main_db_user)." -p".escapeshellarg($dolibarr_main_db_pass); $param=$dolibarr_main_db_name." -h ".$dolibarr_main_db_host; $param.=" -u ".$dolibarr_main_db_user; if (! empty($dolibarr_main_db_port)) $param.=" -P ".$dolibarr_main_db_port; - if (! GETPOST("use_transaction")) $param.=" -l --single-transaction"; - if (GETPOST("disable_fk") || $usedefault) $param.=" -K"; - if (GETPOST("sql_compat") && GETPOST("sql_compat") != 'NONE') $param.=" --compatible=".escapeshellarg(GETPOST("sql_compat","alpha")); - if (GETPOST("drop_database")) $param.=" --add-drop-database"; - if (GETPOST("sql_structure") || $usedefault) + if (! GETPOST("use_transaction", "alpha")) $param.=" -l --single-transaction"; + if (GETPOST("disable_fk", "alpha") || $usedefault) $param.=" -K"; + if (GETPOST("sql_compat", "alpha") && GETPOST("sql_compat", "alpha") != 'NONE') $param.=" --compatible=".escapeshellarg(GETPOST("sql_compat", "alpha")); + if (GETPOST("drop_database", "alpha")) $param.=" --add-drop-database"; + if (GETPOST("sql_structure", "alpha") || $usedefault) { - if (GETPOST("drop") || $usedefault) $param.=" --add-drop-table=TRUE"; - else $param.=" --add-drop-table=FALSE"; + if (GETPOST("drop", "alpha") || $usedefault) $param.=" --add-drop-table=TRUE"; + else $param.=" --add-drop-table=FALSE"; } else { $param.=" -t"; } - if (GETPOST("disable-add-locks")) $param.=" --add-locks=FALSE"; - if (GETPOST("sql_data") || $usedefault) + if (GETPOST("disable-add-locks", "alpha")) $param.=" --add-locks=FALSE"; + if (GETPOST("sql_data", "alpha") || $usedefault) { $param.=" --tables"; - if (GETPOST("showcolumns") || $usedefault) $param.=" -c"; - if (GETPOST("extended_ins") || $usedefault) $param.=" -e"; + if (GETPOST("showcolumns", "alpha") || $usedefault) $param.=" -c"; + if (GETPOST("extended_ins", "alpha") || $usedefault) $param.=" -e"; else $param.=" --skip-extended-insert"; - if (GETPOST("delayed")) $param.=" --delayed-insert"; - if (GETPOST("sql_ignore")) $param.=" --insert-ignore"; - if (GETPOST("hexforbinary") || $usedefault) $param.=" --hex-blob"; + if (GETPOST("delayed", "alpha")) $param.=" --delayed-insert"; + if (GETPOST("sql_ignore", "alpha")) $param.=" --insert-ignore"; + if (GETPOST("hexforbinary", "alpha") || $usedefault) $param.=" --hex-blob"; } else { @@ -436,8 +437,9 @@ class Utils dol_mkdir($conf->admin->dir_output.'/backup'); // Parameteres execution - $command=$cmddump; - if (preg_match("/\s/",$command)) $command=escapeshellarg($command); // Use quotes on command + $command = $cmddump; + $command = preg_replace('/(\$|%)/', '', $command); // We removed chars that can be used to inject vars that contains space inside path of command without seeing there is a space to bypass the escapeshellarg. + if (preg_match("/\s/",$command)) $command=escapeshellarg($command); // If there is spaces, we add quotes on command to be sure $command is only a program and not a program+parameters //$param=escapeshellarg($dolibarr_main_db_name)." -h ".escapeshellarg($dolibarr_main_db_host)." -u ".escapeshellarg($dolibarr_main_db_user)." -p".escapeshellarg($dolibarr_main_db_pass); //$param="-F c"; From 63c0ab93fb21f86c1b736061af9fa1eee90148fd Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 25 Apr 2019 23:21:25 +0200 Subject: [PATCH 2/2] FIX CVE-2019-11201 --- htdocs/core/lib/website.lib.php | 43 +++++++++++++++++++++++++++++++-- htdocs/langs/en_US/website.lang | 3 ++- htdocs/website/index.php | 22 +++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/htdocs/core/lib/website.lib.php b/htdocs/core/lib/website.lib.php index 6f1057936d8..090a0f908dd 100644 --- a/htdocs/core/lib/website.lib.php +++ b/htdocs/core/lib/website.lib.php @@ -31,7 +31,7 @@ * @param string $content Content to replace * @param int $removephppart 0=Replace PHP sections with a PHP badge. 1=Remove completely PHP sections. * @return boolean True if OK - * @see dolWebsiteOutput for function used to replace content in a web server context + * @see dolWebsiteOutput() for function used to replace content in a web server context */ function dolWebsiteReplacementOfLinks($website, $content, $removephppart=0) { @@ -101,6 +101,7 @@ function dolWebsiteReplacementOfLinks($website, $content, $removephppart=0) * @param string $str String to clean * @param string $replacewith String to use as replacement * @return string Result string without php code + * @see dolKeepOnlyPhpCode() */ function dolStripPhpCode($str, $replacewith='') { @@ -133,6 +134,44 @@ function dolStripPhpCode($str, $replacewith='') return $newstr; } +/** + * Keep only PHP code part from a HTML string page. + * + * @param string $str String to clean + * @return string Result string with php code only + * @see dolStripPhpCode() + */ +function dolKeepOnlyPhpCode($str) +{ + $newstr = ''; + + //split on each opening tag + $parts = explode('', $part, 2); + if (!empty($partlings)) + { + $newstr .= $partlings[0].'?>'; + } + else + { + $newstr .= $part.'?>'; + } + } + } + return $newstr; +} /** * Render a string of an HTML content and output it. @@ -140,7 +179,7 @@ function dolStripPhpCode($str, $replacewith='') * * @param string $content Content string * @return void - * @see dolWebsiteReplacementOfLinks for function used to replace content in the backoffice context when USEDOLIBARREDITOR is not on + * @see dolWebsiteReplacementOfLinks() for function used to replace content in the backoffice context when USEDOLIBARREDITOR is not on */ function dolWebsiteOutput($content) { diff --git a/htdocs/langs/en_US/website.lang b/htdocs/langs/en_US/website.lang index 99f0133f175..4de381b0658 100644 --- a/htdocs/langs/en_US/website.lang +++ b/htdocs/langs/en_US/website.lang @@ -95,4 +95,5 @@ InternalURLOfPage=Internal URL of page ThisPageIsTranslationOf=This page/container is translation of ThisPageHasTranslationPages=This page/container has translation NoWebSiteCreateOneFirst=No website created yet. Create on first. -GoTo=Go to \ No newline at end of file +GoTo=Go to +DynamicPHPCodeContainsAForbiddenInstruction=You add dynamic PHP code that contains the PHP instruction '%s' that is forbidden by default as dynamic content (see hidden options WEBSITE_PHP_ALLOW_xxx to increase list of allowed commands). diff --git a/htdocs/website/index.php b/htdocs/website/index.php index 1ec9c2c65c4..592c3cd4234 100644 --- a/htdocs/website/index.php +++ b/htdocs/website/index.php @@ -1453,6 +1453,26 @@ if (($action == 'updatesource' || $action == 'updatecontent' || $action == 'conf $objectpage->content = GETPOST('PAGE_CONTENT','none'); + // Security analysis + $phpfullcodestring = dolKeepOnlyPhpCode($objectpage->content); + //print dol_escape_htmltag($phpfullcodestring);exit; + $forbiddenphpcommands=array("exec", "passthru", "system", "shell_exec", "proc_open"); + if (empty($conf->global->WEBSITE_PHP_ALLOW_WRITE)) // If option is not on, we disallow functions to write files + { + $forbiddenphpcommands=array_merge($forbiddenphpcommands, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "unlink", "mkdir", "rmdir", "symlink", "touch", "umask")); + } + foreach($forbiddenphpcommands as $forbiddenphpcommand) + { + if (preg_match('/'.$forbiddenphpcommand.'\s*\(/ms', $phpfullcodestring)) + { + $error++; + setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", $forbiddenphpcommand), null, 'errors'); + if ($action == 'updatesource') $action = 'editsource'; + if ($action == 'updatecontent') $action = 'editcontent'; + } + } + + // Clean data. We remove all the head section. $objectpage->content = preg_replace('/.*<\/head>/ims', '', $objectpage->content); /* $objectpage->content = preg_replace('//s', '', $objectpage->content); */ @@ -1463,6 +1483,8 @@ if (($action == 'updatesource' || $action == 'updatecontent' || $action == 'conf { $error++; setEventMessages($objectpage->error, $objectpage->errors, 'errors'); + if ($action == 'updatesource') $action = 'editsource'; + if ($action == 'updatecontent') $action = 'editcontent'; } if (! $error)