diff --git a/htdocs/admin/system/filecheck.php b/htdocs/admin/system/filecheck.php index a3d54104a02..c8878b6b69f 100644 --- a/htdocs/admin/system/filecheck.php +++ b/htdocs/admin/system/filecheck.php @@ -147,7 +147,7 @@ if (GETPOST('target') == 'remote') $xmlarray = getURLContent($xmlremote); // Return array('content'=>response,'curl_error_no'=>errno,'curl_error_msg'=>errmsg...) - if (!$xmlarray['curl_error_no'] && $xmlarray['http_code'] != '404') + if (!$xmlarray['curl_error_no'] && $xmlarray['http_code'] != '400' && $xmlarray['http_code'] != '404') { $xmlfile = $xmlarray['content']; //print "xmlfilestart".$xmlfile."xmlfileend"; diff --git a/htdocs/admin/system/perf.php b/htdocs/admin/system/perf.php index 7857773924f..d9901818c92 100644 --- a/htdocs/admin/system/perf.php +++ b/htdocs/admin/system/perf.php @@ -431,7 +431,6 @@ print '
'; print ''; print $form->textwithpicto($langs->trans("CompressionOfResources"), $langs->trans("CompressionOfResourcesDesc")); print ': '; -//$tmp=getURLContent(DOL_URL_ROOT.'/index.php','GET');var_dump($tmp); print '
'; // on PHP print '
'.img_picto('', 'tick.png').' '.$langs->trans("FilesOfTypeCompressed", 'php (.php)').'
'; diff --git a/htdocs/admin/tools/update.php b/htdocs/admin/tools/update.php index 27b53e8266e..f39766818f3 100644 --- a/htdocs/admin/tools/update.php +++ b/htdocs/admin/tools/update.php @@ -54,7 +54,7 @@ $version = '0.0'; if ($action == 'getlastversion') { - $result = getURLContent('http://sourceforge.net/projects/dolibarr/rss'); + $result = getURLContent('https://sourceforge.net/projects/dolibarr/rss'); //var_dump($result['content']); $sfurl = simplexml_load_string($result['content']); } diff --git a/htdocs/api/class/api_setup.class.php b/htdocs/api/class/api_setup.class.php index 8177fbf7e1e..8e632fa0032 100644 --- a/htdocs/api/class/api_setup.class.php +++ b/htdocs/api/class/api_setup.class.php @@ -1634,7 +1634,7 @@ class Setup extends DolibarrApi $xmlarray = getURLContent($xmlremote); // Return array('content'=>response,'curl_error_no'=>errno,'curl_error_msg'=>errmsg...) - if (!$xmlarray['curl_error_no'] && $xmlarray['http_code'] != '404') + if (!$xmlarray['curl_error_no'] && $xmlarray['http_code'] != '400' && $xmlarray['http_code'] != '404') { $xmlfile = $xmlarray['content']; //print "xmlfilestart".$xmlfile."endxmlfile"; diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 908884ee059..9a4ac16e2f2 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -18,11 +18,13 @@ /** * \file htdocs/core/lib/geturl.lib.php - * \brief This file contains functions dedicated to get URL. + * \brief This file contains functions dedicated to get URLs. */ /** - * Function to get a content from an URL (use proxy if proxy defined) + * Function to get a content from an URL (use proxy if proxy defined). + * Support Dolibarr setup for timeout and proxy. + * Enhancement of CURL to add an anti SSRF protection. * * @param string $url URL to call. * @param string $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'DELETE' @@ -125,6 +127,7 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = $newUrl = $url; $maxRedirection = 5; $info = array(); + $response = ''; do { @@ -132,8 +135,43 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = curl_setopt($ch, CURLOPT_URL, $newUrl); - // TODO Test for $localurl on $newUrl - // Parse $newUrl, check server address, then set parsed value with CURLOPT_CONNECT_TO + // Parse $newUrl + $newUrlArray = parse_url($newUrl); + $hosttocheck = $newUrlArray['host']; + $hosttocheck = str_replace(array('[', ']'), '', $hosttocheck); // Remove brackets of IPv6 + + if (in_array($hosttocheck, array('localhost', 'localhost.domain'))) { + $iptocheck = '127.0.0.1'; + } else { + // TODO Resolve $iptocheck to get an IP and set CURLOPT_CONNECT_TO to use this ip + $iptocheck = $hosttocheck; + } + + if (!filter_var($iptocheck, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) { // This is not an IP + $iptocheck = 0; // + } + + if ($iptocheck) { + if ($localurl == 0) { // Only external url allowed (dangerous, may allow to get malware) + if (!filter_var($iptocheck, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $info['http_code'] = 400; + $info['content'] = 'Error bad hostname IP (private or reserved range). Must be an external URL.'; + break; + } + if (in_array($iptocheck, array('100.100.100.200'))) { + $info['http_code'] = 400; + $info['content'] = 'Error bad hostname IP (Used by Alibaba metadata). Must be an external URL.'; + break; + } + } + if ($localurl == 1) { // Only local url allowed (dangerous, may allow to get metadata on server or make internal port scanning) + if (filter_var($iptocheck, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $info['http_code'] = 400; + $info['content'] = 'Error bad hostname. Must be a local URL.'; + break; + } + } + } // Getting response from server $response = curl_exec($ch); diff --git a/htdocs/core/tpl/login.tpl.php b/htdocs/core/tpl/login.tpl.php index 07229bcfed8..0eb3aa94c24 100644 --- a/htdocs/core/tpl/login.tpl.php +++ b/htdocs/core/tpl/login.tpl.php @@ -297,9 +297,9 @@ if (!empty($_SESSION['dol_loginmesg'])) if (!empty($conf->global->MAIN_EASTER_EGG_COMMITSTRIP)) { include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php'; if (substr($langs->defaultlang, 0, 2) == 'fr') { - $resgetcommitstrip = getURLContent("http://www.commitstrip.com/fr/feed/"); + $resgetcommitstrip = getURLContent("https://www.commitstrip.com/fr/feed/"); } else { - $resgetcommitstrip = getURLContent("http://www.commitstrip.com/en/feed/"); + $resgetcommitstrip = getURLContent("https://www.commitstrip.com/en/feed/"); } if ($resgetcommitstrip && $resgetcommitstrip['http_code'] == '200') { diff --git a/htdocs/multicurrency/class/multicurrency.class.php b/htdocs/multicurrency/class/multicurrency.class.php index 2ce3277ae89..7a646386452 100644 --- a/htdocs/multicurrency/class/multicurrency.class.php +++ b/htdocs/multicurrency/class/multicurrency.class.php @@ -634,7 +634,7 @@ class MultiCurrency extends CommonObject dol_syslog("Call url endpoint ".$urlendpoint); - // TODO Use getURLContent() function instead. + // FIXME Use getURLContent() function instead. $ch = curl_init($urlendpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php index b4bdf1d89ab..bc3595a8cfa 100644 --- a/test/phpunit/SecurityTest.php +++ b/test/phpunit/SecurityTest.php @@ -395,7 +395,7 @@ class SecurityTest extends PHPUnit\Framework\TestCase global $conf; include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php'; - $url = 'ftp://aaaa'; + $url = 'ftp://mydomain.com'; $tmp = getURLContent($url); print __METHOD__." url=".$url."\n"; $this->assertGreaterThan(0, strpos($tmp['curl_error_msg'], 'not supported')); // Test error if return does not contains 'not supported' @@ -408,9 +408,34 @@ class SecurityTest extends PHPUnit\Framework\TestCase $url = 'https://www.dolibarr.fr'; // This is a redirect 301 page $tmp = getURLContent($url); // We DO follow print __METHOD__." url=".$url."\n"; - //var_dump($tmp); $this->assertEquals(200, $tmp['http_code'], 'GET url 301 with following -> 200'); // Test error if return does not contains 'not supported' + $url = 'http://localhost'; + $tmp = getURLContent($url, 'GET', '', 0, array(), array('http', 'https'), 0); // Only external URL + print __METHOD__." url=".$url."\n"; + $this->assertEquals(400, $tmp['http_code'], 'GET url to '.$url.' that resolves to a local URL'); // Test we receive an error because localtest.me is not an external URL + + $url = 'http://127.0.0.1'; + $tmp = getURLContent($url, 'GET', '', 0, array(), array('http', 'https'), 0); // Only external URL + print __METHOD__." url=".$url."\n"; + $this->assertEquals(400, $tmp['http_code'], 'GET url to '.$url.' that is a local URL'); // Test we receive an error because localtest.me is not an external URL + + $url = 'https://169.254.0.1'; + $tmp = getURLContent($url, 'GET', '', 0, array(), array('http', 'https'), 0); // Only external URL + print __METHOD__." url=".$url."\n"; + $this->assertEquals(400, $tmp['http_code'], 'GET url to '.$url.' that is a local URL'); // Test we receive an error because localtest.me is not an external URL + + $url = 'http://[::1]'; + $tmp = getURLContent($url, 'GET', '', 0, array(), array('http', 'https'), 0); // Only external URL + print __METHOD__." url=".$url."\n"; + $this->assertEquals(400, $tmp['http_code'], 'GET url to '.$url.' that is a local URL'); // Test we receive an error because localtest.me is not an external URL + + /*$url = 'localtest.me'; + $tmp = getURLContent($url, 'GET', '', 0, array(), array('http', 'https'), 0); // Only external URL + print __METHOD__." url=".$url."\n"; + $this->assertEquals(400, $tmp['http_code'], 'GET url to '.$url.' that resolves to a local URL'); // Test we receive an error because localtest.me is not an external URL + */ + return 0; } }