CVE-2018-17553: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: An "Unrestricted Upload of File with Dangerous Type" issue with directory traversal in navigate_upload.php in Naviwebs Navigate CMS 2.8 allows authenticated attackers to achieve remote code execution via a POST request with engine=picnik and id=../../../navigate_info.php.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 8.8
Published: 2018-10-03T20:29Z
Repository: https://github.com/NavigateCMS/Navigate-CMS
Language: PHP
Files changed: lib/packages/files/files.php, navigate_upload.php
Commit date: 2018-09-22 09:00:50 +0200
Commit message: - files: removed unused code related to "picnik" upload

Diff: diff --git a/lib/packages/files/files.php b/lib/packages/files/files.php
index 4aff5b3d..f546c828 100644
--- a/lib/packages/files/files.php
+++ b/lib/packages/files/files.php
@@ -331,54 +331,10 @@ function run()
 			</html>	
 			';
 			
 			core_terminate();
 			break;
-		/*	
-		case 91: // picnik editing
-			ob_clean();
-			
-			// $strPicnikUrl is the URL that we use to launch Picnik.
-			$strPicnikUrl = "http://www.picnik.com/service";	
-			// $aPicnikParams collects together all the params we'll give Picnik.  Start with an API key
-			$aPicnikParams['_apikey'] = $website->picnik_api_key;
-			// tell Picnik where to send the exported image
-			$aPicnikParams['_export'] = NAVIGATE_URL.'/navigate_upload.php?wid='.$website->id.'&engine=picnik&id='.$_REQUEST['id'].'&engine=picnik&session_id='.session_id();
-			// give the export button a title
-			$aPicnikParams['_export_title'] = t(34, 'Save');
-			// turn on the close button, and tell it to come back here
-			//$aPicnikParams['_close_target'] = $strRoot;
-			// send in the previous "king" image in case the user feels like decorating it
-			$aPicnikParams['_import'] = NAVIGATE_DOWNLOAD.'?wid='.$website->id.'&id='.$_REQUEST['id'].'&disposition=attachment&sid='.session_id();	
-			// tell Picnik to redirect the user to the following URL after the HTTP POST instead of just redirecting to _export
-			$aPicnikParams['_redirect'] = NAVIGATE_DOWNLOAD.'?wid='.$website->id.'&id='.$_REQUEST['id'].'&disposition=inline&ts='.core_time(); //'javascript: return false;';
-		
-			// tell Picnik our name.  It'll use it in a few places as appropriate
-			$aPicnikParams['_host_name'] = 'Navigate';
-			// turn off the "Save &amp; Share" tab so users don't get confused
-			$aPicnikParams['_exclude'] = "out";
-		
-			echo '<html><head></head><body>';
-		
-			echo '<form id="picnik_form" method="POST" action="'.$strPicnikUrl.'" style=" visibility: hidden; ">';
-			
-			// put all the API parameters into the form as hidden inputs
-			foreach( $aPicnikParams as $key => $value ) {
-				echo "<input type='hidden' name='$key' value='$value'/>\n";
-			}
-			
-			//echo "<input type='text' name='address' value='Your Majesty'/>\n";
-			echo "<input type='submit' value='Picnik'/>\n";
-			echo "</form>";
-			echo '<script language="javascript" type="text/javascript">
-					document.forms[0].submit();
-				  </script>';
-			echo '</body></html>';
-
-			core_terminate();
-			break;
-		*/
 		
 		case 0: // list / search result
 		default:						
 			// show requested folder or search
 			$out = files_browser($_REQUEST['parent'], $_REQUEST['navigate-quicksearch']);
diff --git a/navigate_upload.php b/navigate_upload.php
index 0d65c653..c121fd33 100644
--- a/navigate_upload.php
+++ b/navigate_upload.php
@@ -81,15 +81,13 @@ header("Pragma: no-cache");
 
 // Settings
 $targetDir = NAVIGATE_PRIVATE.'/'.$website->id.'/files';
 $maxFileAge = 24 * 60 * 60; // Temp file age in seconds (1 day)
 
-// no maximum uploading / execution time
+// no maximum uploading/execution time
 @set_time_limit(0);
 
-//file_put_contents(NAVIGATE_PRIVATE.'/'.$website->id.'/files/out.txt', print_r($_FILES, true));
-
 // filedrop drag'n'drop engine	
 if($_REQUEST['engine']=='dropzone')
 {
 	if($user->permission("files.upload")=="true")
 	{
@@ -134,31 +132,10 @@ if($_REQUEST['engine']=='dropzone')
 			navigate_upload_remove_temporary($targetDir, $maxFileAge);
 			core_terminate();
 		}
 	}
 }
