Merge pull request #2812 from webmin/fix/remove-bundled-miniserv-cert

Remove bundled Miniserv certificate and safely fall back to HTTP
This commit is contained in:
Jamie Cameron
2026-08-10 11:31:30 -07:00
committed by GitHub
12 changed files with 148 additions and 101 deletions

View File

@@ -195,6 +195,7 @@ pam_restart=Restart
login_notsecure=Not Secure
login_notsecure_desc=This connection is not secure and could let a man-in-the-middle attack intercept your password or session cookie. Click here to switch to an HTTPS connection, unless you are on a trusted local network or behind a secure reverse proxy.
login_notsecure_http_desc=This connection is not encrypted and could let a man-in-the-middle attack intercept your password or session cookie. HTTPS is not enabled in Webmin; enable it unless you are on a trusted local network or behind a secure reverse proxy.
acl_root=Root directory for file chooser
acl_otherdirs=Other visible directories in file chooser

27
login-lib.pl Executable file
View File

@@ -0,0 +1,27 @@
# login-lib.pl
# Common functions for the built-in login pages.
# get_login_http_warning(&miniserv-config)
# Returns the insecure-login warning HTML, or undef if none is needed
sub get_login_http_warning
{
my ($miniserv) = @_;
return undef if ($ENV{'HTTPS'} eq 'ON' ||
(!$miniserv->{'ssl'} && $miniserv->{'no_ssl_warn'}));
my $warning = "⚠ $text{'login_notsecure'}";
my $description = $text{'login_notsecure_http_desc'};
if ($miniserv->{'ssl'}) {
$warning = ui_tag('a', $warning,
{ 'href' => "javascript:void(0);",
'class' => 'inherit-color',
'onclick' => "window.location.href = ".
"window.location.href.replace(/^http:/, 'https:'); return false;",
});
$description = $text{'login_notsecure_desc'};
}
return ui_tag('span', $warning,
{ class => 'not-secure', title => $description });
}
1;

View File

@@ -30,7 +30,7 @@ $vers || usage();
"miniserv.pl", "miniserv-lib.pl", "os_list.txt", "perlpath.pl",
"setup.sh", "setup.pl", "setup.bat",
"setup-repos.sh", "version", "web-lib.pl", "web-lib-funcs.pl",
"config_save.cgi", "chooser.cgi", "miniserv.pem",
"config_save.cgi", "chooser.cgi",
"config-aix", "update-from-repo.sh", "README.md",
"newmods.pl", "copyconfig.pl", "config-hpux", "config-freebsd",
"changepass.pl", "help.cgi", "user_chooser.cgi",
@@ -40,7 +40,7 @@ $vers || usage();
"webmin-systemd", "webmin-init", "webmin-daemon",
"config-openbsd",
"config-macos", "LICENCE",
"session_login.cgi", "acl_security.pl",
"session_login.cgi", "login-lib.pl", "acl_security.pl",
"defaultacl", "rpc.cgi", "date_chooser.cgi",
"safeacl", "install-module.pl", "LICENCE.ja",
"favicon.ico", "config-netbsd", "fastrpc.cgi",

View File

