CVE-2018-11652: Improper Neutralization of Formula Elements in a CSV File (CWE-1236)
value: CSV Injection vulnerability in Nikto 2.1.6 and earlier allows remote attackers to inject arbitrary OS commands via the Server field in an HTTP response header, which is directly injected into a CSV report.
CWE description: The product saves user-provided information into a Comma-Separated Value (CSV) file, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as a command when the file is opened by a spreadsheet product.
CVSS v3 base score: 9.8
Published: 2018-06-01T15:29Z
Repository: https://github.com/sullo/nikto
Language: Other
Files changed: program/plugins/nikto_outdated.plugin, program/plugins/nikto_report_csv.plugin
Commit date: 2018-05-31 23:30:03 -0400
Commit message: Fix CSV injection issue if server responds with a malicious Server string & CSV output is opened in Excel or other spreadsheet app. Potentially malicious cell start characters are now prefaced with a ' mark. Thanks to Adam (@bytesoverbombs) for letting me know!

Also fixed a crash in the outdated plugin if the $sepr field ends up being something that triggers a panic in split().

Diff: diff --git a/program/plugins/nikto_outdated.plugin b/program/plugins/nikto_outdated.plugin
index 219505c..08562c5 100644
--- a/program/plugins/nikto_outdated.plugin
+++ b/program/plugins/nikto_outdated.plugin
@@ -86,11 +86,11 @@ sub nikto_outdated {
         }
         else {
             $sepr = substr($sepr, (length($sepr) - 1), 1);
 
             # break up ID string on $sepr
-            my @T = split(/$sepr/, $mark->{'banner'});
+            my @T = split(/\\$sepr/, $mark->{'banner'});
 
             # assume last is version...
             for ($i = 0 ; $i < $#T ; $i++) { $MATCHSTRING .= "$T[$i] "; }
         }
         $MATCHSTRING =~ s/\s+$//;
diff --git a/program/plugins/nikto_report_csv.plugin b/program/plugins/nikto_report_csv.plugin
index ce65cfe..76bdb3f 100644
--- a/program/plugins/nikto_report_csv.plugin
+++ b/program/plugins/nikto_report_csv.plugin
@@ -51,49 +51,58 @@ sub csv_open {
 # start output
 sub csv_host_start {
     my ($handle, $mark) = @_;
     $mark->{'banner'} =~ s/"/\\"/g;
     my $hostname = $mark->{'vhost'} ? $mark->{'vhost'} : $mark->{'hostname'};
-    print $handle "\"$hostname\","
-      . "\"$mark->{'ip'}\","
-      . "\"$mark->{'port'}\"," . "\"\"," . "\"\"," . "\"\","
-      . "\"$mark->{'banner'}\"\n";
+    print $handle "\"" . csv_safecell($hostname) . "\","
+      . "\"" . csv_safecell($mark->{'ip'}) . "\","
+      . "\"" . csv_safecell($mark->{'port'}) . "\"," . "\"\"," . "\"\"," . "\"\","
+      #. "\"" . $mark->{'banner'} . "\"\n";
+      . "\"" . csv_safecell($mark->{'banner'}) . "\"\n";
     return;
 }
 
 ###############################################################################
 # print an item
 sub csv_item {
     my ($handle, $mark, $item) = @_;
     foreach my $uri (split(' ', $item->{'uri'})) {
         my $line = '';
     	my $hostname = $item->{'mark'}->{'vhost'} ? $item->{'mark'}->{'vhost'} : $item->{'mark'}->{'hostname'};
-        $line .= "\"$hostname\",";
-        $line .= "\"$item->{'mark'}->{'ip'}\",";
-        $line .= "\"$item->{'mark'}->{'port'}\",";
+        $line .= "\"" . csv_safecell($hostname) . "\",";
+        $line .= "\"" . csv_safecell($item->{'mark'}->{'ip'}) . \",";
+        $line .= "\"" . csv_safecell($item->{'mark'}->{'port'}) . "\",";
 
         $line .= "\"";
         if ($item->{'osvdb'} ne '') { $line .= "OSVDB-" . $item->{'osvdb'}; }
         $line .= "\",";
 
         $line .= "\"";
-        if ($item->{'method'} ne '') { $line .= $item->{'method'}; }
+        if ($item->{'method'} ne '') { $line .= csv_safecell($item->{'method'}); }
         $line .= "\",";
 
         $line .= "\"";
         if (($uri ne '') && ($mark->{'root'} ne '') && ($uri !~ /^$mark->{'root'}/))
-			{ $line .= $mark->{'root'} . $uri; }
-		else { $line .= $uri; }
+			{ $line .= csv_safecell($mark->{'root'}) . $uri; }
+		else { $line .= csv_safecell($uri); }
         $line .= "\",";
 	
-		my $msg = $item->{'message'};
-		$uri=quotemeta($uri);
-		my $root = quotemeta($mark->{'root'});
-		$msg =~ s/^$uri:\s//;
-		$msg =~ s/^$root$uri:\s//;
+	my $msg = $item->{'message'};
+	$uri=quotemeta($uri);
+	my $root = quotemeta($mark->{'root'});
+	$msg =~ s/^$uri:\s//;
+	$msg =~ s/^$root$uri:\s//;
         $msg =~ s/"/\\"/g;
-        $line .= "\"$msg\"";
+        $line .= "\"" . csv_safecell($msg) ."\"";
         print $handle "$line\n";
     }
 }
 
+###############################################################################
+# prevent CSV injection attacks
+sub csv_safecell {
+    my $celldata = $_[0] || return;
+    if ($celldata =~ /^[=+@-]/) { $celldata = "'" . $celldata; }
+    return $celldata;
+}
+
 1;

Vulnerable code:             my @T = split(/$sepr/, $mark->{'banner'});
    print $handle "\"$hostname\","
      . "\"$mark->{'ip'}\","
      . "\"$mark->{'port'}\"," . "\"\"," . "\"\"," . "\"\","
      . "\"$mark->{'banner'}\"\n";
        $line .= "\"$hostname\",";
        $line .= "\"$item->{'mark'}->{'ip'}\",";
        $line .= "\"$item->{'mark'}->{'port'}\",";
        if ($item->{'method'} ne '') { $line .= $item->{'method'}; }
			{ $line .= $mark->{'root'} . $uri; }
		else { $line .= $uri; }
		my $msg = $item->{'message'};
		$uri=quotemeta($uri);
		my $root = quotemeta($mark->{'root'});
		$msg =~ s/^$uri:\s//;
		$msg =~ s/^$root$uri:\s//;
        $line .= "\"$msg\"";
Fixed code:             my @T = split(/\\$sepr/, $mark->{'banner'});
    print $handle "\"" . csv_safecell($hostname) . "\","
      . "\"" . csv_safecell($mark->{'ip'}) . "\","
      . "\"" . csv_safecell($mark->{'port'}) . "\"," . "\"\"," . "\"\"," . "\"\","
      #. "\"" . $mark->{'banner'} . "\"\n";
      . "\"" . csv_safecell($mark->{'banner'}) . "\"\n";
        $line .= "\"" . csv_safecell($hostname) . "\",";
        $line .= "\"" . csv_safecell($item->{'mark'}->{'ip'}) . \",";
        $line .= "\"" . csv_safecell($item->{'mark'}->{'port'}) . "\",";
        if ($item->{'method'} ne '') { $line .= csv_safecell($item->{'method'}); }
			{ $line .= csv_safecell($mark->{'root'}) . $uri; }
		else { $line .= csv_safecell($uri); }
	my $msg = $item->{'message'};
	$uri=quotemeta($uri);
	my $root = quotemeta($mark->{'root'});
	$msg =~ s/^$uri:\s//;
	$msg =~ s/^$root$uri:\s//;
        $line .= "\"" . csv_safecell($msg) ."\"";
###############################################################################
# prevent CSV injection attacks
sub csv_safecell {
    my $celldata = $_[0] || return;
    if ($celldata =~ /^[=+@-]/) { $celldata = "'" . $celldata; }
    return $celldata;
}