-else if($_REQUEST['engine']=='picnik')
-{
-	// PHP script to receive image data from Picnik via HTTP POST
-	// retrieve the image's attributes from the $_FILES array
-	$image_tmp_filename = $_FILES['file']['tmp_name'];
-	$image_filename = $_FILES['file']['name'];
-	
-	// Save the image to disk.  It'll go into the same directory as
-	// this script.  You'd probably want to put it somewhere else in the
-	// file system like a "/images" directory, or maybe even into a database.
-	// Make sure your web server has write access to the destination dir,
-	// or the call to file_put_contents isn't going to work.
-	$image_data = file_get_contents( $image_tmp_filename );
-	
-	if(!empty($_REQUEST['id']) && file_exists($targetDir.'/'.$_REQUEST['id']))
-	{	
-		file_put_contents( $targetDir.'/'.$_REQUEST['id'], $image_data );
-	}
-	
-	core_terminate();
-}
 else if($_REQUEST['engine']=='pixlr')
 {	
 	if(!empty($_REQUEST['id']) && file_exists($targetDir.'/'.$_REQUEST['id']))
 	{	
 		if(!empty($_REQUEST['image']))

Vulnerable code: 		/*	
		case 91: // picnik editing
			ob_clean();
			
			// $strPicnikUrl is the URL that we use to launch Picnik.
			$strPicnikUrl = "http://www.picnik.com/service";	
			// $aPicnikParams collects together all the params we'll give Picnik.  Start with an API key
			$aPicnikParams['_apikey'] = $website->picnik_api_key;
			// tell Picnik where to send the exported image
			$aPicnikParams['_export'] = NAVIGATE_URL.'/navigate_upload.php?wid='.$website->id.'&engine=picnik&id='.$_REQUEST['id'].'&engine=picnik&session_id='.session_id();
			// give the export button a title
			$aPicnikParams['_export_title'] = t(34, 'Save');
			// turn on the close button, and tell it to come back here
			//$aPicnikParams['_close_target'] = $strRoot;
			// send in the previous "king" image in case the user feels like decorating it
			$aPicnikParams['_import'] = NAVIGATE_DOWNLOAD.'?wid='.$website->id.'&id='.$_REQUEST['id'].'&disposition=attachment&sid='.session_id();	
			// tell Picnik to redirect the user to the following URL after the HTTP POST instead of just redirecting to _export
			$aPicnikParams['_redirect'] = NAVIGATE_DOWNLOAD.'?wid='.$website->id.'&id='.$_REQUEST['id'].'&disposition=inline&ts='.core_time(); //'javascript: return false;';
		
			// tell Picnik our name.  It'll use it in a few places as appropriate
			$aPicnikParams['_host_name'] = 'Navigate';
			// turn off the "Save &amp; Share" tab so users don't get confused
			$aPicnikParams['_exclude'] = "out";
		
			echo '<html><head></head><body>';
		
			echo '<form id="picnik_form" method="POST" action="'.$strPicnikUrl.'" style=" visibility: hidden; ">';
			
			// put all the API parameters into the form as hidden inputs
			foreach( $aPicnikParams as $key => $value ) {
				echo "<input type='hidden' name='$key' value='$value'/>\n";
			}
			
			//echo "<input type='text' name='address' value='Your Majesty'/>\n";
			echo "<input type='submit' value='Picnik'/>\n";
			echo "</form>";
			echo '<script language="javascript" type="text/javascript">
					document.forms[0].submit();
				  </script>';
			echo '</body></html>';

			core_terminate();
			break;
		*/
// no maximum uploading / execution time
//file_put_contents(NAVIGATE_PRIVATE.'/'.$website->id.'/files/out.txt', print_r($_FILES, true));

else if($_REQUEST['engine']=='picnik')
{
	// PHP script to receive image data from Picnik via HTTP POST
	// retrieve the image's attributes from the $_FILES array
	$image_tmp_filename = $_FILES['file']['tmp_name'];
	$image_filename = $_FILES['file']['name'];
	
	// Save the image to disk.  It'll go into the same directory as
	// this script.  You'd probably want to put it somewhere else in the
	// file system like a "/images" directory, or maybe even into a database.
	// Make sure your web server has write access to the destination dir,
	// or the call to file_put_contents isn't going to work.
	$image_data = file_get_contents( $image_tmp_filename );
	
	if(!empty($_REQUEST['id']) && file_exists($targetDir.'/'.$_REQUEST['id']))
	{	
		file_put_contents( $targetDir.'/'.$_REQUEST['id'], $image_data );
	}
	
	core_terminate();
}
Fixed code: // no maximum uploading/execution time