CVE-2017-2106: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Multiple cross-site scripting vulnerabilities in Webmin versions prior to 1.830 allows remote attackers to inject arbitrary web script or HTML via unspecified vectors.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2017-04-28T16:59Z
Repository: https://github.com/webmin/webmin
Language: Perl
Files changed: miniserv.pl
Commit date: 2016-10-21 15:46:23 -0700
Commit message: Strip out unsafe HTML from error messages

Diff: diff --git a/miniserv.pl b/miniserv.pl
index 9264e231d3..3456755379 100755
--- a/miniserv.pl
+++ b/miniserv.pl
@@ -1252,19 +1252,20 @@ $authuser = undef;
 $validated = undef;
 
 # check address against access list
 if (@deny && &ip_match($acptip, $localip, @deny) ||
     @allow && !&ip_match($acptip, $localip, @allow)) {
-	&http_error(403, "Access denied for $acptip");
+	&http_error(403, "Access denied for ".&html_strip($acptip));
 	return 0;
 	}
 
 if ($use_libwrap) {
 	# Check address with TCP-wrappers
 	if (!hosts_ctl($config{'pam'}, STRING_UNKNOWN,
 		       $acptip, STRING_UNKNOWN)) {
-		&http_error(403, "Access denied for $acptip by TCP wrappers");
+		&http_error(403, "Access denied for ".&html_strip($acptip).
+				 " by TCP wrappers");
 		return 0;
 		}
 	}
 print DEBUG "handle_request: passed IP checks\n";
 
@@ -1284,11 +1285,11 @@ if (!$sel) {
 		print DEBUG "handle_request: exiting due to timeout of $to\n";
 		exit;
 		}
 	else {
 		&http_error(400, "Timeout",
-			    "Waited for that $to seconds for start of headers");
+			    "Waited for $to seconds for start of headers");
 		}
 	}
 $checked_timeout++;
 print DEBUG "handle_request: passed timeout check\n";
 
@@ -1411,15 +1412,16 @@ while(1) {
 		}
 	elsif ($headline =~ /^\s+(.*)$/) {
 		$header{$lastheader} .= $headline;
 		}
 	else {
-		&http_error(400, "Bad Header $headline");
+		&http_error(400, "Bad Header ".&html_strip($headline));
 		}
 	if (&is_bad_header($header{$lastheader}, $lastheader)) {
 		delete($header{$lastheader});
-		&http_error(400, "Bad Header Contents $lastheader");
+		&http_error(400, "Bad Header Contents ".
+				 &html_strip($lastheader));
 		}
 	}
 
 # If a remote IP is given in a header (such as via a proxy), only use it
 # for logging unless trust_real_ip is set
@@ -1522,11 +1524,11 @@ if ($method eq 'POST' &&
 	print DEBUG "handle_request: posted_data=$posted_data\n";
 	}
 
 # Reject CONNECT request, which isn't supported
 if ($method eq "CONNECT" || $method eq "TRACE") {
-	&http_error(405, "Method $method is not supported");
+	&http_error(405, "Method ".&html_strip($method)." is not supported");
 	}
 
 # work out accepted encodings
 %acceptenc = map { $_, 1 } split(/,/, $header{'accept-encoding'});
 
@@ -1594,11 +1596,11 @@ foreach my $d (@davpaths) {
 		$davpath = $d;
 		last;
 		}
 	}
 if (!$davpath && ($method eq "SEARCH" || $method eq "PUT")) {
-	&http_error(400, "Bad Request method $method");
+	&http_error(400, "Bad Request method ".&html_strip($method));
 	}
 
 # Check for password if needed
 if ($config{'userfile'}) {
 	print DEBUG "handle_request: Need authentication\n";
@@ -2014,19 +2016,21 @@ if ($config{'userfile'}) {
 			if (@u && $< != $u[2]) {
 				$( = $u[3]; $) = "$u[3] $u[3]";
 				($>, $<) = ($u[2], $u[2]);
 				}
 			else {
-				&http_error(500, "Unix user $authuser does not exist");
+				&http_error(500, "Unix user ".
+				  &html_strip($authuser)." does not exist");
 				return 0;
 				}
 			}
 		}
 
 	# Check per-user IP access control
 	if (!&check_user_ip($baseauthuser)) {
-		&http_error(403, "Access denied for $acptip for $baseauthuser");
+		&http_error(403, "Access denied for $acptip for ".
+				 &html_strip($baseauthuser));
 		return 0;
 		}
 
 	# Check per-user allowed times
 	if (!&check_user_time($baseauthuser)) {
@@ -2187,11 +2191,11 @@ print DEBUG "handle_request: full=$full\n";
 @stfull = stat($full) if (!@stfull);
 
 # check filename against denyfile regexp
 local $denyfile = $config{'denyfile'};
 if ($denyfile && $full =~ /$denyfile/) {
-	&http_error(403, "Access denied to ".&html_escape($page));
+	&http_error(403, "Access denied to ".&html_strip($page));
 	return 0;
 	}
 
 # Reached the end of the path OK.. see what we've got
 if (-d _) {
@@ -2255,11 +2259,11 @@ if (-d _) {
 				$tm[3],$tm[4]+1,$tm[5]+1900,
 				$tm[0],$tm[1],$tm[2];
 		$len = length($df); $rest = " "x(35-$len);
 		&write_data(sprintf 
 		 "<a href=\"%s\">%-${len}.${len}s</a>$rest %-20.20s %-10.10s\n",
-		 &urlize($df), &html_escape($df), $fdate, $stbuf[7]);
+		 &urlize($df), &html_strip($df), $fdate, $stbuf[7]);
 		}
 	closedir(DIR);
 	&log_request($loghost, $authuser, $reqline, $ok_code, &byte_count());
 	return 0;
 	}
