SECURITY: webmin accepts root with an empty password before first boot completes #14
Labels
No labels
accessibility
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Keel-Linux/handbook#14
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Filed in the private handbook deliberately.
Keel-Linux/common,inithooksand
trackerare public; this must not be described there until it is fixed.Between the moment an appliance boots and the moment
firstboot.d/30rootpasscompletes, Webmin accepts root with an empty password on port 12321.
The mechanism, each link verified
common/conf/turnkey.d/rootpassships, whenROOT_PASSis unset at buildtime, which is the normal case:
U6aMy0wojrahois a valid DES hash of the empty string, not a lockedaccount. Computed independently:
A locked account would carry
!or*. This one matches an emptysubmitted password by ordinary comparison, so
nullokbelow is a secondindependent weakening rather than the cause.
Verified on two live containers:
/etc/pam.d/webminisauth required pam_unix.so nullok;/etc/webmin/miniserv.confhaspasswd_mode=0and noallow=;/etc/webmin/miniserv.usersisroot:x:0; miniserv binds0.0.0.0:12321and[::]:12321;WEBMIN_FW_TCP_INCOMINGopens 12321;systemctl is-enabled webminisenabled;
miniserv.plcontains no empty-password guard.SSH is not affected:
PermitRootLogin yes, but OpenSSH defaultsPermitEmptyPasswords no.Why the existing mitigation does not close it
default/turnkey-init-fencelistsHTTPS_PORTS=(443 12321 12322), so thefence redirects 12321 while it runs. But
lib/init-fence.sh:49-53deliberately skips an address family whose nat table is unavailable, warns,
and carries on. On an LXC system container without nat that is the normal path,
and no 12321 rules were observed on the
forumcontainer.Three things compound it:
docs/traps.mdentry 1 is a first boot that hangs forever in one hook. Thisproject has hit it three times. While it hangs,
30rootpasshas not run.So the window is reachable from the internet for as long as a first boot takes,
and indefinitely if it hangs.
What is and is not established
Verified by reading and by inspecting two live containers: the hash, the PAM
stack, the miniserv configuration, the bind on both families, the fence port
list, and the nat-skip path.
Not demonstrated end to end. No login was attempted against a pre-init
appliance: both available containers already have root passwords set, and
building one would have taken the build lock. High confidence in the mechanism;
the exploit itself is unproven.
Neither
common#8norinithooks#14introduces this. It is inherited upstreambehaviour and is therefore likely to affect TurnKey appliances as well, which
makes it a coordinated-disclosure question and not only ours.
tracker#3defers upstream reporting to about the end of October; a security defect is not
what that deferral was for.
Exposure right now
Existing containers on the build host have root passwords set and are not
exploitable. The risk is any appliance built from a published Keel layer that
is booted on a reachable address, and any appliance whose first boot hung.
Remediation to decide between
!or*in thepassword field, so nothing can match. The reason the empty hash exists at all
needs establishing first, because inithooks may depend on being able to
authenticate before
30rootpass.nullokfrom/etc/pam.d/webminregardless. It is a second,independent weakening with no stated purpose here.
30rootpasshas completed, which is the only fixthat does not depend on the fence working.
allow=.lib/init-fence.shfail loudly rather than warn and continue when itcannot install rules for a family. A mitigation that silently declines is
worse than none, because it is counted on.
The fence's silent skip should be treated as its own defect whatever is decided
about the password.
Two corrections to the mechanism described above, both measured with
unix_chkpwd(the helperpam_unixactually executes) against a scratchshadow bind-mounted over
/etc/shadowunderunshare -r -m. Both make thedefect worse than first stated.
1. It was not only an empty password. It was any password.
With
nullokand a stored field ofU6aMy0wojraho, Linux-PAM's_unix_blankpasswdtestscrypt("", stored) == storedand returns successwithout consulting the submitted password at all. So the original
description, "accepts root with an empty password", understates it: in that
window Webmin accepted root with anything typed into the box.
2. Removing
nullokalone would not have closed it.The stored field is still what
crypt("")returns, so the ordinary comparisonmatches an empty submitted password even with
nullokgone. The two changes areeach necessary and neither is sufficient.
And the obvious fix would have broken first boot
/etc/default/inithooksshipsSUDOADMIN=false, sofirstboot.d/29sudoadminruns
turnkey-sudoadmin off --disable-setpasson every first boot, before30rootpass, and that path callspasswd --unlock root. Measured againstshadow in a scratch root:
passwd -u!*U6aMy0wojrahoturnkey-sudoadminruns underbash -e, so that exit 3 ends the hook beforepermitrootlogin_ssh,restart_sshdandset_status, and/etc/sudoadmin/stateis never written. Locking the account is thereforeexactly the fix that breaks first boot, which is
docs/traps.mdentry 1.The placeholder is now
*: it survives--unlock,crypt()returns it for noinput, and
bin/setpass.pyreplaces the field withchpasswdregardless.What the empty hash actually bought
Nothing in the first boot authenticated against it. sshd has
PermitRootLogin yesbut OpenSSH defaultsPermitEmptyPasswords no, and thefirst-boot dialog owns tty1 through
Conflicts=getty@tty1.service, so no loginprompt exists while it runs. Its only function was a console login on an image
whose first boot has not run, which is the state being closed.
Fixed in
Keel-Linux/common#12— the placeholder,nullokremoved, and Webmin helduntil the first boot marker exists. Also quoted
ROOT_PASS, which wasexpanded against the build directory, so a build password containing a glob
character was not the password that got set: a separate real defect in the
same script.
Keel-Linux/inithooks#18— the fence now fails closed rather thanloudly:
fence_close_portREJECTs in the filter table, which a containerdoes have, inserted at
-I INPUT 1so it precedes the ACCEPT ruleswebmin-fwinstalls for the same port.Not demonstrated end to end: no appliance was booted, so the final verdict
belongs to an appliance boot test.
conf/samba-rootpasscarries the sameliteral for the same reason and is deliberately out of scope; no Keel appliance
builds it.