diff --git a/miniserv.pl b/miniserv.pl
index 08dbdf033..fd60f2ed3 100755
--- a/miniserv.pl
+++ b/miniserv.pl
@@ -1469,6 +1469,19 @@ if ($headerhost) {
$headerhost = undef if (!&check_ipaddress($headerhost) &&
!&check_ip6address($headerhost));
}
+# If trusted_proxies is configured, header-supplied client IP and SSL
+# client info are only honored when the direct TCP peer is in that list.
+# Otherwise drop them so an attacker reaching miniserv directly cannot
+# spoof X-Forwarded-For or X-SSL-Client-* to bypass auth.
+if ($config{'trust_real_ip'} && $config{'trusted_proxies'} ne '' &&
+ !&ip_match($acptip, $localip,
+ split(/\s+/, $config{'trusted_proxies'}))) {
+ print DEBUG "handle_request: peer $acptip not in trusted_proxies; ".
+ "ignoring forwarding and SSL client headers\n";
+ $headerhost = undef;
+ delete $header{'x-ssl-client-dn'};
+ delete $header{'x-ssl-client-verify'};
+ }
if ($config{'trust_real_ip'}) {
$acpthost = $headerhost || $acpthost;
if (&check_ipaddress($headerhost) || &check_ip6address($headerhost)) {
@@ -1723,7 +1736,7 @@ if ($header{'user-agent'} =~ /webmin/i ||
my $trust_ssl = $config{'trust_real_ip'} && !$config{'no_trust_ssl'};
if ($use_ssl && $verified_client ||
$trust_ssl && $header{'x-ssl-client-dn'} &&
- $header{'x-ssl-client-verify'} =~ /^success/i) {
+ $header{'x-ssl-client-verify'} =~ /^success$/i) {
if ($use_ssl && $verified_client) {
$peername = Net::SSLeay::X509_NAME_oneline(
Net::SSLeay::X509_get_subject_name(
@@ -1731,8 +1744,12 @@ if ($use_ssl && $verified_client ||
$ssl_con)));
$u = &find_user_by_cert($peername);
}
- if ($trust_ssl && !$u && $header{'x-ssl-client-dn'}) {
- # Use proxied client cert
+ if ($trust_ssl && !$u && $header{'x-ssl-client-dn'} &&
+ !($use_ssl && $verified_client)) {
+ # Use proxied client cert (only when this connection
+ # is not itself a verified mTLS client; otherwise the
+ # header could be set by a real-cert client that didn't
+ # match a user, to authenticate as someone else).
$u = &find_user_by_cert($header{'x-ssl-client-dn'});
}
if ($u) {
diff --git a/webmin/change_access.cgi b/webmin/change_access.cgi
index b9eac3f19..0ce50ea60 100755
--- a/webmin/change_access.cgi
+++ b/webmin/change_access.cgi
@@ -30,6 +30,15 @@ if (!$@ && $in{'libwrap'}) {
}
}
+@tprox = split(/\s+/, $in{'trusted_proxies'});
+foreach $h (@tprox) {
+ $err = &valid_allow($h);
+ &error($err) if ($err);
+ }
+if ($in{'trust'} == 2 && !@tprox) {
+ &error($text{'access_etproxies'});
+ }
+
&lock_file($ENV{'MINISERV_CONFIG'});
&get_miniserv_config(\%miniserv);
delete($miniserv{"allow"});
@@ -52,6 +61,7 @@ else {
$miniserv{'trust_real_ip'} = 0;
$miniserv{'no_trust_ssl'} = 1;
}
+$miniserv{'trusted_proxies'} = join(' ', @tprox);
&put_miniserv_config(\%miniserv);
&unlock_file($ENV{'MINISERV_CONFIG'});
&show_restart_page();
diff --git a/webmin/edit_access.cgi b/webmin/edit_access.cgi
index c81880cc8..1fe33471d 100755
--- a/webmin/edit_access.cgi
+++ b/webmin/edit_access.cgi
@@ -38,6 +38,10 @@ print &ui_table_row(&hlink($text{'access_trust_lvl'}, "access_trust_lvl"),
[ 1, $text{'access_trust_lvl1'} ],
[ 2, $text{'access_trust_lvl2'} ] ]));
+@tprox = split(/\s+/, $miniserv{'trusted_proxies'});
+print &ui_table_row(&hlink($text{'access_tproxies'}, "access_tproxies"),
+ &ui_textarea("trusted_proxies", join("\n", @tprox), 4, 30));
+
eval "use Authen::Libwrap qw(hosts_ctl STRING_UNKNOWN)";
if (!$@) {
print &ui_table_row($text{'access_libwrap'},
diff --git a/webmin/help/access_tproxies.html b/webmin/help/access_tproxies.html
new file mode 100644
index 000000000..afe6562cf
--- /dev/null
+++ b/webmin/help/access_tproxies.html
@@ -0,0 +1,25 @@
+
+When this list is set, headers received from any other peer are ignored, +preventing a client that can reach Webmin directly from spoofing its source +IP or impersonating a user via a fake SSL client certificate header. + +
+This field is required when the trust level is set to trust both the remote +IP and SSL certificate provided by proxies. It is strongly recommended +whenever any proxy header trust is enabled. + +
+Example: +
+10.0.0.5 +192.168.1.0/24 +2001:DB8::/32 ++ diff --git a/webmin/lang/en b/webmin/lang/en index 810635d16..9f29358d8 100644 --- a/webmin/lang/en +++ b/webmin/lang/en @@ -35,6 +35,8 @@ access_trust_lvl=Trust level for proxy headers access_trust_lvl0=No, do not trust any headers from the proxy access_trust_lvl1=Yes, trust the remote IP address provided by proxies access_trust_lvl2=Yes, trust both the remote IP and SSL cert provided by proxies +access_tproxies=Trusted proxy addresses +access_etproxies=At least one trusted proxy address must be entered when trusting the remote IP and SSL cert from proxies bind_title=Ports and Addresses bind_desc2=This form can be used to change the port number that Webmin listens on, or have it listen on only a single IP address on your system. You can also configure it to accept connections on multiple ports, or to listen on several IP addresses. Note - your web browser may prompt you to log in again after changing the port or binding address.