More secured getURLContent method. Add PHPUnit on getURLContent

This commit is contained in:
Laurent Destailleur 2020-10-27 18:02:05 +01:00
parent d750dc48a1
commit 80d13e711c
8 changed files with 75 additions and 13 deletions

View File

@ -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";

View File

@ -431,7 +431,6 @@ print '<br>';
print '<strong>';
print $form->textwithpicto($langs->trans("CompressionOfResources"), $langs->trans("CompressionOfResourcesDesc"));
print '</strong>: ';
//$tmp=getURLContent(DOL_URL_ROOT.'/index.php','GET');var_dump($tmp);
print '<br>';
// on PHP
print '<div id="httpcompphpok">'.img_picto('', 'tick.png').' '.$langs->trans("FilesOfTypeCompressed", 'php (.php)').'</div>';

View File

@ -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']);
}

View File

@ -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";

View File

@ -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);

View File

@ -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')
{

View File

@ -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);

View File

@ -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;
}
}