SSH Allows Direct Root Login
Checks whether the SSH daemon accepts logins as root with a password.
- Why It Matters
- Root is the one account name every attacker knows. Allowing it over SSH turns any password guess into full control of the server, and removes the audit trail of which person used root.
- How It Detects
- Reads the effective sshd configuration (sshd -T, or the config files as a fallback) and inspects PermitRootLogin. The value 'yes' fails. When the keyword is absent from the files (sshd -T always prints it), the installed OpenSSH version decides: 7.0 and later default to prohibit-password and pass with that noted; older versions defaulted to yes and fail; an unknown version is Incomplete, because the effective value needs sshd -T as root. 'prohibit-password', 'without-password', 'forced-commands-only' and 'no' pass. A risky value set inside a Match block in the config files fails as a conditional finding, with the Match condition in the evidence.
- How to Fix
- In /etc/ssh/sshd_config set: PermitRootLogin no (or prohibit-password if key-based root login is truly required). Log in as a named user and use sudo. Then run: sshd -t && systemctl reload sshd
- How to Verify
sshd -T | grep -i permitrootlogin should print 'permitrootlogin no'.- CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H- Control Category
- Identity and Authentication: CIS Controls v8 5, 6; NIST CSF 2.0 PR.AA
- Fix Effort
- Low
- CIS Controls v8 Safeguard
- 5.4
- NIST CSF 2.0 Subcategory
- PR.AA-05
- CWE
- CWE-250
- NIST SP 800-53 Rev. 5
- AC-6(2), IA-2
- Benchmark Topic
- SSH server configuration: root login (topic only, no benchmark text)
- References
- OpenBSD, sshd_config(5) manual page
NIST SP 800-123, Guide to General Server Security
Source Code
The complete file that defines this check. Run it with openvapt scan --only HOST-SSH-001, or read it offline with openvapt explain HOST-SSH-001.
internal/checks/host/ssh_root_login.go · Lines 1-67View on GitHub at This Commit
| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | |
| 6 | "github.com/openvapt/openvapt/internal/check" |
| 7 | "github.com/openvapt/openvapt/internal/checks/collect" |
| 8 | "github.com/openvapt/openvapt/internal/sys" |
| 9 | ) |
| 10 | |
| 11 | // rootLoginUnset decides an absent PermitRootLogin from the installed |
| 12 | // OpenSSH version: the default changed from yes to prohibit-password in |
| 13 | // OpenSSH 7.0 (2015), so a modern server with the keyword unset is not |
| 14 | // exposed, and reporting High there would be a false positive. |
| 15 | func rootLoginUnset(ctx context.Context, s sys.System, cfg map[string]string) check.Result { |
| 16 | major, minor, raw, ok := collect.OpenSSHVersion(ctx, s) |
| 17 | if !ok { |
| 18 | return check.IncompleteR("sshd", "PermitRootLogin is not set and the OpenSSH version is unknown; the effective value needs sshd -T (run as root)", "source: "+sourceOr(cfg[collect.SSHDSourceKey], "unknown")) |
| 19 | } |
| 20 | ver := "openssh-server " + raw |
| 21 | if major >= 7 { |
| 22 | return check.PassR("sshd", "PermitRootLogin is not set; OpenSSH 7.0 and later default to prohibit-password", "permitrootlogin (default) prohibit-password", ver) |
| 23 | } |
| 24 | _ = minor |
| 25 | return check.FailR("sshd", "PermitRootLogin is not set and this OpenSSH release defaults to yes", "permitrootlogin (default) yes", ver) |
| 26 | } |
| 27 | |
| 28 | func sourceOr(v, d string) string { |
| 29 | if v == "" { |
| 30 | return d |
| 31 | } |
| 32 | return v |
| 33 | } |
| 34 | |
| 35 | func init() { |
| 36 | check.Register(&check.Check{ |
| 37 | ID: "HOST-SSH-001", |
| 38 | Title: "SSH Allows Direct Root Login", |
| 39 | Domain: check.DomainHost, |
| 40 | Severity: check.High, |
| 41 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", |
| 42 | Summary: "Checks whether the SSH daemon accepts logins as root with a password.", |
| 43 | Rationale: "Root is the one account name every attacker knows. Allowing it over SSH turns any password guess into full control of the server, and removes the audit trail of which person used root.", |
| 44 | Detection: "Reads the effective sshd configuration (sshd -T, or the config files as a fallback) and inspects PermitRootLogin. The value 'yes' fails. When the keyword is absent from the files (sshd -T always prints it), the installed OpenSSH version decides: 7.0 and later default to prohibit-password and pass with that noted; older versions defaulted to yes and fail; an unknown version is Incomplete, because the effective value needs sshd -T as root. 'prohibit-password', 'without-password', 'forced-commands-only' and 'no' pass. A risky value set inside a Match block in the config files fails as a conditional finding, with the Match condition in the evidence.", |
| 45 | Fix: "In /etc/ssh/sshd_config set: PermitRootLogin no (or prohibit-password if key-based root login is truly required). Log in as a named user and use sudo. Then run: sshd -t && systemctl reload sshd", |
| 46 | Verify: "sshd -T | grep -i permitrootlogin should print 'permitrootlogin no'.", |
| 47 | Refs: []check.Ref{refSSHDManual, refNIST800123}, |
| 48 | Standards: check.Standards{CWE: []string{"CWE-250"}, NIST: []string{"AC-6(2)", "IA-2"}, CISTopic: "SSH server configuration: root login"}, |
| 49 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 50 | cfg, ok := collect.SSHD(ctx, s) |
| 51 | if !ok { |
| 52 | return []check.Result{check.SkipR("sshd", "SSH daemon configuration not found")} |
| 53 | } |
| 54 | v, set := cfg["permitrootlogin"] |
| 55 | if v == "yes" { |
| 56 | return []check.Result{check.FailR("sshd", "Root may log in over SSH with a password", "permitrootlogin "+v)} |
| 57 | } |
| 58 | if !set { |
| 59 | return []check.Result{rootLoginUnset(ctx, s, cfg)} |
| 60 | } |
| 61 | if r, bad := sshMatchFail(cfg, "Root may log in over SSH with a password", "permitrootlogin"); bad { |
| 62 | return []check.Result{r} |
| 63 | } |
| 64 | return []check.Result{check.PassR("sshd", "Direct root password login is disabled", "permitrootlogin "+v)} |
| 65 | }, |
| 66 | }) |
| 67 | } |
Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.
Cite This Page
If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.
OpenVAPT. (2026). HOST-SSH-001 SSH Allows Direct Root Login (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/HOST-SSH-001.html
In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
@misc{openvapt_host_ssh_001,
author = {{OpenVAPT}},
title = {{HOST-SSH-001 SSH Allows Direct Root Login}},
year = {2026},
howpublished = {\url{https://openvapt.com/checks/HOST-SSH-001.html}},
note = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}
Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.