@@ -1,49 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDejCCAmKgAwIBAgIUI7oycX7XtLsNCJb1v2GGl1pZS28wDQYJKoZIhvcNAQEL
BQAwNzESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJVUzEUMBIGA1UEBwwL
U2FudGEgQ2xhcmEwHhcNMjIxMDEwMjEyNzI4WhcNMzIxMDA3MjEyNzI4WjA3MRIw
EAYDVQQDDAlsb2NhbGhvc3QxCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtTYW50YSBD
bGFyYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANSUOgVKjclwwmdL
OD3jSKHjuS9YqmfVqB+AyUfE97Lq9qvmydbSrpaEvXgmcG8Qlh6PwtIH9dSmCYl8
obftWC4ReN4ubl5meyEc0BRZmEPxC8j6s18S8ICTEQ7ZiNxoDwAciXA2Op6sAuS4
42HxcArHFpRXYMrgwGP4mes4j3L6ugWivvpz0zGDMNG+zWlMnlx9NJ4klSVvDueW
bsAd+KPuzF5S4QaTJ0oASu5UCK/JmHpDtXFGDwm72fWNdfspRYblSrmxFFiZUNoQ
t9iggMMaPlxIwajLTvrDi/Jzp0OXKdu5fRRsgPmzvgq/SWH9kDidZwpxw67BC1rm
g/EDtmMCAwEAAaN+MHwwHQYDVR0OBBYEFCjRwPscxpufLiIXn8nPb5S8ruIMMB8G
A1UdIwQYMBaAFCjRwPscxpufLiIXn8nPb5S8ruIMMA8GA1UdEwEB/wQFMAMBAf8w
FAYDVR0RBA0wC4IJbG9jYWxob3N0MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqG
SIb3DQEBCwUAA4IBAQAAMHPYjlF++zsrpVU44HqfwkQ6Y123eqGWw6HXDF9ga+oW
aAD6iHIf06rmpFQ/GnU5QzIAR0QkhCCcnpCFIX5Quluv5aQ1pxBtLuRW8QB7jugg
m/Bk204Ck4dj5EgJ5CGOj5yVjKanaMXa3hLp1dYMkas6VQyYBdMAJosrGWdFczvQ
/bpfgPWF0DZhzskTdTWce3rv6VHc6biDGUHNaCH7dtTJfenUZfgtNXMNl4raBQMC
83mEfJYhe1pqJRvzoC0dTeYeF/66Q5CfIxSpb2cMCtNl6wWqS4WJtQCOBCoKqQtH
9qDGxQCiISyMTqiTUU9GYYWsTZ9do8ZSc5VvO6uf
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDUlDoFSo3JcMJn
Szg940ih47kvWKpn1agfgMlHxPey6var5snW0q6WhL14JnBvEJYej8LSB/XUpgmJ
fKG37VguEXjeLm5eZnshHNAUWZhD8QvI+rNfEvCAkxEO2YjcaA8AHIlwNjqerALk
uONh8XAKxxaUV2DK4MBj+JnrOI9y+roFor76c9MxgzDRvs1pTJ5cfTSeJJUlbw7n
lm7AHfij7sxeUuEGkydKAEruVAivyZh6Q7VxRg8Ju9n1jXX7KUWG5Uq5sRRYmVDa
ELfYoIDDGj5cSMGoy076w4vyc6dDlynbuX0UbID5s74Kv0lh/ZA4nWcKccOuwQta
5oPxA7ZjAgMBAAECggEBAMeftmiXnVRIblafvV3onKFh/tnmUoeVjP6gauPZpJ3A
pgnBfVMdn1O7rU0yE61IjyB98f5X+VNK5HPWtOrKmF8Si0VhpsVBSWlL7F7fz+wl
ZOLEAkNKvsyOzpr6TtpjYYrCQZI9aojskP8GXIjyODv2v05oS33Y1vJKVwgboiHe
d3CbRn9nbarNdJ5FHrv6qbz9xiRREdsNeYTafzPNzKXnkLrJ9T0lsOItichwXk+e
e47fYi7cqmPc8mZ+cx3ct8z4RF/iGmdLc/nVd9k68/ola7DEtNVGGCw/1zAgAiJG
vKbVK/rWJ0qWKXjW2Vf0Uc7M4gmhU9tmq9wYXmYUKrkCgYEA/5f+VmpdDUAu75el
D8D7L6LvJDH4NoClpbHJ3srJ1gpGniL2mWR9xplRe7Sth9rWMziPo2vcpb0n8CKB
799sTLyY/F4/pjlnQrLGhs6ZipdCnx0Wp6cCcNTchJV7fsvJWQi96z69m9z4aOCY
ls5J0lgostgGmqJ0tUTLAJn4af0CgYEA1Oq6wx/2+NkaQSL3JhlBmybbaK/L7B5X
Bn2uwxaIBGKsWaH02VNx3kv7tbsEJ9bj2Zsf47CAJaw8ojNyT754YDBjnqawoI+G
RGP5Rjz7+IiW3EgACHQBhmxASjBTpCFcpszxjLwhL6i/0yyaosIq0459iz4dN9he
27nNjM48398CgYA8zQVdvTOhgVEpAaPsWXXnYRUOQSzQmk3NWruw2gClgBBIcfzD
hJo+8h3mFZbqKG6oBJ52u6PjcVncz/ik/TsgDgU+k5UEj8c2oJyFWQRBabYZb6wB
+cKk4J4MlBqqkQO2wFOdcHFecHRPTo494ZjCBuK3mJgJlPe6X0EDRZ1IaQKBgACF
Ei+nxFKXNRkznKbbKO3YCnEW/Mh2yn+ZjvOq+moIU6NkYdSl/4ErblHjQv9V5viB
CHLl22o8IWnD9mN7c4/IjnW0snmx4AIKvHEPdQ4GR1gCVP2wStCpMv77XzjnW/KM
TYqqaupS3yUE1tvO3YvmrSfJ3whj3tdqfLX7SurBAoGBAOW8Pn8/kIB0l5Hm5lKm
lh52bhoBzptiPfnddwXROl2IGieALZU/lKUvZv79aVJuoY86+qOfSZqiUypGiIPR
+VWa7deV7Stugf6KmnRzOp0ZdS/SCX9rppoJmdwORW58cfIhmdQV+vKlCL9ZO0HF
QqbKXdpb8BfqgsmIwTV+7zbF
-----END PRIVATE KEY-----

