CVE-2017-15644: Server-Side Request Forgery (SSRF) (CWE-918)
value: SSRF exists in Webmin 1.850 via the PATH_INFO to tunnel/link.cgi, as demonstrated by a GET request for tunnel/link.cgi/http://INTRANET-IP:8000.
CWE description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
CVSS v3 base score: 8.6
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("$!")); }