CVE-2017-15645: Cross-Site Request Forgery (CSRF) (CWE-352)
value: CSRF exists in Webmin 1.850. By sending a GET request to at/create_job.cgi containing dir=/&cmd= in the URI, an attacker to execute arbitrary commands.
CWE description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
CVSS v3 base score: 8.8
Published: 2017-10-19T22:29Z
Repository: https://github.com/webmin/webmin
Language: Perl
Files changed: web-lib-funcs.pl
Commit date: 2017-09-05 10:35:44 -0700
Commit message: Escape potentially malicious HTTP headers

Diff: diff --git a/web-lib-funcs.pl b/web-lib-funcs.pl
index db5858c26a..29c2fd5708 100755
--- a/web-lib-funcs.pl
+++ b/web-lib-funcs.pl
@@ -2306,11 +2306,11 @@ alarm($timeout || 60);
 my $h = &make_http_connection($host, $port, $ssl, "GET", $page, \@headers);
 alarm(0);
 $h = $main::download_timed_out if ($main::download_timed_out);
 if (!ref($h)) {
 	if ($error) { $$error = $h; return; }
-	else { &error($h); }
+	else { &error(&html_escape($h)); }
 	}
 &complete_http_download($h, $dest, $error, $cbfunc, $osdn, $host, $port,
 			$headers, $ssl, $nocache, $timeout);
 if ((!$error || !$$error) && !$nocache) {
 	&write_to_http_cache($url, $dest);
@@ -2335,11 +2335,11 @@ alarm($timeout || 60);
 ($line = &read_http_connection($h)) =~ tr/\r\n//d;
 if ($line !~ /^HTTP\/1\..\s+(200|30[0-9]|400)(\s+|$)/) {
 	alarm(0);
 	&close_http_connection($h);
 	if ($error) { ${$error} = $line; return; }
-	else { &error("Download failed : $line"); }
+	else { &error("Download failed : ".&html_escape($line)); }
 	}
 my $rcode = $1;
 &$cbfunc(1, $rcode >= 300 && $rcode < 400 ? 1 : 0)
 	if ($cbfunc);
 while(1) {
@@ -2381,11 +2381,12 @@ if ($rcode >= 300 && $rcode < 400) {
 		}
 	elsif ($header{'location'}) {
 		# Assume relative to same dir .. not handled
 		&close_http_connection($h);
 		if ($error) { ${$error} = "Invalid Location header $header{'location'}"; return; }
-		else { &error("Invalid Location header $header{'location'}"); }
+		else { &error("Invalid Location header ".
+			      &html_escape($header{'location'})); }
 		}
 	else {
 		&close_http_connection($h);
 		if ($error) { ${$error} = "Missing Location header"; return; }
 		else { &error("Missing Location header"); }
@@ -2410,11 +2411,11 @@ else {
 		# Write to a file
 		my $got = 0;
 		if (!&open_tempfile(PFILE, ">$destfile", 1)) {
 			&close_http_connection($h);
 			if ($error) { ${$error} = "Failed to write to $destfile : $!"; return; }
-			else { &error("Failed to write to $destfile : $!"); }
+			else { &error("Failed to write to ".&html_escape($destfile)." : ".&html_escape("$!")); }
 			}
 		binmode(PFILE);		# For windows
 		while(defined($buf = &read_http_connection($h, 1024))) {
 			&print_tempfile(PFILE, $buf);
 			$got += length($buf);

Vulnerable code: 	else { &error($h); }
	else { &error("Download failed : $line"); }
		else { &error("Invalid Location header $header{'location'}"); }
			else { &error("Failed to write to $destfile : $!"); }
Fixed code: 	else { &error(&html_escape($h)); }
	else { &error("Download failed : ".&html_escape($line)); }
		else { &error("Invalid Location header ".
			      &html_escape($header{'location'})); }
			else { &error("Failed to write to ".&html_escape($destfile)." : ".&html_escape("$!")); }