View File

@@ -3,6 +3,7 @@
BEGIN { push(@INC, "."); };
use WebminCore;
require './login-lib.pl';
$pragma_no_cache = 1;
#$ENV{'MINISERV_INTERNAL'} || die "Can only be called by miniserv.pl";
@@ -73,17 +74,7 @@ print "$text{'pam_prefix'}\n";
print &ui_form_start("@{[&get_webprefix()]}/pam_login.cgi", "post");
print &ui_hidden("cid", $in{'cid'});
my $not_secure;
if ($ENV{'HTTPS'} ne 'ON' && $miniserv{'ssl'}) {
my $link = ui_tag('a', "⚠ $text{'login_notsecure'}",
{ 'href' => "javascript:void(0);",
'class' => 'inherit-color',
'onclick' => "window.location.href = ".
"window.location.href.replace(/^http:/, 'https:'); return false;",
});
$not_secure = ui_tag('span', $link,
{ class => 'not-secure', title => $text{'login_notsecure_desc'} });
}
my $not_secure = &get_login_http_warning(\%miniserv);
print &ui_table_start($text{'pam_header'} . $not_secure,
"width=40% class='loginform'", 2);
@@ -153,4 +144,3 @@ EOF
}
&ui_print_footer();

View File

@@ -4,6 +4,7 @@
BEGIN { push(@INC, "."); };
use WebminCore;
require './login-lib.pl';
$pragma_no_cache = 1;
#$ENV{'MINISERV_INTERNAL'} || die "Can only be called by miniserv.pl";
@@ -93,17 +94,7 @@ print "$text{'session_prefix'}\n";
print &ui_form_start("@{[&get_webprefix()]}/session_login.cgi", "post");
print &ui_hidden("page", $in{'page'});
my $not_secure;
if ($ENV{'HTTPS'} ne 'ON' && $miniserv{'ssl'}) {
my $link = ui_tag('a', "⚠ $text{'login_notsecure'}",
{ 'href' => "javascript:void(0);",
'class' => 'inherit-color',
'onclick' => "window.location.href = ".
"window.location.href.replace(/^http:/, 'https:'); return false;",
});
$not_secure = ui_tag('span', $link,
{ class => 'not-secure', title => $text{'login_notsecure_desc'} });
}
my $not_secure = &get_login_http_warning(\%miniserv);
print &ui_table_start($text{'session_header'} . $not_secure,
"width=40% class='loginform'", 2);

View File