@@ -2652,11 +2656,11 @@ else {
 	&write_keep_alive(0);
 	&write_data("\r\n");
 	&reset_byte_count();
 	&write_data("<h1>Error - $_[1]</h1>\n");
 	if ($_[2]) {
-		&write_data("<pre>$_[2]</pre>\n");
+		&write_data("<p>$_[2]</p>\n");
 		}
 	}
 &log_request($loghost, $authuser, $reqline, $_[0], &byte_count())
 	if ($reqline);
 &log_error($_[1], $_[2] ? " : $_[2]" : "");
@@ -2974,11 +2978,11 @@ sub read_line
 local ($nowait, $nolimit) = @_;
 local($idx, $more, $rv);
 while(($idx = index($main::read_buffer, "\n")) < 0) {
 	if (length($main::read_buffer) > 100000 && !$nolimit) {
 		&http_error(414, "Request too long",
-		    "Received excessive line <pre>$main::read_buffer</pre>");
+		    "Received excessive line <pre>".&html_strip($main::read_buffer)."</pre>");
 		}
 
 	# need to read more..
 	&wait_for_data_error() if (!$nowait);
 	if ($use_ssl) {
@@ -5204,11 +5208,12 @@ if ($config{'dav_remoteuser'} && !$< && $validated) {
 		if ($config{'dav_root'} eq '*') {
 			$root = $u[7];
 			}
 		}
 	else {
-		&http_error(500, "Unix user $authuser does not exist");
+		&http_error(500, "Unix user ".&html_strip($authuser).
+				 " does not exist");
 		return 0;
 		}
 	}
 $root ||= $config{'dav_root'};
 $root ||= "/";
@@ -6051,10 +6056,17 @@ $tmp =~ s/\"/&quot;/g;
 $tmp =~ s/\'/&#39;/g;
 $tmp =~ s/=/&#61;/g;
 return $tmp;
 }
 
+sub html_strip
+{
+my ($tmp) = @_;
+$tmp =~ s/<[^>]*>//g;
+return $tmp;
+}
+
 # validate_twofactor(username, token)
 # Checks if a user's two-factor token is valid or not. Returns undef on success
 # or the error message on failure.
 sub validate_twofactor
 {

Vulnerable code: 	&http_error(403, "Access denied for $acptip");
		&http_error(403, "Access denied for $acptip by TCP wrappers");
			    "Waited for that $to seconds for start of headers");
		&http_error(400, "Bad Header $headline");
		&http_error(400, "Bad Header Contents $lastheader");
	&http_error(405, "Method $method is not supported");
	&http_error(400, "Bad Request method $method");
				&http_error(500, "Unix user $authuser does not exist");
		&http_error(403, "Access denied for $acptip for $baseauthuser");
	&http_error(403, "Access denied to ".&html_escape($page));
		 &urlize($df), &html_escape($df), $fdate, $stbuf[7]);
		&write_data("<pre>$_[2]</pre>\n");
		    "Received excessive line <pre>$main::read_buffer</pre>");
		&http_error(500, "Unix user $authuser does not exist");
Fixed code: 	&http_error(403, "Access denied for ".&html_strip($acptip));
		&http_error(403, "Access denied for ".&html_strip($acptip).
				 " by TCP wrappers");
			    "Waited for $to seconds for start of headers");
		&http_error(400, "Bad Header ".&html_strip($headline));
		&http_error(400, "Bad Header Contents ".
				 &html_strip($lastheader));
	&http_error(405, "Method ".&html_strip($method)." is not supported");
	&http_error(400, "Bad Request method ".&html_strip($method));
				&http_error(500, "Unix user ".
				  &html_strip($authuser)." does not exist");
		&http_error(403, "Access denied for $acptip for ".
				 &html_strip($baseauthuser));
	&http_error(403, "Access denied to ".&html_strip($page));
		 &urlize($df), &html_strip($df), $fdate, $stbuf[7]);
		&write_data("<p>$_[2]</p>\n");
		    "Received excessive line <pre>".&html_strip($main::read_buffer)."</pre>");
		&http_error(500, "Unix user ".&html_strip($authuser).
				 " does not exist");
sub html_strip
{
my ($tmp) = @_;
$tmp =~ s/<[^>]*>//g;
return $tmp;
}