CVE-2017-1000501: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Awstats version 7.6 and earlier is vulnerable to a path traversal flaw in the handling of the "config" and "migrate" parameters resulting in unauthenticated remote code execution.
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: 9.8
Published: 2018-01-03T15:29Z
Repository: https://github.com/eldy/awstats
Language: Perl
Files changed: wwwroot/cgi-bin/awstats.pl
Commit date: 2017-12-17 12:55:48 +0100
Commit message: FIX Security reported by cPanel Security Team (can execute arbitraty
code)
Diff: diff --git a/wwwroot/cgi-bin/awstats.pl b/wwwroot/cgi-bin/awstats.pl
index 091d6823..fca4900f 100755
--- a/wwwroot/cgi-bin/awstats.pl
+++ b/wwwroot/cgi-bin/awstats.pl
@@ -1778,11 +1778,11 @@ sub Read_Config {
 			if ($Debug){debug("Opened config: $searchdir$SiteConfig", 2);}
 			last;
 		}else{if ($Debug){debug("Unable to open config file: $searchdir$SiteConfig", 2);}}
 	}
 	
-		#CL - Added to open config if full path is passed to awstats 
+	#CL - Added to open config if full path is passed to awstats 
 	if ( !$FileConfig ) {
 		
 		my $SiteConfigBis = File::Spec->rel2abs($SiteConfig);
 		debug("Finally, try to open an absolute path : $SiteConfigBis", 2);
 	
@@ -2203,11 +2203,14 @@ sub Parse_Config {
 			$MinHitExtra[$1] = $value;
 			next;
 		}
 
 		# Plugins
-		if ( $param =~ /^LoadPlugin/ ) { push @PluginsToLoad, $value; next; }
+		if ( $param =~ /^LoadPlugin/ ) {
+			$value =~ s/[^a-zA-Z0-9_\/\.\+:=\?\s%\-]//g;		# Sanitize plugin name and string param because it is used later in an eval.
+			push @PluginsToLoad, $value; next; 
+		}
 
 	  # Other parameter checks we need to put after MaxNbOfExtra and MinHitExtra
 		if ( $param =~ /^MaxNbOf(\w+)/ ) { $MaxNbOf{$1} = $value; next; }
 		if ( $param =~ /^MinHit(\w+)/ )  { $MinHit{$1}  = $value; next; }
 
@@ -3249,11 +3252,11 @@ sub Read_Plugins {
 "Plugin load for plugin '$pluginname' failed with return code: $loadret"
 							);
 						}
 						my $ret;    # To get init return
 						my $initfunction =
-						  "\$ret=Init_$pluginname('$pluginparam')";
+						  "\$ret=Init_$pluginname('$pluginparam')";		# Note that pluginname and pluginparam were sanitized when reading cong file entry 'LoadPlugin'
 						my $initret = eval("$initfunction");
 						if ( $initret && $initret eq 'xxx' ) {
 							$initret =
 'Error: The PluginHooksFunctions variable defined in plugin file does not contain list of hooked functions';
 						}
@@ -17138,11 +17141,14 @@ if ( $ENV{'GATEWAY_INTERFACE'} ) {    # Run from a browser as CGI
 	}
 
 	# No update but report by default when run from a browser
 	$UpdateStats = ( $QueryString =~ /update=1/i ? 1 : 0 );
 
-	if ( $QueryString =~ /config=([^&]+)/i ) { $SiteConfig = &Sanitize("$1"); }
+	if ( $QueryString =~ /config=([^&]+)/i ) { 
+		$SiteConfig = &Sanitize("$1");
+		$SiteConfig =~ s/\.\.//g; 		# Avoid directory transversal
+	}
 	if ( $QueryString =~ /diricons=([^&]+)/i ) { $DirIcons = "$1"; }
 	if ( $QueryString =~ /pluginmode=([^&]+)/i ) {
 		$PluginMode = &Sanitize( "$1", 1 );
 	}
 	if ( $QueryString =~ /configdir=([^&]+)/i ) {
@@ -17225,11 +17231,14 @@ else {                             # Run from command line
 	}
 
 	# Update with no report by default when run from command line
 	$UpdateStats = 1;
 
-	if ( $QueryString =~ /config=([^&]+)/i ) { $SiteConfig = &Sanitize("$1"); }
+	if ( $QueryString =~ /config=([^&]+)/i ) { 
+		$SiteConfig = &Sanitize("$1"); 
+		$SiteConfig =~ s/\.\.//g; 
+	}
 	if ( $QueryString =~ /diricons=([^&]+)/i ) { $DirIcons = "$1"; }
 	if ( $QueryString =~ /pluginmode=([^&]+)/i ) {
 		$PluginMode = &Sanitize( "$1", 1 );
 	}
 	if ( $QueryString =~ /configdir=([^&]+)/i ) {

Vulnerable code: 		#CL - Added to open config if full path is passed to awstats 
		if ( $param =~ /^LoadPlugin/ ) { push @PluginsToLoad, $value; next; }
						  "\$ret=Init_$pluginname('$pluginparam')";
	if ( $QueryString =~ /config=([^&]+)/i ) { $SiteConfig = &Sanitize("$1"); }
	if ( $QueryString =~ /config=([^&]+)/i ) { $SiteConfig = &Sanitize("$1"); }
Fixed code: 	#CL - Added to open config if full path is passed to awstats 
		if ( $param =~ /^LoadPlugin/ ) {
			$value =~ s/[^a-zA-Z0-9_\/\.\+:=\?\s%\-]//g;		# Sanitize plugin name and string param because it is used later in an eval.
			push @PluginsToLoad, $value; next; 
		}
						  "\$ret=Init_$pluginname('$pluginparam')";		# Note that pluginname and pluginparam were sanitized when reading cong file entry 'LoadPlugin'
	if ( $QueryString =~ /config=([^&]+)/i ) { 
		$SiteConfig = &Sanitize("$1");
		$SiteConfig =~ s/\.\.//g; 		# Avoid directory transversal
	}
	if ( $QueryString =~ /config=([^&]+)/i ) { 
		$SiteConfig = &Sanitize("$1"); 
		$SiteConfig =~ s/\.\.//g; 
	}