@@ -341,6 +341,7 @@ else {
# Ask the user if SSL should be used
if ($ENV{'ssl'} ne '') {
$ssl = $ENV{'ssl'};
$no_ssl_warn = 1 if (!$ssl);
}
else {
$ssl = 0;
@@ -351,6 +352,9 @@ else {
if ($sslyn =~ /^y/i) {
$ssl = 1;
}
else {
$no_ssl_warn = 1;
}
}
else {
print "The Perl SSLeay library is not installed. SSL not available.\n"
@@ -431,6 +435,7 @@ else {
if ($ENV{'allow'}) {
$miniserv{'allow'} = $ENV{'allow'};
}
$miniserv{'no_ssl_warn'} = 1 if ($no_ssl_warn);
if ($ENV{'session'} eq '') {
$miniserv{'session'} = $os_type eq 'windows' ? 0 : 1;
}
@@ -488,7 +493,8 @@ else {
chmod(0600, $ufile);
# Generate cert
if (system("openssl version >/dev/null 2>&1") == 0) {
$openssl_available = system("openssl version >/dev/null 2>&1") == 0;
if ($openssl_available) {
# We can generate a new SSL key for this host
$host = &get_system_hostname();
$cert = &tempname();
@@ -519,11 +525,27 @@ else {
}
unlink($cert, $key);
}
if (!-r $kfile) {
# Fall back to the built-in key
&copy_source_dest("$wadir/miniserv.pem", $kfile);
if (-r $kfile) {
chmod(0600, $kfile);
}
else {
delete($miniserv{'keyfile'});
if ($ssl) {
print "\n";
if ($openssl_available) {
print "ERROR: Failed to generate or install a unique TLS certificate for this host.\n";
}
else {
print "ERROR: OpenSSL is not available, so a unique TLS certificate could not be generated.\n";
}
print "WARNING: Webmin will be configured to use HTTP only.\n";
print "Login credentials and sessions will not be encrypted until SSL is enabled\n";
print "with a valid certificate. See https://webmin.com/docs/modules/webmin-configuration/#ssl-encryption for help.\n\n";
$ssl = 0;
$miniserv{'ssl'} = 0;
}
&put_miniserv_config(\%miniserv);
}
chmod(0600, $kfile);
print ".. done\n";
print "\n";

View File

@@ -458,6 +458,9 @@ else
fi
# Ask the user if SSL should be used
if [ "$ssl" = "0" ]; then
no_ssl_warn=1
fi
if [ "$ssl" = "" ]; then
ssl=0
$perl -e 'use Net::SSLeay' >/dev/null 2>/dev/null
@@ -466,6 +469,8 @@ else
read sslyn
if [ "$sslyn" = "y" -o "$sslyn" = "Y" ]; then
ssl=1
else
no_ssl_warn=1
fi
else
echo "The Perl SSLeay library is not installed. SSL not available."
@@ -528,6 +533,9 @@ else
echo "pidfile=$var_dir/miniserv.pid" >> $cfile
echo "logtime=168" >> $cfile
echo "ssl=$ssl" >> $cfile
if [ "$no_ssl_warn" = "1" ]; then
echo "no_ssl_warn=1" >> $cfile
fi
echo "no_ssl2=1" >> $cfile
echo "no_ssl3=1" >> $cfile
openssl version 2>&1 | grep "OpenSSL 1" >/dev/null
@@ -598,8 +606,10 @@ else
echo "userfile=$ufile" >> $cfile
kfile=$config_dir/miniserv.pem
openssl_available=0
openssl version >/dev/null 2>&1
if [ "$?" = "0" ]; then
openssl_available=1
# OpenSSL support `-addext` flag?
addtextsup="-addext subjectAltName=DNS:$host,DNS:localhost -addext extendedKeyUsage=serverAuth"
openssl version 2>&1 | grep "OpenSSL 1.0" >/dev/null
@@ -621,12 +631,28 @@ EOF
fi
rm -f $tempdir/cert $tempdir/key
fi
if [ ! -r $kfile ]; then
# Fall back to the built-in key
cp "$wadir/miniserv.pem" $kfile
if [ -r "$kfile" ]; then
chmod 600 "$kfile"
echo "keyfile=$config_dir/miniserv.pem" >> $cfile
elif [ "$ssl" = "1" ]; then
echo ""
if [ "$openssl_available" = "1" ]; then
echo "ERROR: Failed to generate or install a unique TLS certificate for this host."
else
echo "ERROR: OpenSSL is not available, so a unique TLS certificate could not be generated."
fi
echo "WARNING: Webmin will be configured to use HTTP only."
echo "Login credentials and sessions will not be encrypted until SSL is enabled"
echo "with a valid certificate. See https://webmin.com/docs/modules/webmin-configuration/#ssl-encryption for help."
echo ""
ssl=0
new_cfile=$tempdir/$$.miniserv.conf
if ! sed 's/^ssl=.*/ssl=0/' "$cfile" >"$new_cfile" ||
! mv "$new_cfile" "$cfile"; then
echo "ERROR: Failed to switch Webmin to HTTP-only mode."
exit 1
fi
fi
chmod 600 $kfile
echo "keyfile=$config_dir/miniserv.pem" >> $cfile
chmod 600 $cfile
echo ".. done"
@@ -980,9 +1006,11 @@ for m in $newmods; do
done
# Make miniserv config files non-world-readable
for f in miniserv.conf miniserv.pem miniserv.users; do
chown -R root $config_dir/$f
chgrp -R bin $config_dir/$f
chmod -R og-rw $config_dir/$f
if [ -e "$config_dir/$f" ]; then
chown -R root $config_dir/$f
chgrp -R bin $config_dir/$f
chmod -R og-rw $config_dir/$f
fi
done
chmod +r $config_dir/version
if [ "$nochown" = "" ]; then

View File

@@ -0,0 +1,42 @@
#!/usr/bin/perl
# Regression tests for detection of certificates bundled by older releases.
use strict;
use warnings;
use Test::More;
use File::Basename qw(dirname);
use File::Spec;
use File::Temp qw(tempdir);
my $script = File::Spec->rel2abs(
File::Spec->catfile(dirname(__FILE__), '..', 'web-lib-funcs.pl'));
require $script;
my $cert = File::Spec->catfile(tempdir(CLEANUP => 1), 'miniserv.pem');
open(my $fh, '>', $cert) or die "open($cert): $!";
print {$fh} "legacy certificate fixture\n";
close($fh) or die "close($cert): $!";
no warnings qw(redefine once);
my $digest = 'fcc4fc2ba3c00ede7008725668ff3af9';
local *main::execute_command = sub {
my (undef, undef, $output) = @_;
${$output} = "$digest $cert\n";
$? = 0;
};
local $ENV{'HTTPS'} = 'OFF';
ok(main::miniserv_using_default_cert($cert),
'a formerly bundled certificate remains detectable without a bundled file');
$digest = '0123456789abcdef0123456789abcdef';
ok(!main::miniserv_using_default_cert($cert),
'a machine-generated certificate is not flagged');
local $ENV{'MINISERV_KEYFILE'} = $cert;
local $ENV{'HTTPS'} = 'ON';
$digest = '2bb1926297df3d0429be3a4cd00b43ce';
ok(main::miniserv_using_default_cert(),
'HTTPS login detects the other legacy certificate');
done_testing();

View File

@@ -125,9 +125,7 @@ print &ui_tabs_end_tab();
# SSL key generation form
print &ui_tabs_start_tab("mode", "create");
print "$text{'ssl_newkey'}<p>\n";
my $curkey = &read_file_contents($miniserv{'keyfile'});
my $origkey = &read_file_contents("$root_directory/miniserv.pem");
if ($curkey eq $origkey) {
if (&miniserv_using_default_cert($miniserv{'keyfile'})) {
# System is using the original (insecure) Webmin key!
print "<b>$text{'ssl_hole'}</b><p>\n";
}

View File

@@ -14367,21 +14367,21 @@ if (!%current_theme_info || $nocache) {
return \%current_theme_info;
}
# miniserv_using_default_cert()
# Returns 1 if miniserv is using one of the hard-coded certs
# miniserv_using_default_cert([certificate-file])
# Returns 1 if miniserv is using one of the formerly bundled certificates
sub miniserv_using_default_cert
{
return 0 if ($ENV{'HTTPS'} ne 'ON');
my ($currentcertfile) = @_;
return 0 if (!$currentcertfile && $ENV{'HTTPS'} ne 'ON');
my $defaultcertname = 'miniserv.pem';
my $bundledcertfile = "$root_directory/$defaultcertname";
my $currentcertfile = $ENV{'MINISERV_KEYFILE'};
$currentcertfile ||= $ENV{'MINISERV_KEYFILE'};
if (!$currentcertfile) {
my %miniserv;
&get_miniserv_config(\%miniserv);
$currentcertfile = $miniserv{'keyfile'};
}
if ( $currentcertfile =~ /$defaultcertname$/ &&
-r $currentcertfile && -r $bundledcertfile) {
if ($currentcertfile && $currentcertfile =~ /\Q$defaultcertname\E$/ &&
-r $currentcertfile) {
my $out;
&execute_command("md5sum ".quotemeta($currentcertfile), undef, \$out);
return 0 if ($?);

View File

@@ -17,7 +17,6 @@ our $module_name;
our $strong_ssl_ciphers;
our $pfs_ssl_ciphers;
our $info;
our $root_directory;
our %config;
our $letsencrypt_cmd;
@@ -165,9 +164,7 @@ print ui_tabs_end_tab();
# SSL key generation form
print ui_tabs_start_tab("mode", "create");
print "$text{'ssl_newkey'}<p>\n";
my $curkey = read_file_contents($miniserv{'keyfile'});
my $origkey = read_file_contents("$root_directory/miniserv.pem");
if ($curkey eq $origkey) {
if (miniserv_using_default_cert($miniserv{'keyfile'})) {
# System is using the original (insecure) Webmin key!
print "<b>$text{'ssl_hole'}</b><p>\n";
}