# OpenVAPT > OpenVAPT is a free, academic, non-commercial vulnerability assessment (VA) tool for Linux servers with source-available code, researched, developed and built entirely by AI Cybersecurity Engineers under human direction. AI Cybersecurity Engineers are AI systems (Claude by Anthropic, through Claude Code, and OpenAI Codex) acting in a cyber security engineering role. They are not people, not registered or professional engineers, and provide no professional engineering or security service. It runs read-only checks on the machine it is installed on (and only that machine) to identify weaknesses in the server, its network exposure and its websites, rates findings with CVSS v3.1 and proposes cited fixes. It is made for learning how a VAPT tool works. It is not a business, not a penetration testing service and collects no data. Key facts: - Price: free. No account, no licence key. - Use: anyone in any country, including companies, may use it on machines they own or control, for their own use. It may not be sold or used to provide services. - Risk: a free gift provided as is, with no warranty; users carry 100% of the risk. - Licence: OpenVAPT Source-Available Licence 1.3 (LicenseRef-OpenVAPT-SAL-1.3): own use only, no services, no selling. - Platform: Linux. Single static Go binary, standard library only. - Scope: designed to be loopback-only, enforced in code. Detection only, no exploit code. - Standards: NIST SP 800-115, CVSS v3.1, OWASP Top 10 2021, OWASP ASVS 4.0.3, CWE, NIST SP 800-53. - Source: https://github.com/OpenVAPT/openvapt - Researched, developed and built entirely by AI Cybersecurity Engineers (Claude by Anthropic via Claude Code, with OpenAI Codex) under the owner's direction; not audited by a human security expert or reviewed by a human lawyer. - Third-party names identify those projects only; their owners do not sponsor, endorse or have any connection with OpenVAPT. ## Docs - [FAQ](https://openvapt.com/faq.html) - [How It Is Built, With Code Citations](https://openvapt.com/how-it-works.html) - [Check Catalogue](https://openvapt.com/checks/) - [Standards and Methodology](https://openvapt.com/standards.html) - [Comparison With Lynis, OpenSCAP, OpenVAS, Nuclei, Trivy, ZAP](https://openvapt.com/compare.html) - [What It Does Not Do](https://openvapt.com/limits.html) - [Lessons and Lab](https://openvapt.com/learn.html) - [For Teachers: Course Plans, Worksheets, Rubric, Practice Labs](https://openvapt.com/teach.html) - [Teaching Terms](https://openvapt.com/legal/teaching.html) - [Download and Acknowledgements: Download Only, or Download and Scan in One Command](https://openvapt.com/download.html) - [Release Channels and Promotion Gates](https://openvapt.com/channels.html) - [No Data Collected](https://openvapt.com/no-data.html) - [EULA](https://openvapt.com/legal/eula.html) - [Disclaimer](https://openvapt.com/legal/disclaimer.html) - [Privacy Policy](https://openvapt.com/legal/privacy.html) - [Licence](https://openvapt.com/legal/licence.html) - [How to Cite OpenVAPT (APA, IEEE, Harvard, MLA, Chicago, Vancouver, BibTeX, RIS, CSL-JSON)](https://openvapt.com/cite.html) - [Citing OpenVAPT: Credit and Citation Guide](https://openvapt.com/legal/citing.html) - [Credits, Sources and Citations](https://openvapt.com/legal/credits.html) - [Every Source Cited, by Publisher](https://openvapt.com/legal/sources.html) - [Governance, Control and Assurance Framework](https://openvapt.com/legal/governance.html) - [OpenVAPT Academy: Free VAPT Course From Theory to Application](https://openvapt.com/academy/) - [VAPT Glossary](https://openvapt.com/glossary.html) - [Coverage Dashboard: Checks by Category, Severity and Framework](https://openvapt.com/coverage.html) - [Check Catalogue Dataset (JSON)](https://openvapt.com/checks/catalogue.json) - [AI Disclosure](https://openvapt.com/legal/ai-disclosure.html) - [AI Cybersecurity Engineer Policy](https://openvapt.com/legal/agents.html) - [Security Contact (RFC 9116 security.txt)](https://openvapt.com/.well-known/security.txt) - [For Machines and AI: Every Data File (sitemap, feed, Markdown twins, datasets)](https://openvapt.com/machine-readable.html) - [Releases Feed (Atom)](https://openvapt.com/feed.xml) - [Research Software Metadata (codemeta.json)](https://openvapt.com/codemeta.json) - [Full Text for Models: every check and FAQ (llms-full.txt)](https://openvapt.com/llms-full.txt) ## Markdown Versions Every check page, every Academy module, the FAQ and the glossary have a Markdown twin at the same address with .md instead of .html, for example https://openvapt.com/checks/HOST-SSH-001.md and https://openvapt.com/academy/f1.md. Each twin starts with front matter naming the canonical HTML page, the licence and the last change date. ## How to Cite Using OpenVAPT in research, teaching or a report? Please cite it. The author is the group author "OpenVAPT"; do not add personal names. OpenVAPT has no DOI yet. Cite the version and the web address; the version identifies the exact code. APA 7: OpenVAPT. (2026). OpenVAPT: An open academic vulnerability assessment tool (Version 0.1.0-alpha.6) [Computer software]. https://github.com/OpenVAPT/openvapt BibTeX: ```bibtex @software{openvapt_2026, author = {{OpenVAPT}}, title = {{OpenVAPT: An Open Academic Vulnerability Assessment Tool}}, year = {2026}, version = {0.1.0-alpha.6}, url = {https://github.com/OpenVAPT/openvapt}, note = {Built by AI Cybersecurity Engineers. https://openvapt.com}, license = {LicenseRef-OpenVAPT-SAL-1.3} } ``` Every style, and when credit is required (licence section 5.4): https://openvapt.com/cite.html. Command line: `openvapt cite --format bibtex`. ## FAQ ### What Is OpenVAPT? OpenVAPT is a free, academic, non-commercial vulnerability assessment (VA) learning tool for Linux servers, with source-available code on GitHub, researched, developed and built entirely by AI Cybersecurity Engineers under human direction. It runs read-only checks on the machine it is installed on to identify weaknesses in the server, its network exposure and its websites, rates each finding with CVSS v3.1, proposes a fix with cited sources, and writes a local report. It is made for learning how a vulnerability assessment tool works. ### Is OpenVAPT a Free VAPT Tool? Yes. OpenVAPT is free to download and use, with no paid tier, no account and no licence key. It covers the vulnerability assessment (VA) part of VAPT. It does not perform penetration testing (PT): it never exploits or attacks anything. ### Is OpenVAPT Open Source? Every line of OpenVAPT's code is public on GitHub at github.com/OpenVAPT/openvapt to read, study and review. It is licensed only under the OpenVAPT Source-Available Licence 1.3, which permits reading and studying the code and using it for your own use, but does not license selling it or providing any service with it, paid or unpaid. It is therefore source-available, not open source under the Open Source Initiative definition. ### Who Can Use OpenVAPT, and in Which Countries? Anyone, in any country. OpenVAPT is offered worldwide, free of charge, to individuals, students, teachers, schools, universities and organisations, for their own use on machines they own or control. There is no regional version and no country restriction in the software or the licence. Each user remains responsible for the sanctions and export control laws that apply to them, and must follow the laws of the country where they and the machine are, including computer misuse and data protection laws. The licence and End User Licence Agreement are governed by Malaysian law only because that is where the licensor is; this does not limit who may use OpenVAPT, and it does not remove protections that the mandatory law of a user's own country gives them. ### Can I Use OpenVAPT for My Company's Servers? Yes, for your own use. Individuals and organisations, including companies, may run OpenVAPT free of charge on computers and servers they own or control, to learn and to assess those same machines. OpenVAPT itself is an academic project, not a business, and it may not be used to provide services to anyone else, including on a client's server even with their permission. ### Can I Use OpenVAPT to Offer VAPT or Security Services to Clients? No. OpenVAPT may not be sold, included in a product, or used to provide vulnerability assessment, penetration testing, auditing, scanning or any other service to anyone else, paid or unpaid. It is for academic, learning and own use only. ### Can OpenVAPT Scan Other Servers or Websites? No. OpenVAPT only assesses the machine it runs on. Every network connection is restricted in code to loopback addresses (127.0.0.0/8 and ::1), and host names are not resolved, so it is designed so that it cannot be pointed at another server, network or website. Users must still have the right to run it on the machine where they run it. ### Is OpenVAPT a Penetration Test? No. OpenVAPT is an automated, non-intrusive vulnerability assessment following the Planning, Discovery and Reporting phases of the four-phase methodology in NIST SP 800-115 (Planning, Discovery, Attack, Reporting), without the Attack phase. It contains no exploit code. A report without findings does not prove a system is secure. ### What Does OpenVAPT Check? OpenVAPT runs 310 read-only checks: 168 for the server (OS support and patches, SSH including forwarding and authentication methods, accounts, sudo, file permissions, SUID, cron, kernel hardening, systemd service hardening, containers, logging, auditing, time sync), 50 for the network (listening services, database, cache, file-share, DNS and mail service configuration, exposed admin and application server ports, host and IPv6 firewalls, firewall rules compared with listening services, Docker firewall bypass, TLS versions, ciphers, keys and certificates) and 92 for web apps (security headers, cookies, HTTPS redirect, exposed .git, .env and backups, phpinfo, directory listing, status pages, PHP settings, Apache Tomcat, Node.js and Python (Django and Flask) settings, and WordPress, Joomla and Drupal hardening). ### Which Standards Does OpenVAPT Follow? OpenVAPT follows NIST SP 800-115 for methodology, rates severity with CVSS v3.1, and maps checks to the OWASP Top 10 (2021), OWASP ASVS 4.0.3, CWE and NIST SP 800-53 Rev. 5. Reports are available in HTML, JSON, SARIF 2.1.0 and Markdown. ### Does OpenVAPT Collect Any Data? No. The OpenVAPT tool is designed to make no connection to any other machine and has no telemetry. The openvapt.com website sets no cookies, has no analytics, forms or accounts, and its server logs are turned off. Reports stay on the user's own machine. ### Is OpenVAPT Safe to Run on a Production Server? OpenVAPT is designed to be read-only: it reads files, runs a short allow-list of read-only commands and sends GET requests to local websites through 127.0.0.1, and it is designed not to change, install or restart anything. It may still contain mistakes and uses server resources, so it is provided as is and used 100% at your own risk; test it on a non-production machine first and preview every action with openvapt scan --dry-run. ### How Is OpenVAPT Different from Lynis, OpenSCAP, OpenVAS, Nuclei or OWASP ZAP? OpenVAPT is primarily a teaching project: every check is documented and cited to its exact source code, and it combines server, network-exposure and local website checks in one read-only binary that can only assess its own machine. Mature tools such as Lynis and OpenSCAP (host hardening), OpenVAS (network vulnerability scanning), Nuclei (template-based scanning) and OWASP ZAP (web application testing) are broader and better suited to professional work. OpenVAPT is not affiliated with, sponsored or endorsed by any of these projects or their owners; their names are used only to identify them. ### Who Made OpenVAPT? Was It Built by AI Cybersecurity Engineers? Yes. All of OpenVAPT, including its code, tests, documentation, website, course and legal drafts, was researched, developed and built by AI Cybersecurity Engineers: Claude (Anthropic) through Claude Code produced the work, and OpenAI Codex reviewed the code, proposed and implemented fixes, and built parts of the code to Claude's specification (network, data-service and TLS checks, the report redesign and the vulnerability matching engine), each reviewed by Claude before merging. AI Cybersecurity Engineers are AI systems (Claude by Anthropic, through Claude Code, and OpenAI Codex) acting in a cyber security engineering role. They are not people, not registered or professional engineers, and provide no professional engineering or security service. OpenVAPT's human project owner directs the work, makes every decision and approves what is published, but did not hand-write the code. It has not been audited by an independent human security expert, and the legal documents were reviewed by an AI legal reviewer, not by a human lawyer. The full history is public on GitHub. ### Can I Learn VAPT with OpenVAPT? Yes. The OpenVAPT Academy is a free course of 36 modules that goes from security basics, VAPT methodology, CVE, CWE and CVSS, through Linux, SSH, network, TLS, web security, systemd service hardening, firewall policy, application servers (Tomcat, Node.js, Python), CMS hardening, access control and logging, to running an assessment, fixing and verifying findings, reporting and building your own check. Each module links theory to the real OpenVAPT checks and includes a hands-on lab and a self-check quiz. ### How Do I Install OpenVAPT? Build it from source with Go (recommended for transparency), or run the one-command installer: curl -fsSL https://raw.githubusercontent.com/OpenVAPT/openvapt/main/scripts/install.sh | sh . The optional installer, hosted on GitHub and run at your own risk, picks the most stable release channel that has a release and warns when that is a pre-release, downloads the Linux binary for your CPU from GitHub Releases over HTTPS, is designed to refuse to install it unless its SHA-256 checksum matches, verifies its Sigstore signature when cosign is installed, and installs it without root. By default it scans nothing (Download Only); adding --run scans straight after installing (Download and Scan), which still requires you to type I ACCEPT. ### Can I Download Without Scanning? Yes. Download Only is the installer's default: curl -fsSL https://raw.githubusercontent.com/OpenVAPT/openvapt/main/scripts/install.sh | sh installs and verifies OpenVAPT and scans nothing. It then prints the next steps: openvapt scan --dry-run to preview every file, command and request a scan would use, and sudo openvapt scan to scan later. You can also download the binary, SHA256SUMS and the Sigstore bundle from GitHub Releases and verify them yourself, as the download page shows, or build from source. ### How Do I Scan Straight After Downloading? Add --run to the installer: curl -fsSL https://raw.githubusercontent.com/OpenVAPT/openvapt/main/scripts/install.sh | sh -s -- --run . After installing and verifying, it starts a read-only scan of the same machine in your terminal. The scan shows the acknowledgements and continues only when you type I ACCEPT on the terminal; the installer never accepts for you and refuses to scan without a terminal. It writes HTML, JSON, SARIF and Markdown reports, signed with your local key, to a new private folder ~/openvapt-reports/ named after the UTC time (or --out DIR), then prints the severity summary, the exit code, every report path and how to open the HTML report. Add --sudo to run only the scan with sudo for full coverage. A scan usually takes one to five minutes and is a self-assessment, not a penetration test. ### Is OpenVAPT Free for Students and Teachers? Yes. OpenVAPT is free for anyone. It includes a built-in simulated lab (openvapt demo) with a deliberately vulnerable server and a hardened server, so students can run a full simulated scan, read a report and compare the fixes on any computer, without scanning a real machine. Teaching is governed by the Teaching With OpenVAPT terms (TEACHING.md). ### Can Teachers Use OpenVAPT in Class? Yes, free of charge, under the Teaching With OpenVAPT terms. Schools, colleges, universities (including those charging ordinary tuition), government training academies and organisations training their own staff may teach with it. Teachers may adapt and translate the Academy modules, quizzes and course plans into their own slides and learning systems with attribution and the required notices; teach online, live or recorded; and use it for grading, projects and capture-the-flag exercises on the simulated lab or on lab machines and virtual machines the institution owns, has authorised and has isolated. Each student accepts the terms personally, or through a parent or guardian if under 18. A student may run it on a computer the student personally owns or controls only as that student's own Own Use, never as a class requirement. Commercial training providers, paid public bootcamps, fee-paying certification courses, training another organisation's staff and any class scan of a machine the institution does not own or control are not allowed. Practice lab answer keys can be printed by any student, so they suit learning exercises rather than secure exams. OpenVAPT is pre-release and used 100% at your own risk. ### How Do I Cite OpenVAPT? Press Cite at the top or bottom of any page of openvapt.com for APA, IEEE, Harvard, MLA, Chicago and BibTeX with Copy buttons and .bib, .ris, CITATION.cff and CSL-JSON downloads, or run openvapt cite (for example openvapt cite --format bibtex) to print the citation for your exact version. Every report also ends with a How to Cite block. The author is the group author OpenVAPT, with no personal names, and OpenVAPT has no DOI yet. Citing is requested whenever OpenVAPT helped your work, and credit is required by section 5.4 of the licence when you copy or share its material. ## All checks ### HOST-AUD-001 Audit Daemon Not Enabled at Boot - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that the audit daemon (auditd) is set to start at every boot, not only running now. - Why: An audit daemon that was started by hand stops recording at the next reboot, leaving a gap exactly when an attacker who forced a restart would want one. HOST-LOG-002 reports whether auditd is running; this check reports whether it will come back. - How detected: Runs the allow-listed read-only command systemctl is-enabled auditd and reads the single word it prints: enabled, static, alias or indirect pass; disabled, masked or enabled-runtime fail. If the command cannot run and /etc/audit exists the result is Incomplete; if /etc/audit does not exist the check is skipped. - Fix: systemctl enable auditd (on some distributions: systemctl enable --now auditd). Then confirm the rules in /etc/audit/rules.d load at boot with augenrules --check. - Verify: systemctl is-enabled auditd should print enabled. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-002 Identity Files Not Audited - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that changes to the account, group, password and sudo files are recorded by the audit system. - Why: An attacker who adds an account, joins a privileged group or grants themselves sudo edits one of a handful of files. A watch rule on each makes that change an audit event with the user, process and time attached; without it the change leaves no trace beyond the file itself. - How detected: Requires a watch (-w or -F path=) on /etc/passwd, /etc/group, /etc/shadow, /etc/gshadow, /etc/sudoers and /etc/sudoers.d. Reads the active lines of /etc/audit/rules.d/*.rules and /etc/audit/audit.rules; the audit log itself is never read. The loaded rule set is not read (auditctl is not on the read-only allow-list), so when the rules are in the files the allow-listed systemctl is-active auditd must report active for a Pass; otherwise the result is Incomplete, and the evidence says the loaded rules were not verified. - Fix: Add to /etc/audit/rules.d/50-identity.rules: -w /etc/passwd -p wa -k identity and the same for /etc/group, /etc/shadow, /etc/gshadow, /etc/sudoers and /etc/sudoers.d/ , then run augenrules --load. - Verify: auditctl -l | grep -E 'passwd|shadow|sudoers' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-003 Login and Logout Events Not Audited - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that the files recording logins, failed logins and sessions are watched by the audit system. - Why: Login records are the first thing an investigator asks for and the first thing an intruder cleans. Watching lastlog, the failed-login store and the wtmp and btmp session files makes tampering itself an audit event. - How detected: Requires a watch on /var/log/lastlog, on a failed-login store (/var/log/faillog, /var/log/faillock, /var/run/faillock or /var/log/tallylog), and on /var/log/wtmp and /var/log/btmp. Reads the active lines of /etc/audit/rules.d/*.rules and /etc/audit/audit.rules; the audit log itself is never read. The loaded rule set is not read (auditctl is not on the read-only allow-list), so when the rules are in the files the allow-listed systemctl is-active auditd must report active for a Pass; otherwise the result is Incomplete, and the evidence says the loaded rules were not verified. - Fix: Add to /etc/audit/rules.d/50-login.rules: -w /var/log/lastlog -p wa -k logins , -w /var/run/faillock -p wa -k logins , -w /var/log/wtmp -p wa -k session , -w /var/log/btmp -p wa -k session , then augenrules --load. - Verify: auditctl -l | grep -E 'lastlog|faillock|wtmp|btmp' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-004 System Time Changes Not Audited - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that the audit system records changes to the system clock and time zone. - Why: Moving the clock is a classic way to confuse log timelines, forge timestamps or defeat certificate and token expiry. Auditing the time-setting system calls and /etc/localtime attributes every change to a user and process. - How detected: Requires a rule on the system calls adjtimex, settimeofday and clock_settime, and a watch on /etc/localtime. Reads the active lines of /etc/audit/rules.d/*.rules and /etc/audit/audit.rules; the audit log itself is never read. The loaded rule set is not read (auditctl is not on the read-only allow-list), so when the rules are in the files the allow-listed systemctl is-active auditd must report active for a Pass; otherwise the result is Incomplete, and the evidence says the loaded rules were not verified. - Fix: Add to /etc/audit/rules.d/50-time.rules: -a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change (and the same with arch=b32), plus -w /etc/localtime -p wa -k time-change , then augenrules --load. - Verify: auditctl -l | grep -E 'settimeofday|localtime' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-005 Kernel Module Loading Not Audited - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that loading and unloading kernel modules is recorded by the audit system. - Why: A rootkit is usually a kernel module. Auditing init_module, finit_module and delete_module (or the kmod program) records the moment one is loaded, which is often the only evidence that survives. - How detected: Requires a rule on the system calls init_module and delete_module (finit_module is recommended as well), or a watch or execution rule on the kmod, insmod, rmmod and modprobe programs. Reads the active lines of /etc/audit/rules.d/*.rules and /etc/audit/audit.rules; the audit log itself is never read. The loaded rule set is not read (auditctl is not on the read-only allow-list), so when the rules are in the files the allow-listed systemctl is-active auditd must report active for a Pass; otherwise the result is Incomplete, and the evidence says the loaded rules were not verified. - Fix: Add to /etc/audit/rules.d/50-modules.rules: -a always,exit -F arch=b64 -S init_module,finit_module,delete_module -k modules (and arch=b32), or -a always,exit -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset -k modules , then augenrules --load. - Verify: auditctl -l | grep -E 'init_module|kmod' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-006 Privileged Command Use Not Audited - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that the audit system records when ordinary users run privileged (setuid or setgid) programs. - Why: Programs such as sudo, su, passwd and mount run with elevated rights. Recording each execution by a real user (auid at or above the first normal user id) shows who used privilege and when, which is the core of a usable audit trail. - How detected: Requires at least one execution rule that combines -F perm=x with an -F auid>= filter (the usual form for privileged programs), or an execve rule filtered on euid=0. Reads the active lines of /etc/audit/rules.d/*.rules and /etc/audit/audit.rules; the audit log itself is never read. The loaded rule set is not read (auditctl is not on the read-only allow-list), so when the rules are in the files the allow-listed systemctl is-active auditd must report active for a Pass; otherwise the result is Incomplete, and the evidence says the loaded rules were not verified. - Fix: Generate rules for every setuid and setgid program: find / -xdev \( -perm -4000 -o -perm -2000 \) -type f | awk '{print "-a always,exit -F path=" $1 " -F perm=x -F auid>=1000 -F auid!=unset -k privileged"}' > /etc/audit/rules.d/50-privileged.rules then augenrules --load. - Verify: auditctl -l | grep 'perm=x' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-007 Audit Configuration Not Immutable - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that the audit rule set ends with -e 2, which locks the rules until the next reboot. - Why: Without the immutable flag, anyone who gains root can delete the audit rules (auditctl -D) and work unobserved. With -e 2 the rules can only be changed by rebooting, which is itself a loud, logged event. - How detected: Requires the last active line of the audit rules to be -e 2. Reads the active lines of /etc/audit/rules.d/*.rules and /etc/audit/audit.rules; the audit log itself is never read. The loaded rule set is not read (auditctl is not on the read-only allow-list), so when the rules are in the files the allow-listed systemctl is-active auditd must report active for a Pass; otherwise the result is Incomplete, and the evidence says the loaded rules were not verified. - Fix: Add -e 2 as the last line of the last file in /etc/audit/rules.d (for example 99-finalize.rules), then augenrules --load and reboot. Plan rule changes before enabling it: every later change needs a reboot. - Verify: auditctl -s | grep enabled should print enabled 2. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-008 Audit Log Files Readable by Others - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that the audit log directory and files can be read only by root. - Why: Audit logs record which user ran which command with which arguments, including file names and sometimes secrets passed on a command line. They are also the record an intruder most wants to edit. Both reasons call for root-only access. - How detected: Stats /var/log/audit (metadata only) and every file in it. The directory must not grant group write or any access to others (0750 or stricter); files must be owned by root with mode 0640 or stricter. Log contents are never read. If the directory does not exist the check is skipped; if it cannot be inspected the result is Incomplete. - Fix: chmod 0750 /var/log/audit && chmod 0600 /var/log/audit/* && chown -R root:root /var/log/audit , and set log_group = root in /etc/audit/auditd.conf so new files keep these modes. - Verify: stat -c '%a %U %n' /var/log/audit /var/log/audit/* - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-009 Audit Daemon Ignores a Full Log Disk - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that auditd is told what to do when its log file is full or the disk runs low, rather than silently dropping events. - Why: With max_log_file_action = ignore the log grows until the disk is full; with suspend auditing simply stops. An unset space_left_action means nobody is warned before that happens. Either way an attacker who fills the disk switches auditing off. - How detected: Reads max_log_file_action, space_left_action and admin_space_left_action from /etc/audit/auditd.conf. Each must be set and must not be ignore or suspend. Skipped when auditd is not installed; Incomplete when the file exists but cannot be read. - Fix: In /etc/audit/auditd.conf set max_log_file_action = keep_logs (or rotate with enough num_logs), space_left_action = email (or syslog or exec), and admin_space_left_action = halt or single as policy requires, then systemctl restart auditd. Caution: admin_space_left_action = halt or single stops the server or drops it to single-user mode when the audit disk fills; choose it only where losing audit records is worse than downtime, and give /var/log/audit room and monitoring first. - Verify: grep -E '^(max_log_file_action|space_left_action|admin_space_left_action)' /etc/audit/auditd.conf - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-010 Journal Not Persistent or Not Compressed - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that systemd-journald is configured to keep the journal on disk and to compress it. - Why: Storage=volatile keeps the journal only in memory, so a reboot (or a crash an attacker caused) erases the evidence. Compression keeps a persistent journal small enough to retain for weeks. HOST-LOG-001 looks for the journal directory; this check reads the configuration that decides what happens to it. - How detected: Reads Storage= and Compress= from /etc/systemd/journald.conf and /etc/systemd/journald.conf.d/*.conf. Storage=persistent passes; auto or unset passes only when /var/log/journal exists; volatile or none fails. Compress=no fails. Incomplete when the files exist but cannot be read; skipped when systemd is not present. - Fix: In /etc/systemd/journald.conf set Storage=persistent and Compress=yes , create the directory with mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal , then systemctl restart systemd-journald. - Verify: journalctl --header | head and ls -d /var/log/journal - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-AUD-011 Logs Not Forwarded to a Remote Collector - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that system logs are sent to another machine, so an intruder with root cannot erase the only copy. - Why: Whoever gains root on a server can delete or edit its local logs. A copy that already left the machine, to a log server or a SIEM, is the record an investigation can trust. NIST SP 800-92 treats centralised log collection as a basic log management control. - How detected: Looks for a forwarding rule in /etc/rsyslog.conf and /etc/rsyslog.d/*.conf (a selector followed by @host or @@host, or an omfwd action), a network destination in /etc/syslog-ng/syslog-ng.conf, or a URL= in /etc/systemd/journal-upload.conf. The remote address is never copied into the report. Incomplete when a file exists but cannot be read. - Fix: rsyslog: create /etc/rsyslog.d/90-remote.conf with *.* action(type="omfwd" target="logs.example.internal" port="6514" protocol="tcp" StreamDriver="gtls" StreamDriverMode="1" StreamDriverAuthMode="x509/name") and restart rsyslog. Or configure systemd-journal-upload with URL= pointing at a journal-remote collector. Prefer TLS so logs are not readable in transit. - Verify: grep -rE '^[^#].*@|omfwd' /etc/rsyslog.conf /etc/rsyslog.d/ or grep '^URL=' /etc/systemd/journal-upload.conf - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/audit_journal.go ### HOST-BOOT-001 Boot Loader Has No Password - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks that GRUB is configured with a superuser and a hashed password so the boot entries cannot be edited at the console. - Why: Without a boot loader password anyone at the console or virtual console can edit the kernel command line (for example add init=/bin/bash) and get a root shell without any credentials, bypassing every control on the running system. - How detected: Reads /boot/grub/grub.cfg, /boot/grub2/grub.cfg, /boot/grub2/user.cfg, /boot/efi/EFI/*/grub.cfg, /boot/efi/EFI/*/user.cfg and /etc/grub.d/* and looks for set superusers= together with password_pbkdf2 (or password). Only the presence of these directives is reported; hashes are never shown. Skipped when no GRUB configuration exists; incomplete when a file exists but cannot be read. - Fix: Run grub-mkpasswd-pbkdf2 (grub2-setpassword on RHEL family), add set superusers="admin" and password_pbkdf2 admin to /etc/grub.d/40_custom (RHEL family writes /boot/grub2/user.cfg), then regenerate with update-grub or grub2-mkconfig -o /boot/grub2/grub.cfg. Add --unrestricted to the normal menu entry so unattended reboots still work. - Verify: grep -E 'set superusers|password_pbkdf2' /boot/grub/grub.cfg /boot/grub2/grub.cfg /boot/grub2/user.cfg /etc/grub.d/* 2>/dev/null | sed 's/grub.pbkdf2.*//' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/boot.go ### HOST-BOOT-002 UEFI Secure Boot Disabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks the firmware's SecureBoot variable to see whether the boot chain is verified. - Why: Secure Boot makes the firmware refuse unsigned boot loaders and kernels, which blocks boot kits and unsigned kernels planted by an attacker with disk or console access. Without it, anything written to the boot partition runs at the next reboot. - How detected: Reads /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c (the four-byte attribute header followed by one data byte; 1 means enabled). Machines without /sys/firmware/efi boot in legacy BIOS mode and are skipped, as are UEFI machines that expose no SecureBoot variable; an unreadable variable is incomplete. No command such as mokutil is run. - Fix: Enable Secure Boot in the firmware set-up and boot a signed shim and kernel (every mainstream distribution ships them). Custom kernels and out-of-tree modules then need signing with a Machine Owner Key. Where the platform cannot do Secure Boot, record the exception. Caution: unsigned kernels and modules (for example DKMS drivers such as NVIDIA or ZFS) stop loading once it is on, so sign or replace them first and keep console access for the first boot. - Verify: mokutil --sb-state or od -An -t u1 /sys/firmware/efi/efivars/SecureBoot-* (the fifth byte is 1 when enabled). - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/boot.go ### HOST-BOOT-003 Uncommon Filesystem Modules Not Disabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - What: Checks that the cramfs, freevxfs, jffs2, hfs, hfsplus, udf and squashfs kernel modules are disabled in modprobe.d. - Why: Each filesystem driver is kernel code that parses untrusted data. A local user who can mount removable media or a loop device gets the kernel to parse a crafted image with one of these rarely maintained drivers, and several have had memory-corruption bugs. Removing drivers nobody uses removes that attack surface. - How detected: Reads /etc/modprobe.d/*.conf and /usr/lib/modprobe.d/*.conf and, for each module, accepts install /bin/false (or /bin/true) or blacklist . squashfs is left out when /snap exists because snaps need it. Modules with neither line fail; an unreadable modprobe.d file makes the check incomplete. - Fix: Create /etc/modprobe.d/openvapt-filesystems.conf with, for each module: install /bin/false and blacklist then unload any loaded one with modprobe -r . Caution: first confirm that no listed module is in use (lsmod, findmnt); blocking a filesystem a mount or package format needs (for example squashfs for snap packages, or vfat for the EFI system partition) breaks those mounts at the next boot. - Verify: for m in cramfs freevxfs jffs2 hfs hfsplus udf squashfs; do modprobe -n -v $m; lsmod | grep -w $m; done - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/boot.go ### HOST-BOOT-004 USB Storage Driver Not Disabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - What: Checks that the usb-storage kernel module is disabled in modprobe.d on servers that do not need removable media. - Why: A USB stick plugged into a server is the simplest way to copy data out or bring malware in without touching the network. Disabling the driver makes the device invisible to the kernel. - How detected: Reads /etc/modprobe.d/*.conf and /usr/lib/modprobe.d/*.conf for install usb-storage /bin/false (or /bin/true) or blacklist usb-storage. Neither present fails; an unreadable file makes the check incomplete. - Fix: Create /etc/modprobe.d/openvapt-usb.conf containing install usb-storage /bin/false and blacklist usb-storage then modprobe -r usb-storage. Servers that need USB backups should record the exception instead. - Verify: modprobe -n -v usb-storage should print install /bin/false; lsmod | grep usb_storage should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/boot.go ### HOST-BOOT-005 Kernel Module Loading Unrestricted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:L - What: Checks that the running kernel refuses unsigned modules or has module loading switched off entirely. - Why: A root-level attacker who can load any module owns the kernel: rootkits hide processes and files from every tool on the box. Signature enforcement or kernel.modules_disabled = 1 after boot makes that persistence step fail. - How detected: Reads /proc/sys/kernel/modules_disabled (1 passes), otherwise /sys/module/module/parameters/sig_enforce (Y passes) and /sys/kernel/security/lockdown ([integrity] or [confidentiality] passes). Kernels built without loadable module support are skipped; a value that exists but cannot be read is incomplete. - Fix: Boot with module.sig_enforce=1 (or lockdown=integrity) on the kernel command line and sign any out-of-tree modules, or set kernel.modules_disabled = 1 from a late boot service once every needed module is loaded (it cannot be turned back on without a reboot). - Verify: sysctl kernel.modules_disabled; cat /sys/module/module/parameters/sig_enforce /sys/kernel/security/lockdown - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/boot.go ### HOST-BOOT-006 No File Integrity Monitoring - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:N - What: Checks that a file integrity tool (AIDE, Tripwire, Samhain or equivalent) is installed and scheduled to run. - Why: After a compromise the first question is which files changed. Without a baseline and a scheduled comparison, a replaced binary, an added SUID file or an edited PAM stack stays invisible for months. - How detected: Looks for the tools' programs and configuration (/usr/bin/aide, /usr/sbin/aide, /etc/aide/aide.conf, /etc/aide.conf, /usr/sbin/tripwire, /etc/tripwire, /usr/sbin/samhain, /etc/samhainrc, /usr/sbin/osqueryd and /opt/ossec/bin/ossec-syscheckd) and then for a schedule: a file in /etc/cron.d, /etc/cron.daily or /etc/cron.weekly, a systemd timer under /etc/systemd/system or /usr/lib/systemd/system, or a line in /etc/crontab that names the tool. Tool present with no schedule still fails. Stat and glob only; configuration contents are not read except /etc/crontab. - Fix: Install AIDE (apt install aide / dnf install aide), initialise the database with aideinit or aide --init, move the new database into place, and enable the daily check (dailyaidecheck.timer on Debian family, or a cron.daily entry) that mails or logs the differences. - Verify: aide --check (or your tool's check command) runs and reports; systemctl list-timers | grep -i aide or ls /etc/cron.daily | grep -i aide - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/boot.go ### HOST-BOOT-007 Prelink Installed - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:N - What: Checks that the prelink tool is absent, or disabled where the package is still installed. - Why: Prelink rewrites binaries and libraries in place, which changes their hashes and defeats integrity monitoring and package verification, and it weakens address randomisation by fixing load addresses. Modern systems gain nothing from it. - How detected: Stats /usr/sbin/prelink, /usr/bin/prelink and /etc/prelink.conf. If any exists, reads /etc/sysconfig/prelink and /etc/default/prelink for PRELINKING=no, which passes with a note. Absent files pass; a stat error other than not-found is incomplete. - Fix: Undo and remove it: prelink -ua && apt purge prelink (or dnf remove prelink). Then re-initialise the integrity database (aide --init). - Verify: command -v prelink should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/boot.go ### HOST-BOOT-008 Core Dumps Not Restricted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that process core dumps are turned off through PAM limits, and through systemd-coredump where the kernel hands dumps to it. - Why: A core dump is a copy of a program's memory, including passwords, keys and session tokens it held. On a server nobody debugs, dumps are only a leak waiting in /var/lib/systemd/coredump or the working directory. HOST-KRN-004 covers setuid programs; this check covers every process. - How detected: Reads /etc/security/limits.conf and /etc/security/limits.d/*.conf for a * hard core 0 line, and /proc/sys/kernel/core_pattern. When the pattern pipes dumps to systemd-coredump, /etc/systemd/coredump.conf and /etc/systemd/coredump.conf.d/*.conf must set Storage=none (ProcessSizeMax=0 also counts). Either the limits line or the systemd setting passes. Missing files are observed as missing; a file that exists but cannot be read makes the check incomplete. - Fix: Add * hard core 0 to /etc/security/limits.conf (or a file in limits.d), set Storage=none and ProcessSizeMax=0 in the [Coredump] section of /etc/systemd/coredump.conf, and keep fs.suid_dumpable = 0. - Verify: grep -E '^\*\s+hard\s+core\s+0' /etc/security/limits.conf /etc/security/limits.d/*; grep -E '^(Storage|ProcessSizeMax)' /etc/systemd/coredump.conf; cat /proc/sys/kernel/core_pattern - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/boot.go ### HOST-CRON-001 Cron Jobs Run Programs Other Users Can Modify - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Resolves the programs and scripts that system and root cron jobs run and checks whether any is world-writable or sits in a world-writable folder. - Why: HOST-FS-005 protects the cron tables themselves, but a root job that runs /usr/local/bin/backup.sh is only as trustworthy as that file. If any user can edit the script, or replace it in a writable folder, their code runs as root at the next scheduled minute. - How detected: Parses /etc/crontab, /etc/cron.d/* and root's crontab in /var/spool/cron for job lines, stats every absolute path in each command (up to three per job) and every script in cron.hourly, cron.daily, cron.weekly and cron.monthly, and fails on a file with the 'other write' bit or a parent folder that is world-writable without the sticky bit. - Fix: chmod o-w and its folder; move scripts out of /tmp or user-writable folders into /usr/local/sbin owned by root; have cron run only files owned by root (chown root:root). - Verify: for p in $(awk '/^[^#]/ && NF>6 {print $7}' /etc/crontab /etc/cron.d/*); do stat -c '%a %U %n' $p; done - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hygiene.go ### HOST-CRON-002 World-Writable Directory in Cron's PATH - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks the PATH that system cron jobs search for commands for world-writable or relative entries. - Why: A cron job that calls backup-db rather than /usr/local/sbin/backup-db runs whichever file of that name PATH finds first. If a folder on that PATH is world-writable, or the PATH contains an empty or relative entry, any user can plant a program that root runs on schedule (PATH interception). - How detected: Reads PATH= from /etc/crontab (default /usr/bin:/bin when absent), /etc/cron.d/* and /etc/environment, stats every folder (metadata only) and fails on a world-writable folder without the sticky bit or on an empty, '.' or relative entry. Folders that cannot be stat'ed make the result incomplete. - Fix: Remove the writable folder from PATH in /etc/crontab (or chmod o-w it), delete relative entries, and call programs in cron jobs by absolute path. - Verify: grep ^PATH /etc/crontab /etc/cron.d/* ; for d in $(grep ^PATH /etc/crontab | cut -d= -f2 | tr : ' '); do stat -c '%a %n' $d; done - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hygiene.go ### HOST-CTR-001 Docker API Exposed Over TCP Without TLS - Domain: host, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H - What: Checks whether the Docker daemon is configured to listen on a TCP socket without TLS client verification. - Why: The Docker API is equivalent to root on the host. Exposed over TCP without mutual TLS, anyone who can reach the port can start a privileged container and take over the machine; this is a common cause of cryptomining infections. - How detected: Reads /etc/docker/daemon.json (hosts and tlsverify) and Docker systemd unit files and drop-ins for -H tcp:// flags. - Fix: Remove tcp:// listeners and use the local Unix socket or SSH (docker context with ssh://). If remote access is unavoidable, enable tlsverify with client certificates and restrict the port in the firewall. - Verify: ss -tlnp | grep -E ':(2375|2376)' and grep -r 'tcp://' /etc/docker /etc/systemd/system/docker.service.d - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers.go ### HOST-CTR-002 Docker Daemon Not Hardened - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:L - What: Checks the Docker daemon defaults: no-new-privileges, inter-container communication, user namespace remapping and live restore. - Why: Hardened daemon defaults limit what a compromised container can do: no-new-privileges blocks setuid escalation, disabling icc isolates containers, and user namespaces map container root to an unprivileged host user. - How detected: Reads /etc/docker/daemon.json and lists each hardening setting that is missing: no-new-privileges true, icc false, userns-remap set, live-restore true. - Fix: Add to /etc/docker/daemon.json: "no-new-privileges": true, "icc": false, "userns-remap": "default", "live-restore": true then restart Docker after testing your containers. - Verify: cat /etc/docker/daemon.json ; docker info | grep -Ei 'security|userns' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers.go ### HOST-CTR-003 Privileged or Host-Namespace Containers - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - What: Looks for containers running privileged, sharing the host's PID, network or IPC namespace, or with dangerous added capabilities. - Why: A privileged container, or one with CAP_SYS_ADMIN or host namespaces, has almost no isolation: a compromise of the application inside is a compromise of the host. - How detected: Reads /var/lib/docker/containers/*/hostconfig.json (needs root) and reports Privileged true, PidMode/NetworkMode/IpcMode host, or CapAdd containing ALL, SYS_ADMIN, SYS_PTRACE, SYS_MODULE or NET_ADMIN. - Fix: Recreate each container without --privileged, --pid=host, --net=host and --ipc=host; drop all capabilities and add back only the specific ones needed (--cap-drop=ALL --cap-add=...). - Verify: docker inspect --format '{{.Name}} {{.HostConfig.Privileged}} {{.HostConfig.PidMode}} {{.HostConfig.NetworkMode}} {{.HostConfig.CapAdd}}' $(docker ps -aq) - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers.go ### HOST-CTR-004 Containers Mount the Docker Socket or Host Root - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - What: Looks for containers that bind-mount the Docker socket, the host root filesystem or sensitive host paths. - Why: A container with /var/run/docker.sock can control Docker and therefore the host. Mounting / or /etc gives direct write access to host configuration. - How detected: Reads Binds from /var/lib/docker/containers/*/hostconfig.json (needs root) and reports mounts of /var/run/docker.sock, /run/docker.sock, /, /etc, /root, /proc or /sys from the host. - Fix: Remove these mounts. If a tool genuinely needs the Docker API, use a restricted socket proxy that allows only the required read-only calls. - Verify: docker inspect --format '{{.Name}} {{.HostConfig.Binds}}' $(docker ps -aq) - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers.go ### HOST-CTR-005 Docker Container Logs Have No Size Limit - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - What: Checks whether the Docker daemon's default log driver caps log file size and rotation. - Why: With the default json-file driver and no max-size, a chatty or attacked container can fill the disk with its own logs and take the host and every other container down. Bounded, rotated logs also keep evidence manageable for investigations. - How detected: Reads /etc/docker/daemon.json. Fails when log-driver is json-file or unset and log-opts lacks max-size, or when a container's LogConfig uses json-file without max-size. Drivers that ship logs elsewhere (journald, syslog, fluentd, awslogs) pass. - Fix: Add to /etc/docker/daemon.json: "log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"} (or use journald) and restart Docker; recreate containers that set their own LogConfig. - Verify: docker info --format '{{.LoggingDriver}}' ; grep -A3 log-opts /etc/docker/daemon.json - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-006 Docker Userland Proxy Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L - What: Checks whether the Docker daemon still uses the docker-proxy userland process for published ports. - Why: The userland proxy is a root process per published port that exists only for an old loopback edge case. Hairpin NAT does the same job in the kernel with less code running as root and fewer processes to crash or exploit. - How detected: Reads /etc/docker/daemon.json and fails unless userland-proxy is false. - Fix: Add "userland-proxy": false to /etc/docker/daemon.json and restart Docker; test that published ports still answer from the host itself. - Verify: ps -C docker-proxy should list nothing after containers are recreated. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-007 Docker Default Ulimits Not Set - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L - What: Checks whether the Docker daemon sets default ulimits (open files and processes) for containers. - Why: Without default limits a container inherits the daemon's generous limits and can exhaust file descriptors or spawn processes until the host starves. Daemon-wide defaults protect every container that forgets to set its own. - How detected: Reads /etc/docker/daemon.json and fails unless default-ulimits contains at least nofile and nproc. - Fix: Add to /etc/docker/daemon.json: "default-ulimits": {"nofile": {"Name": "nofile", "Hard": 65536, "Soft": 32768}, "nproc": {"Name": "nproc", "Hard": 4096, "Soft": 2048}} and restart Docker; tune the numbers to your workloads. - Verify: docker info | grep -A3 -i ulimit or docker run --rm alpine sh -c 'ulimit -n; ulimit -u' (your own test container) - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-008 Docker Socket Accessible to Non-Administrators - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - What: Checks the Docker socket's permissions and who belongs to the docker group. - Why: Whoever can write to /var/run/docker.sock is root: one command starts a privileged container with the host's disk mounted. Members of the docker group have that power without sudo, logging or a password prompt, so the group should be empty or hold only administrators. - How detected: Stats /var/run/docker.sock (or /run/docker.sock) and fails when others can read or write it, or when it is not owned by root. Reads /etc/group and /etc/passwd and lists every member of the docker group as evidence; a non-empty group fails. - Fix: chmod 660 /var/run/docker.sock && chown root:docker /var/run/docker.sock (the daemon recreates it with these defaults). Remove users from the docker group (gpasswd -d docker) and give administrators sudo docker, or move to rootless Docker. - Verify: stat -c '%a %U:%G %n' /var/run/docker.sock ; getent group docker - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-009 Docker Not Running in Rootless Mode - Domain: host, severity: info, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:N - What: Reports whether the Docker daemon runs as root or in rootless mode. Informational. - Why: In rootless mode the daemon and every container run inside a user namespace as an ordinary account, so a container breakout lands in that account rather than in root. It is not possible for every workload (some networking and storage features differ), so this is information for a design decision, not a failure. - How detected: Looks for a rootless indicator: a per-user socket at /run/user/*/docker.sock, a per-user daemon configuration at /home/*/.config/docker/daemon.json, or a user systemd unit for docker. Reports root mode when only the system daemon is present. - Fix: Where the workloads allow it, install rootless Docker (dockerd-rootless-setuptool.sh install) for the service account and disable the system daemon; otherwise keep userns-remap (HOST-CTR-002) as the fallback. - Verify: docker info --format '{{.SecurityOptions}}' lists name=rootless. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-010 Docker Daemon Allows Insecure Registries or Debug Features - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N - What: Checks daemon.json for insecure-registries, debug mode and experimental features. - Why: An insecure registry is pulled over plain HTTP or with an unverified certificate, so anyone on the path can swap the image for a malicious one. Debug logging and experimental features widen the daemon's attack surface and leak internal detail into logs. - How detected: Reads /etc/docker/daemon.json and the ExecStart lines of docker.service and its drop-ins (docker.service.d/*.conf under /etc/systemd/system, /run/systemd/system, /usr/lib/systemd/system and /lib/systemd/system). Fails when insecure-registries is non-empty, debug or experimental is true, or the dockerd command line carries --insecure-registry, --debug (-D) or --experimental. Incomplete when daemon.json exists but cannot be read or parsed, or a unit file cannot be read. - Fix: Remove insecure-registries (serve the registry over TLS with a trusted certificate, or add its CA to /etc/docker/certs.d//ca.crt), set "debug": false and "experimental": false in daemon.json, and remove any --insecure-registry, --debug (-D) or --experimental flag from the dockerd ExecStart in docker.service drop-ins (then systemctl daemon-reload). Restart Docker in a maintenance window: running containers stop unless live-restore is on. - Verify: docker info | grep -Ei 'insecure|debug|experimental' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-011 Seccomp or AppArmor Disabled for Containers - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - What: Looks for the daemon or individual containers turning off the default seccomp profile, AppArmor or SELinux labelling. - Why: Docker's default seccomp profile blocks around 40 dangerous system calls and AppArmor or SELinux confines what a container's root can touch on the host. Running unconfined removes the main barriers between a compromised container and the kernel. - How detected: Reads /etc/docker/daemon.json (seccomp-profile unconfined) and each container's hostconfig.json SecurityOpt (needs root) for seccomp=unconfined, seccomp:unconfined, apparmor=unconfined or label=disable. - Fix: Remove --security-opt seccomp=unconfined and apparmor=unconfined from the container definitions and recreate them; if an application needs one extra system call, write a custom seccomp profile that adds only that call. Delete seccomp-profile from daemon.json. - Verify: docker inspect --format '{{.Name}} {{.HostConfig.SecurityOpt}}' $(docker ps -aq) - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-012 Containers Run Without Memory or Process Limits - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - What: Looks for containers with no memory limit and no PID limit. - Why: A container with no limits can use all the host's memory or fork until the kernel runs out of process slots, taking the host and every neighbour down (a fork bomb or a memory leak is enough). Limits turn that into a problem for one container. - How detected: Reads Memory and PidsLimit from each container's hostconfig.json (needs root) and fails when both are zero or unset. - Fix: Recreate each container with --memory (for example 512m) and --pids-limit (for example 256), or set them in the compose file under deploy.resources.limits / pids_limit. - Verify: docker inspect --format '{{.Name}} mem={{.HostConfig.Memory}} pids={{.HostConfig.PidsLimit}}' $(docker ps -aq) - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-013 Containers Run as Root With a Writable Root Filesystem - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - What: Looks for containers whose process runs as root and whose root filesystem is writable. - Why: Root inside the container is the first half of most container escapes, and a writable root filesystem lets an attacker install tools and persist. A non-root user plus --read-only with a few tmpfs mounts removes both at once for most services. - How detected: Reads Config.User from config.v2.json and ReadonlyRootfs from hostconfig.json (needs root) and fails when the user is empty, root or 0 and the root filesystem is writable. - Fix: Add USER to the image or run with --user 1000:1000, and start the container with --read-only plus --tmpfs /tmp for scratch space; recreate the container. - Verify: docker inspect --format '{{.Name}} user={{.Config.User}} ro={{.HostConfig.ReadonlyRootfs}}' $(docker ps -aq) - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-014 containerd Remote API or Podman Unconfined Defaults - Domain: host, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks the containerd and Podman configuration files for a TCP API without TLS and for unconfined seccomp or disabled labelling defaults. - Why: containerd's gRPC API is root on the host, like the Docker API; a tcp_address without TLS exposes it to the network. Podman's containers.conf sets the defaults every container inherits: an unconfined seccomp profile or disabled SELinux labelling removes the isolation for all of them. - How detected: Reads /etc/containerd/config.toml ([grpc] tcp_address set without tcp_tls_cert) and /etc/containers/containers.conf plus /usr/share/containers/containers.conf (seccomp_profile unconfined, label false, or apparmor_profile unconfined in [containers]). - Fix: containerd: remove tcp_address or set tcp_tls_cert and tcp_tls_key, then restart containerd. Podman: delete seccomp_profile = "unconfined", set label = true and apparmor_profile = "container-default" in containers.conf. - Verify: grep -E 'tcp_address|tcp_tls' /etc/containerd/config.toml ; grep -E 'seccomp_profile|^label|apparmor_profile' /etc/containers/containers.conf - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CTR-015 Docker Content Trust Not Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:N - What: Checks whether DOCKER_CONTENT_TRUST=1 is set system-wide so that only signed images are pulled. - Why: Without content trust, docker pull accepts whatever the registry or a tampering proxy returns for a tag. Signature verification makes a substituted image fail to pull instead of running as root on your host. - How detected: Reads /etc/environment, /etc/profile, /etc/profile.d/*.sh and the Docker service drop-ins for DOCKER_CONTENT_TRUST=1. Fails when Docker is installed and the variable is not set anywhere system-wide. - Fix: Add DOCKER_CONTENT_TRUST=1 to /etc/environment (or a file in /etc/profile.d) and sign your own images with docker trust sign; images without signatures then need an explicit --disable-content-trust for a deliberate exception. - Verify: grep -r DOCKER_CONTENT_TRUST /etc/environment /etc/profile.d - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers_depth.go ### HOST-CVE-001 Installed OS Packages With Known Vulnerabilities - Domain: host, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks installed packages against an offline vulnerability snapshot. - Why: Known vulnerable package versions can expose a server to documented attacks. - How detected: Reads installed package versions and matches OSV ecosystem ranges; each match names its matcher (exact version or range) and the snapshot date. When the binary carries only the test fixture snapshot (a source build without 'make vulndb'), the check reports Incomplete rather than Pass, because no advisory data was available. - Fix: Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild. - Verify: Re-run OpenVAPT after patching and confirm the finding is absent. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/cve.go ### HOST-CVE-002 Known Exploited Vulnerabilities Present - Domain: host, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks installed packages against an offline vulnerability snapshot. - Why: Known vulnerable package versions can expose a server to documented attacks. - How detected: Reads installed package versions and matches OSV ecosystem ranges; each match names its matcher (exact version or range) and the snapshot date. When the binary carries only the test fixture snapshot (a source build without 'make vulndb'), the check reports Incomplete rather than Pass, because no advisory data was available. - Fix: Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild. - Verify: Re-run OpenVAPT after patching and confirm the finding is absent. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/cve.go ### HOST-CVE-003 Vulnerability Snapshot Outdated or Test Data - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks installed packages against an offline vulnerability snapshot. - Why: Known vulnerable package versions can expose a server to documented attacks. - How detected: Reads installed package versions and matches OSV ecosystem ranges; each match names its matcher (exact version or range) and the snapshot date. When the binary carries only the test fixture snapshot (a source build without 'make vulndb'), the check reports Incomplete rather than Pass, because no advisory data was available. - Fix: Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild. - Verify: Re-run OpenVAPT after patching and confirm the finding is absent. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/cve.go ### HOST-DKR-001 Users With Root-Equivalent Docker Access - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - What: Checks whether non-root users can control Docker, which is equivalent to root access. - Why: Members of the docker group, or anyone who can write to the Docker socket, can start a container that mounts the host filesystem and become root. - How detected: Reads members of the docker group from /etc/group and the permissions of /var/run/docker.sock. - Fix: Remove users who do not need it from the docker group (gpasswd -d docker); consider rootless Docker or Podman; keep docker.sock at 660 root:docker. - Verify: getent group docker ; stat -c '%a %U:%G' /var/run/docker.sock - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-FS-001 Sensitive Account Files Have Unsafe Permissions - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - What: Checks that /etc/shadow and /etc/gshadow are not readable by others, and /etc/passwd and /etc/group are not writable by others. - Why: Readable shadow files let any local user (or a compromised web app) copy password hashes and crack them offline. Writable passwd/group files let them create a root account. - How detected: Stats each file without reading its contents and inspects the permission bits for 'other' and 'group' access. - Fix: chown root:root /etc/passwd /etc/group && chmod 644 /etc/passwd /etc/group ; chown root:shadow /etc/shadow /etc/gshadow && chmod 640 /etc/shadow /etc/gshadow (use root:root and 000 on RHEL-family systems). - Verify: stat -c '%a %U:%G %n' /etc/passwd /etc/group /etc/shadow /etc/gshadow - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files.go ### HOST-FS-002 Temporary Directory Missing the Sticky Bit - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H - What: Checks that world-writable temp directories have the sticky bit set. - Why: Without the sticky bit any user can delete or replace other users' files in /tmp, enabling symlink attacks against root-run scripts. - How detected: Stats /tmp and /var/tmp; world-writable without the sticky bit (mode 1777 expected) fails. - Fix: chmod 1777 /tmp /var/tmp - Verify: stat -c '%a %n' /tmp /var/tmp should print 1777 for both. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files.go ### HOST-FS-003 World-Writable Files in System Configuration - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Looks for files under /etc and in cron directories that any user can modify. - Why: A world-writable config or cron file lets any local user change how a root-owned service behaves, often leading straight to root. - How detected: Walks /etc (metadata only, up to 20000 files, symlinks not followed) and lists regular files with the 'other write' bit set, plus directories up to three levels deep that are world-writable without the sticky bit (anyone could replace files inside them). - Fix: For each listed file or directory: chmod o-w . Then find out how it became writable (a deployment script or a careless chmod 777). - Verify: find /etc -xdev \( -type f -perm -0002 \) -o \( -type d -perm -0002 ! -perm -1000 \) should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files.go ### HOST-FS-004 Unexpected SUID or SGID Programs - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Looks for set-user-ID or set-group-ID programs in places where packages do not normally install them. - Why: A SUID program runs with its owner's rights (usually root). One placed in /tmp, /home, /var/www or /opt is a common persistence and privilege-escalation trick. - How detected: Walks /tmp, /var/tmp, /dev/shm, /home, /var/www, /srv, /opt and /usr/local (metadata only) and lists files with the SUID or SGID bit. - Fix: Confirm whether each program is needed. Remove the bit with chmod u-s,g-s , or delete the file if you did not put it there and investigate how it arrived. - Verify: find /tmp /home /var/www /opt /usr/local -xdev -perm /6000 -type f should print nothing unexpected. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files.go ### HOST-FS-005 Cron Files Writable by Non-Root Users - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks that system cron files and directories are owned by root and not writable by others. - Why: Cron runs these jobs as root. Anyone who can edit them can run any command as root at the next scheduled minute. - How detected: Stats /etc/crontab, /etc/cron.d and its files, and the cron.hourly/daily/weekly/monthly directories, flagging non-root owners or group/other write bits. - Fix: chown root:root && chmod go-w for each listed path (600 for /etc/crontab is a common hardening choice). - Verify: stat -c '%a %U %n' /etc/crontab /etc/cron.* - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files.go ### HOST-FS-006 Boot Loader Configuration Exposed - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks that the GRUB configuration is not readable or writable by normal users. - Why: The boot loader configuration controls kernel parameters at boot. If writable, it allows boot-time privilege escalation; if readable, it may expose a boot password hash. - How detected: Stats every existing file matching /boot/grub/grub.cfg, /boot/grub2/grub.cfg, /boot/grub2/user.cfg (metadata only, contents are never read) and fails if it is accessible by group or others. - Fix: chown root:root && chmod 600 (for example /boot/grub/grub.cfg or /boot/grub2/grub.cfg). - Verify: stat -c '%a %U %n' /boot/grub/grub.cfg /boot/grub2/grub.cfg - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-FS-007 Private TLS Keys Readable by Others - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - What: Checks that TLS private keys under the standard key directories are not readable by other users. - Why: A stolen private key lets an attacker impersonate your websites and, with older key exchange, decrypt recorded traffic. - How detected: Stats every existing file matching /etc/ssl/private/*, /etc/pki/tls/private/*, /etc/letsencrypt/archive/*/privkey*.pem (metadata only, contents are never read) and fails if it is readable or writable by others. - Fix: chown root:root && chmod 600 (or 640 with a dedicated group the web server runs in). Rotate any key that was exposed. - Verify: stat -c '%a %U:%G %n' /etc/ssl/private/* /etc/pki/tls/private/* /etc/letsencrypt/archive/*/privkey*.pem - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-FS-008 Temporary Filesystems Allow Executables or Devices - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that /tmp, /var/tmp and /dev/shm are mounted with nodev and nosuid. - Why: World-writable temporary areas are where attackers drop tools. nosuid stops planted setuid programs working there, and nodev stops device files being created. - How detected: Reads /proc/mounts. For each of /tmp, /var/tmp and /dev/shm that is a separate mount, nodev and nosuid must be present. Paths that are not separate mounts are reported as skipped. - Fix: Mount the path as tmpfs or a separate filesystem with options nodev,nosuid (and noexec where software allows), for example via systemd tmp.mount or /etc/fstab, then remount. - Verify: findmnt -no OPTIONS /tmp /var/tmp /dev/shm - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-FS-009 Default File Creation Mask Too Permissive - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that the default umask makes new files unreadable to other users (027 or stricter). - Why: With umask 022 every new file is readable by every local user, including configuration exports, reports and backups created by administrators. - How detected: Reads UMASK from /etc/login.defs. Values that leave the 'other' read bit open (for example 022 or 002) fail. - Fix: Set UMASK 027 in /etc/login.defs (and, if used, in /etc/profile or /etc/bash.bashrc). - Verify: grep '^UMASK' /etc/login.defs - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-FS-010 /var/tmp Not a Separate Filesystem - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L - What: Checks that /var/tmp is its own mount (or a bind mount of /tmp) with nodev, nosuid and noexec. - Why: /var/tmp is world-writable and survives reboots. On the root filesystem it can be filled to exhaust disk space, and it accepts planted device files and executables unless mounted with restricting options. - How detected: Reads /proc/mounts (no command is run) and looks for a mount entry for /var/tmp carrying the options nodev, nosuid, noexec. Unreadable or partial /proc/mounts is reported as incomplete. - Fix: Mount /var/tmp as a separate filesystem, or bind-mount it onto /tmp: /tmp /var/tmp none bind,nodev,nosuid,noexec 0 0 in /etc/fstab, then reboot or remount. Caution: /var/tmp is meant to survive reboots and some installers and databases run programs from it, so check what uses it before binding it to /tmp with noexec, and keep console access when editing /etc/fstab (a wrong line can stop the boot). - Verify: findmnt -no TARGET,OPTIONS /var/tmp - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-011 /home Not a Separate Filesystem With nodev - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L - What: Checks that user home directories live on their own mount with nodev (and nosuid where practical). - Why: Users control the contents of /home. A separate mount stops a user filling the root filesystem, and nodev stops device files being created in a home directory to reach raw disks or memory. - How detected: Reads /proc/mounts (no command is run) and looks for a mount entry for /home carrying the options nodev. Unreadable or partial /proc/mounts is reported as incomplete. - Fix: Place /home on its own partition or logical volume and add nodev,nosuid to its options in /etc/fstab; remount with: mount -o remount /home . Caution: moving /home to a new volume needs a backup, a maintenance window and console access; a wrong /etc/fstab line can stop the boot. - Verify: findmnt -no TARGET,OPTIONS /home - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-012 Log Directories on the Root Filesystem - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L - What: Checks that /var/log (or /var) is a separate filesystem so runaway logging cannot fill the root disk. - Why: Logs grow under attacker influence (failed logins, scanner noise). When they share the root filesystem, filling them stops the whole server, and when the disk is full new security events are lost. - How detected: Reads /proc/mounts (no command is run) and looks for a mount entry for /var/log or /var. Unreadable or partial /proc/mounts is reported as incomplete. - Fix: Place /var/log (and ideally /var/log/audit) on its own partition or logical volume, listed in /etc/fstab with nodev,nosuid,noexec. Caution: moving log directories needs a backup, a maintenance window and console access; stop the logging services while copying, and a wrong /etc/fstab line can stop the boot. - Verify: findmnt -no TARGET,OPTIONS /var/log /var - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-013 Temporary Filesystems Allow Executables - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that /tmp, /var/tmp and /dev/shm, where they are separate mounts, carry the noexec option. - Why: Attackers who gain a foothold download tools into a world-writable temporary directory and run them from there. noexec makes that step fail and forces noisier workarounds. - How detected: Reads /proc/mounts. Each of /tmp, /var/tmp and /dev/shm that appears as a mount must include noexec; paths that are not separate mounts are skipped (HOST-FS-008 covers nodev and nosuid, HOST-FS-010 covers /var/tmp as a mount). - Fix: Add noexec to the mount options of each temporary filesystem (systemd tmp.mount drop-in or /etc/fstab) and remount. Some package managers need exec on /tmp during installs; set TMPDIR for them rather than removing noexec. - Verify: findmnt -no TARGET,OPTIONS /tmp /var/tmp /dev/shm - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-014 World-Writable Files Outside Temporary Directories - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N - What: Looks for files that any user can modify in program, service and home locations other than /etc (which HOST-FS-003 covers). - Why: A world-writable script, binary or data file lets any local account, including a compromised web application, change what other users and services run or read. - How detected: Walks /usr/local, /opt, /srv, /var/www, /home, /root, /usr/bin and /usr/sbin (metadata only, up to 20000 files per tree, symlinks not followed) and lists regular files with the 'other write' bit set. - Fix: For each listed file: chmod o-w . Then find the deployment step or chmod 777 habit that created it. - Verify: find /usr/local /opt /srv /var/www /home /usr/bin /usr/sbin -xdev -type f -perm -0002 should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-015 Files With No Known Owner or Group - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N - What: Looks for files whose owner or group id no longer appears in /etc/passwd or /etc/group. - Why: When an account is deleted, its files keep the numeric id. The next account created with that id silently inherits them, which can hand a new user old keys, scripts or cron jobs. - How detected: Reads the numeric ids from /etc/passwd and /etc/group, then walks /etc, /usr/local, /opt, /srv, /var/www, /home and /root (metadata only, up to 20000 files per tree, symlinks not followed) and lists files whose uid or gid is unknown. Unreadable account files make the check incomplete. - Fix: Decide who should own each listed file and apply chown : , or remove files that belonged to a deleted account after checking their contents. - Verify: find /etc /usr/local /opt /srv /var/www /home -xdev \( -nouser -o -nogroup \) should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-016 SUID or SGID Programs Outside the Known Baseline - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N - What: Lists set-user-ID and set-group-ID programs in the system binary directories that are not on the documented baseline of what distributions ship. - Why: Every SUID program is a door into another account, usually root. Programs such as nmap, vim, find, python or a custom helper with the SUID bit are a known route to instant root; the fewer unexplained SUID programs, the smaller that door. - How detected: Walks /bin, /sbin, /usr/bin, /usr/sbin, /usr/libexec and /usr/lib (metadata only, up to 20000 files per tree, symlinks not followed), keeps files with the SUID or SGID bit, and reports those whose file name is not in the baseline list kept in this check's source (passwd, sudo, su, mount, ping, ssh-keysign and about forty others). An empty inventory is reported as incomplete. - Fix: For each listed program decide whether the bit is needed. Remove it with chmod u-s,g-s or uninstall the package. Record the ones you keep so the next review is quick. Caution: programs such as passwd, sudo, su, mount and ping need their bit to work for normal users; removing it from those breaks password changes and administrator access. - Verify: find /bin /sbin /usr/bin /usr/sbin /usr/libexec /usr/lib -xdev -type f -perm /6000 and compare with your record. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-017 World-Writable Directories Without the Sticky Bit - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N - What: Looks for shared writable directories outside /tmp and /etc that let any user delete or replace other users' files. - Why: In a world-writable directory without the sticky bit, any user can remove or swap a file placed there by another user or by a service, which turns upload folders and spool directories into file-replacement attacks. - How detected: Lists directories up to three levels below /var, /home, /opt, /srv and /usr/local (metadata only, at most 5000 per tree, symlinks never treated as directories) and reports those writable by others without the sticky bit. HOST-FS-002 covers /tmp and /var/tmp; HOST-FS-003 covers /etc. - Fix: chmod +t for directories that must stay shared, or chmod o-w for those that should not be writable by everyone. - Verify: find /var /home /opt /srv /usr/local -xdev -type d -perm -0002 ! -perm -1000 should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-018 Kernel Symbol Map or Initial RAM Disk Readable by Others - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that /boot/System.map-* and the initial RAM disk images are not readable by every user. - Why: System.map lists every kernel symbol address, which defeats kptr_restrict and helps kernel exploits. An initial RAM disk can embed network credentials, disk-unlock key files or custom scripts that should not be world-readable. - How detected: Stats /boot/System.map-*, /boot/initrd.img-*, /boot/initramfs-*.img and /boot/initrd-* (metadata only) and fails if any is readable by others. Skipped when none exist (for example inside a container). - Fix: chmod 600 /boot/System.map-* /boot/initrd* /boot/initramfs* and, on Debian family systems, set UMASK=0077 in /etc/initramfs-tools/initramfs.conf so new images are created private. - Verify: stat -c '%a %n' /boot/System.map-* /boot/initrd* - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-019 Cron and At Not Restricted to Allowed Users - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that cron.allow and at.allow exist, are private to root, and that the deny lists are not relied upon instead. - Why: By default every local account may schedule jobs. A deny list only names known accounts; an allow list says exactly who may schedule, which is what least privilege asks for and what makes persistence by a compromised service account harder. - How detected: Stats /etc/cron.allow and /etc/at.allow (metadata only). For each scheduler that is installed (/etc/crontab or /etc/cron.d for cron, /etc/at.deny, /etc/at.allow or /var/spool/at for at), a missing allow file, an allow file not owned by root, or one readable or writable by group or others fails. A deny file present without an allow file is reported. - Fix: Create the allow lists: touch /etc/cron.allow /etc/at.allow && chmod 600 /etc/cron.allow /etc/at.allow && chown root:root /etc/cron.allow /etc/at.allow then add one user name per line for the accounts that need scheduling, and remove /etc/cron.deny and /etc/at.deny. Caution: once cron.allow exists, crontabs of users not listed stop running, so list every service and user account that has a crontab (ls /var/spool/cron/crontabs or /var/spool/cron) before creating it. - Verify: stat -c '%a %U %n' /etc/cron.allow /etc/at.allow; ls /etc/cron.deny /etc/at.deny 2>&1 - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-020 Cron Configuration Readable by Other Users - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that /etc/crontab is mode 600 and the cron.d, cron.hourly, cron.daily, cron.weekly and cron.monthly directories are mode 700, all owned by root. - Why: Readable cron configuration tells a local attacker exactly which scripts root runs and when, which is the reconnaissance step before replacing one of them. HOST-FS-005 stops writing; this check stops reading. - How detected: Stats /etc/crontab and the five cron directories (metadata only). Any group or other permission bit, or a non-root owner, fails. Paths that do not exist are skipped. - Fix: chown root:root /etc/crontab /etc/cron.* && chmod 600 /etc/crontab && chmod 700 /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly - Verify: stat -c '%a %U %n' /etc/crontab /etc/cron.* - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-021 Shell Profile Sets a Permissive File Creation Mask - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that system-wide shell start-up files do not set a umask that leaves new files readable by every user. - Why: A umask line in /etc/profile or /etc/bash.bashrc overrides the login.defs default for every interactive shell. Many distributions still ship umask 022 there, which makes every file an administrator creates readable by all local accounts. - How detected: Reads /etc/profile, /etc/bash.bashrc, /etc/bashrc, /etc/zsh/zshrc and /etc/profile.d/*.sh and finds lines of the form umask NNN. A value that leaves the 'other' read bit open (022, 002, 000) fails; 027 or stricter passes. Files that exist but could not be read fully make the check incomplete; when no shell file sets a umask the check is skipped (HOST-FS-009 covers login.defs). - Fix: Change every umask 022 line in the listed files to umask 027, or remove the line so the login.defs value applies. Users start a new login shell to pick it up. - Verify: grep -rn '^[[:space:]]*umask' /etc/profile /etc/bash.bashrc /etc/bashrc /etc/profile.d/ 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-FS-022 Account Database Files or Their Backups Not Owned and Locked by Root - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - What: Checks the owner and permissions of /etc/passwd, /etc/group, /etc/shadow, /etc/gshadow and their dash-suffixed backup copies. - Why: The tools that edit accounts leave a backup copy (passwd-, shadow-) next to the live file. A readable shadow- backup leaks the same password hashes as shadow itself, and a file not owned by root can be rewritten by whoever owns it. HOST-FS-001 checks the mode of the live files; this check adds ownership and the backups. - How detected: Stats the eight files (metadata only). passwd, group and their backups must be owned by uid 0 and not writable by group or others; shadow, gshadow and their backups must be owned by uid 0 and have no permission bits for others. Files that do not exist are skipped; none existing is reported as incomplete. - Fix: chown root:root /etc/passwd /etc/passwd- /etc/group /etc/group- && chmod 644 /etc/passwd /etc/group && chmod 600 /etc/passwd- /etc/group- ; chown root:shadow /etc/shadow /etc/shadow- /etc/gshadow /etc/gshadow- && chmod 640 /etc/shadow /etc/gshadow && chmod 600 /etc/shadow- /etc/gshadow- (use root:root and mode 000 on RHEL family systems). - Verify: stat -c '%a %U:%G %n' /etc/passwd* /etc/group* /etc/shadow* /etc/gshadow* - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/files_mounts.go ### HOST-IDS-001 No Brute-Force Protection Service - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that a log-based intrusion prevention service (fail2ban, sshguard or CrowdSec) is running. - Why: Internet-facing services receive constant guessing. Automatically blocking sources that fail repeatedly reduces noise and risk, and turns logs into active defence. - How detected: Asks systemd whether fail2ban, sshguard or crowdsec is active. - Fix: Install and enable one, for example: apt install fail2ban && systemctl enable --now fail2ban , with an sshd jail enabled. - Verify: systemctl is-active fail2ban - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-K8S-001 Kubelet Allows Anonymous or Unauthorised Access - Domain: host, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H - What: Checks the kubelet configuration for anonymous authentication, AlwaysAllow authorisation or the unauthenticated read-only port. - Why: The kubelet API can run commands in every pod on the node. With anonymous access or AlwaysAllow, anyone who can reach port 10250 controls the node's workloads; the read-only port 10255 leaks pod and environment details. - How detected: Reads /var/lib/kubelet/config.yaml and looks for authentication.anonymous.enabled: true, authorization.mode: AlwaysAllow, or a non-zero readOnlyPort. - Fix: Set authentication.anonymous.enabled: false, authorization.mode: Webhook and readOnlyPort: 0 in the kubelet configuration, then restart the kubelet. - Verify: grep -A2 -E 'anonymous|authorization|readOnlyPort' /var/lib/kubelet/config.yaml - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers.go ### HOST-K8S-002 Kubernetes Data Directory Accessible to Others - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N - What: Checks that the etcd data directory is accessible only by its owner. - Why: etcd stores every Kubernetes object, including Secrets, usually unencrypted at rest. Read access to its data directory exposes all cluster credentials. - How detected: Stats /var/lib/etcd and fails if group or others have any permission. - Fix: chmod 700 /var/lib/etcd && chown etcd:etcd /var/lib/etcd (or root:root for kubeadm static pods). Enable encryption at rest for Secrets. - Verify: stat -c '%a %U:%G %n' /var/lib/etcd - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/containers.go ### HOST-K8S-003 Kubernetes Credential Files Readable by Others - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - What: Checks that kubeconfig files and Kubernetes PKI private keys are not readable by other users. - Why: admin.conf and the cluster CA key grant full control of the cluster; any local user who can read them becomes a cluster administrator. - How detected: Stats every existing file matching /etc/kubernetes/admin.conf, /etc/kubernetes/super-admin.conf, /etc/kubernetes/kubelet.conf, /etc/kubernetes/controller-manager.conf, /etc/kubernetes/scheduler.conf, /etc/kubernetes/pki/*.key, /etc/kubernetes/pki/etcd/*.key (metadata only, contents are never read) and fails if it is readable or writable by group or others. - Fix: chmod 600 /etc/kubernetes/*.conf /etc/kubernetes/pki/*.key /etc/kubernetes/pki/etcd/*.key && chown root:root on each. - Verify: stat -c '%a %n' /etc/kubernetes/*.conf /etc/kubernetes/pki/*.key - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-KRN-001 Address Space Layout Randomisation Not Fully Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N - What: Checks that the kernel randomises memory layout for stack, libraries, heap and VDSO. - Why: ASLR makes memory-corruption exploits much harder because attackers cannot predict where code lives. - How detected: Reads /proc/sys/kernel/randomize_va_space directly (no command is run) and compares it with the hardened value 2. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.randomize_va_space = 2 then apply with: sysctl --system - Verify: sysctl kernel.randomize_va_space should print 2. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-002 Kernel Pointers Exposed to Unprivileged Users - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that kernel memory addresses are hidden from normal users. - Why: Leaked kernel addresses defeat kernel ASLR and are the first step of most local kernel exploits. - How detected: Reads /proc/sys/kernel/kptr_restrict directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.kptr_restrict = 1 then apply with: sysctl --system - Verify: sysctl kernel.kptr_restrict should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-003 Kernel Log Readable by Unprivileged Users - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that only privileged users can read the kernel log (dmesg). - Why: The kernel log can reveal addresses, hardware and driver details useful for local exploitation. - How detected: Reads /proc/sys/kernel/dmesg_restrict directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.dmesg_restrict = 1 then apply with: sysctl --system - Verify: sysctl kernel.dmesg_restrict should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-004 Setuid Programs May Dump Core - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that privileged programs do not write core dumps. - Why: A core dump of a setuid program can contain password hashes or keys and may be readable by the user who crashed it. - How detected: Reads /proc/sys/fs/suid_dumpable directly (no command is run) and compares it with the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: fs.suid_dumpable = 0 then apply with: sysctl --system - Verify: sysctl fs.suid_dumpable should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-005 IP Forwarding Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the server does not route packets between networks. - Why: A server that forwards packets can be used to pivot between networks. Only routers, VPN gateways and container hosts need this. - How detected: Reads /proc/sys/net/ipv4/ip_forward directly and compares it with the hardened value 0. When a container runtime or Kubernetes node is detected read-only (runtime sockets such as /var/run/docker.sock, /run/containerd/containerd.sock or /run/crio/crio.sock, /etc/kubernetes, or bridge interfaces such as docker0, cni0, cbr0 or flannel.1 in /sys/class/net), forwarding is required: the check then passes with context when the FORWARD path is filtered (iptables or nftables forward policy drop, or DROP/REJECT rules in DOCKER-USER), fails when forwarded traffic is accepted by default, and is incomplete when the policy cannot be read. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.ip_forward = 0 then apply with: sysctl --system - Verify: sysctl net.ipv4.ip_forward should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-006 ICMP Redirects Accepted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the kernel ignores ICMP redirect messages. - Why: Forged ICMP redirects let an attacker on the local network reroute the server's traffic through themselves. - How detected: Reads /proc/sys/net/ipv4/conf/*/accept_redirects directly (all, default for future interfaces, and every interface except loopback; no command is run) and combines the all value with each interface value as the kernel does. The kernel ignores redirects on an interface when both values are 0 or, on an interface that forwards packets (net.ipv4.ip_forward or the interface's forwarding setting), when either one is 0; any interface where redirects remain accepted fails. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.conf.all.accept_redirects = 0 then apply with: sysctl --system - Verify: sysctl net.ipv4.conf.all.accept_redirects should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-007 Source-Routed Packets Accepted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the kernel drops packets that dictate their own route. - Why: Source routing lets an attacker bypass network filtering and spoof trusted addresses. - How detected: Reads /proc/sys/net/ipv4/conf/all/accept_source_route directly (no command is run) and compares it with the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.conf.all.accept_source_route = 0 then apply with: sysctl --system - Verify: sysctl net.ipv4.conf.all.accept_source_route should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-008 TCP SYN Cookies Disabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - What: Checks that SYN cookies protect the server from SYN flood attacks. - Why: Without SYN cookies a simple flood of half-open connections can make every service unreachable. - How detected: Reads /proc/sys/net/ipv4/tcp_syncookies directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.tcp_syncookies = 1 then apply with: sysctl --system - Verify: sysctl net.ipv4.tcp_syncookies should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-009 Unprivileged eBPF Allowed - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - What: Checks that normal users cannot load eBPF programs into the kernel. - Why: Unprivileged eBPF has been the entry point for many local privilege-escalation bugs. - How detected: Reads /proc/sys/kernel/unprivileged_bpf_disabled directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.unprivileged_bpf_disabled = 1 then apply with: sysctl --system - Verify: sysctl kernel.unprivileged_bpf_disabled should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-010 Process Tracing Not Restricted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that processes cannot attach a debugger to other processes of the same user. - Why: Unrestricted ptrace lets any compromised process read the memory of others, for example to steal SSH agent keys or browser sessions. - How detected: Reads /proc/sys/kernel/yama/ptrace_scope directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.yama.ptrace_scope = 1 then apply with: sysctl --system - Verify: sysctl kernel.yama.ptrace_scope should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-011 Symlink Protection Disabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that the kernel blocks following symlinks owned by other users in world-writable sticky directories. - Why: Without it, classic /tmp symlink races let local users trick root-run programs into overwriting files. - How detected: Reads /proc/sys/fs/protected_symlinks directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: fs.protected_symlinks = 1 then apply with: sysctl --system - Verify: sysctl fs.protected_symlinks should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-012 Hardlink Protection Disabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that users cannot create hard links to files they do not own. - Why: Hard links to privileged files enable time-of-check races and keep vulnerable setuid binaries alive after updates. - How detected: Reads /proc/sys/fs/protected_hardlinks directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: fs.protected_hardlinks = 1 then apply with: sysctl --system - Verify: sysctl fs.protected_hardlinks should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-013 Reverse Path Filtering Disabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the kernel drops packets whose source address could not be routed back through the receiving interface. - Why: Reverse path filtering blocks many spoofed-source packets used in attacks and amplification. - How detected: Reads /proc/sys/net/ipv4/conf/*/rp_filter directly (all, default for future interfaces, and every interface except loopback; no command is run) and combines the all value with each interface value as the kernel does. The kernel uses the larger of the two values, so an interface passes when that larger value is 1 (strict) or 2 (loose). - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.conf.all.rp_filter = 1 then apply with: sysctl --system - Verify: sysctl net.ipv4.conf.all.rp_filter should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-014 Martian Packets Not Logged - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that packets with impossible source addresses are logged. - Why: Logging martian packets helps detect spoofing and misrouting attempts. - How detected: Reads /proc/sys/net/ipv4/conf/*/log_martians directly (all, default for future interfaces, and every interface except loopback; no command is run) and combines the all value with each interface value as the kernel does. The kernel logs martians on an interface when either value is 1, so an interface passes when all or the interface value is 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.conf.all.log_martians = 1 then apply with: sysctl --system - Verify: sysctl net.ipv4.conf.all.log_martians should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-015 Server Sends ICMP Redirects - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the server does not send ICMP redirects, which only routers should do. - Why: A non-router sending redirects can be abused to influence other hosts' routing on the local network. - How detected: Reads /proc/sys/net/ipv4/conf/*/send_redirects directly (all, default and every interface; no command is run) and fails if any of them differs from the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.conf.all.send_redirects = 0 then apply with: sysctl --system - Verify: sysctl net.ipv4.conf.all.send_redirects should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-016 Broadcast ICMP Echo Answered - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - What: Checks that the server ignores ping requests sent to broadcast addresses. - Why: Answering broadcast pings makes the server a participant in Smurf-style amplification attacks. - How detected: Reads /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.icmp_echo_ignore_broadcasts = 1 then apply with: sysctl --system - Verify: sysctl net.ipv4.icmp_echo_ignore_broadcasts should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-017 IPv6 ICMP Redirects Accepted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the kernel ignores IPv6 ICMP redirect messages. - Why: Forged IPv6 redirects let an attacker on the local network reroute traffic through themselves. - How detected: Reads /proc/sys/net/ipv6/conf/*/accept_redirects directly (all, default and every interface; no command is run) and fails if any of them differs from the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv6.conf.all.accept_redirects = 0 then apply with: sysctl --system - Verify: sysctl net.ipv6.conf.all.accept_redirects should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-018 Performance Events Open to Unprivileged Users - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that unprivileged users cannot use kernel performance events to observe the system. - Why: Unrestricted perf events expose kernel and other processes' activity and have been used in local exploits. - How detected: Reads /proc/sys/kernel/perf_event_paranoid directly (no command is run) and compares it with the hardened value 2. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.perf_event_paranoid = 2 then apply with: sysctl --system - Verify: sysctl kernel.perf_event_paranoid should print 2. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-019 Magic SysRq Key Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - What: Checks that the magic SysRq key combination is disabled or limited to keyboard synchronisation. - Why: With SysRq enabled, anyone at a console or with access to /proc/sysrq-trigger can reboot the machine, kill every process or dump memory without logging in. - How detected: Reads /proc/sys/kernel/sysrq directly (no command is run) and compares it with the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.sysrq = 0 then apply with: sysctl --system - Verify: sysctl kernel.sysrq should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-020 FIFO Protection in Shared Directories Disabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that the kernel refuses to open FIFOs in world-writable sticky directories that another user owns. - Why: A FIFO planted in /tmp can make a privileged program block or write into an attacker-controlled pipe, a classic step in local attacks. - How detected: Reads /proc/sys/fs/protected_fifos directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: fs.protected_fifos = 1 then apply with: sysctl --system - Verify: sysctl fs.protected_fifos should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-021 Regular File Protection in Shared Directories Disabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that the kernel refuses to open, with O_CREAT, regular files in world-writable sticky directories that another user owns. - Why: Without it, a user can pre-create a file that a root-run script later writes to in /tmp, redirecting privileged output into a file they control. - How detected: Reads /proc/sys/fs/protected_regular directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: fs.protected_regular = 1 then apply with: sysctl --system - Verify: sysctl fs.protected_regular should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-022 Unprivileged User Namespaces Allowed - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that normal users cannot create user namespaces, unless the server runs containers that need them. - Why: User namespaces give an unprivileged process a fake root inside a namespace, which exposes kernel code paths that were written for root only; many local kernel exploits start by unsharing a user namespace. - How detected: Reads /proc/sys/kernel/unprivileged_userns_clone (Debian and Ubuntu kernels) and, when that does not exist, /proc/sys/user/max_user_namespaces. The first is hardened at 0; the second at 0. Kernels with neither parameter are skipped. Passes with a note when /proc/sys/kernel/apparmor_restrict_unprivileged_userns is 1 (Ubuntu 23.10 and later), because AppArmor then denies the namespace capabilities to unconfined programs. Container hosts that need rootless namespaces should suppress this check deliberately. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing kernel.unprivileged_userns_clone = 0 (Debian family) or user.max_user_namespaces = 0 (other kernels), then apply with: sysctl --system. Rootless Docker, Podman, Flatpak and some browsers need user namespaces, so test first. - Verify: sysctl kernel.unprivileged_userns_clone user.max_user_namespaces - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel_depth.go ### HOST-KRN-023 IPv6 Source-Routed Packets Accepted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the kernel drops IPv6 packets that dictate their own route. - Why: IPv6 routing headers can be used to bypass filters and spoof trusted addresses, as source routing could in IPv4. - How detected: Reads /proc/sys/net/ipv6/conf/*/accept_source_route directly (all, default and every interface; no command is run) and fails if any of them differs from the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv6.conf.all.accept_source_route = 0 then apply with: sysctl --system - Verify: sysctl net.ipv6.conf.all.accept_source_route should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-024 Secure ICMP Redirects Accepted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the kernel also ignores ICMP redirects that come from its configured gateways. - Why: An attacker who spoofs the gateway address can still reroute traffic unless secure redirects are off as well; a server has no reason to accept redirects at all. - How detected: Reads /proc/sys/net/ipv4/conf/*/secure_redirects directly (all, default and every interface; no command is run) and fails if any of them differs from the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.conf.all.secure_redirects = 0 then apply with: sysctl --system - Verify: sysctl net.ipv4.conf.all.secure_redirects should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-025 IPv6 Router Advertisements Accepted - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: Checks that the server does not reconfigure its IPv6 addresses and routes from router advertisements. - Why: A rogue router advertisement on the local network can add a default route through an attacker's machine or assign addresses that bypass firewall rules; servers use static addressing. - How detected: Reads /proc/sys/net/ipv6/conf/*/accept_ra directly (all, default and every interface; no command is run) and fails if any of them differs from the hardened value 0. Passes with a note when IPv6 forwarding is off and /proc/net/ipv6_route shows a default route learned from a router advertisement (RTF_DEFAULT or RTF_ADDRCONF flag), because turning advertisements off would remove that route. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv6.conf.all.accept_ra = 0 then apply with: sysctl --system. Caution: on a host that gets its IPv6 address or default route from router advertisements (SLAAC, most cloud dual-stack VMs), accept_ra = 0 removes the IPv6 default route and cuts IPv6 connectivity. Use it only with static IPv6 addressing; otherwise keep accept_ra = 1 and rely on RA Guard on the switch or a host firewall rule for RA sources. - Verify: sysctl net.ipv6.conf.all.accept_ra should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-026 Bogus ICMP Error Responses Logged - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - What: Checks that the kernel silently ignores malformed ICMP error responses instead of logging each one. - Why: Logging every bogus ICMP error lets a remote sender fill the kernel log and disk at will, hiding real events among noise. - How detected: Reads /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses directly (no command is run) and compares it with the hardened value 1. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.ipv4.icmp_ignore_bogus_error_responses = 1 then apply with: sysctl --system - Verify: sysctl net.ipv4.icmp_ignore_bogus_error_responses should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-027 Kexec Kernel Replacement Allowed - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:L - What: Checks that a running kernel cannot be replaced at run time with kexec. - Why: kexec lets root boot a different kernel without going through the firmware, which bypasses Secure Boot and module signing and is used to load unsigned kernels after a compromise. - How detected: Reads /proc/sys/kernel/kexec_load_disabled directly (no command is run) and compares it with the hardened value 1. Passes with a note when the allow-listed systemctl is-enabled reports kdump or kdump-tools enabled, because kdump needs kexec to load its crash kernel. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.kexec_load_disabled = 1 then apply with: sysctl --system. Caution: kexec_load_disabled = 1 also stops kdump from loading its crash kernel (kdump.service on the RHEL family, kdump-tools on Debian and Ubuntu), so crash dumps are lost. Keep it 0 where kdump is used, or accept that trade-off deliberately. The setting cannot be turned back off without a reboot. - Verify: sysctl kernel.kexec_load_disabled should print 1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-028 Unprivileged userfaultfd Allowed - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that only privileged processes can use userfaultfd. - Why: userfaultfd lets a process pause the kernel at a chosen moment, which has turned many kernel race conditions into reliable local exploits. - How detected: Reads /proc/sys/vm/unprivileged_userfaultfd directly (no command is run) and compares it with the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: vm.unprivileged_userfaultfd = 0 then apply with: sysctl --system. Caution: unprivileged live migration tools (for example QEMU post-copy migration or CRIU run as a normal user) need userfaultfd; test those first. - Verify: sysctl vm.unprivileged_userfaultfd should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-029 TTY Line Discipline Autoloading Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that unprivileged users cannot make the kernel load terminal line discipline modules. - Why: Automatically loaded line disciplines have carried several exploitable kernel bugs; disabling autoload shrinks the kernel attack surface reachable from a normal shell. - How detected: Reads /proc/sys/dev/tty/ldisc_autoload directly (no command is run) and compares it with the hardened value 0. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: dev.tty.ldisc_autoload = 0 then apply with: sysctl --system - Verify: sysctl dev.tty.ldisc_autoload should print 0. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-030 BPF JIT Hardening Off - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that the BPF just-in-time compiler blinds constants in generated code for every user. - Why: Without hardening, attacker-chosen constants in BPF programs land in executable kernel memory, a known technique for building kernel exploit payloads. - How detected: Reads /proc/sys/net/core/bpf_jit_harden directly (no command is run) and compares it with the hardened value 2. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: net.core.bpf_jit_harden = 2 then apply with: sysctl --system - Verify: sysctl net.core.bpf_jit_harden should print 2. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-KRN-031 io_uring Open to Unprivileged Users - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that io_uring is disabled, or limited to privileged processes, on kernels that offer the setting. - Why: io_uring has produced a long run of local privilege-escalation bugs; several large operators disable it for untrusted workloads. - How detected: Reads /proc/sys/kernel/io_uring_disabled directly (no command is run) and compares it with the hardened value 2. - Fix: Create /etc/sysctl.d/60-openvapt-hardening.conf containing: kernel.io_uring_disabled = 2 then apply with: sysctl --system. Caution: applications that use io_uring for fast I/O (some databases, proxies and runtimes) fall back or fail to start when it is disabled; test them first, or use 1 to allow only a chosen group. - Verify: sysctl kernel.io_uring_disabled should print 2. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/kernel.go ### HOST-LOG-001 System Logging Not Persistent - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that system logs survive a reboot (rsyslog/syslog-ng running, or a persistent journal). - Why: Without persistent logs there is no evidence after an incident: no record of logins, sudo use, or service errors. - How detected: Checks whether rsyslog or syslog-ng is active, or whether /var/log/journal exists (systemd persistent journal). - Fix: Either install and enable rsyslog, or make the journal persistent: mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal && systemctl restart systemd-journald - Verify: ls -d /var/log/journal or systemctl is-active rsyslog - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/logging_time.go ### HOST-LOG-002 Security Audit Daemon Not Running - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks whether the Linux audit daemon (auditd) is running. - Why: auditd records security-relevant events (changes to /etc/passwd, privilege use, file deletions) that ordinary logs miss, which incident responders rely on. - How detected: Asks systemd whether the auditd unit is active. - Fix: Install the audit package (apt install auditd, or dnf install audit), enable it (systemctl enable --now auditd) and load a rule set appropriate to your policy. - Verify: systemctl is-active auditd should print 'active'. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/logging_time.go ### HOST-LOG-003 Security Logs Readable by All Users - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that authentication and system logs are not world-readable. - Why: Authentication logs contain user names, source addresses and sometimes passwords typed into the user name field. Only administrators should read them. - How detected: Stats every existing file matching /var/log/auth.log, /var/log/secure, /var/log/syslog, /var/log/messages (metadata only, contents are never read) and fails if it is readable or writable by others. - Fix: chmod o-rwx /var/log/auth.log /var/log/secure /var/log/syslog /var/log/messages and set the matching create mode in /etc/logrotate.d. - Verify: stat -c '%a %n' /var/log/auth.log /var/log/secure - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-LOG-004 Log Rotation Not Configured - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - What: Checks that logrotate (or journald size limits) keeps logs from filling the disk. - Why: Unrotated logs eventually fill the disk and stop services, and an attacker can deliberately flood logs to cause that or to hide activity. - How detected: Checks for /etc/logrotate.conf, or a SystemMaxUse setting in /etc/systemd/journald.conf. - Fix: Install logrotate (apt install logrotate / dnf install logrotate) and keep its daily timer enabled, or set SystemMaxUse= in journald.conf. - Verify: ls /etc/logrotate.conf ; systemctl is-active logrotate.timer - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-MAC-001 No Mandatory Access Control Enforced - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that AppArmor or SELinux is enabled and enforcing. - Why: Mandatory access control confines services even when they run as root, so a compromised web server or database cannot freely read or change the rest of the system. - How detected: Reads /sys/fs/selinux/enforce (1 means enforcing) and /sys/module/apparmor/parameters/enabled (Y means enabled). - Fix: Ubuntu/Debian: keep AppArmor enabled (systemctl enable --now apparmor) and its profiles in enforce mode. RHEL family: set SELINUX=enforcing in /etc/selinux/config and relabel. Test applications in permissive or complain mode first. - Verify: getenforce or aa-status - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-OS-001 Operating System Past End of Security Support - Domain: host, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether the Linux distribution release still receives free security updates. - Why: Once a release reaches end of life, newly discovered vulnerabilities are never fixed. Every later CVE in the kernel, OpenSSL or the web server stays open forever. - How detected: Reads ID and VERSION_ID from /etc/os-release and looks them up in a dated table of free vendor security-support dates, including Debian LTS (table date 2026-10-13). Releases older than the oldest table row for their distribution fail. Releases within 90 days of end of life also fail, so there is time to plan the upgrade. - Fix: Plan an upgrade to a supported release (for example do-release-upgrade on Ubuntu, or a fresh install and migration). Paid extended support (Ubuntu Pro, RHEL ELS) is an alternative while migrating. - Verify: cat /etc/os-release and compare with the vendor lifecycle page. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/os_patch.go ### HOST-PAM-001 Password Quality Module Not Enforced - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that new passwords pass through pam_pwquality with its dictionary check on, so dictionary words and user-name variants are rejected. - Why: NIST SP 800-63B-4 asks verifiers to reject passwords found in dictionaries and blocklists rather than to impose composition rules. pam_pwquality does that work on Linux, but only when the password stack actually calls it and its dictcheck and usercheck options are not switched off. A minlen in pwquality.conf (HOST-PWD-003) does nothing if the module is never invoked. - How detected: Reads /etc/pam.d/common-password, /etc/pam.d/system-auth and /etc/pam.d/password-auth for an active pam_pwquality (or pam_passwdqc or pam_cracklib) line, then reads dictcheck, usercheck and enforcing from /etc/security/pwquality.conf.d/*.conf and /etc/security/pwquality.conf and from the module line. A missing module, dictcheck=0, usercheck=0 or enforcing=0 fails. Skipped when no password stack exists; Incomplete when one exists but cannot be read. - Fix: Install libpam-pwquality (Debian/Ubuntu) or libpwquality (RHEL family). Make sure the password stack has password requisite pam_pwquality.so retry=3 before pam_unix (pam-auth-update or authselect enable-feature with-pwquality), and in /etc/security/pwquality.conf keep dictcheck = 1 and usercheck = 1 . Pair it with a breached-password blocklist where possible. Keep a root session open and test a login and a password change in a second session before closing it: a wrong PAM line can lock every user out. - Verify: grep pam_pwquality /etc/pam.d/common-password /etc/pam.d/system-auth 2>/dev/null; grep -rE '^(dictcheck|usercheck|enforcing)' /etc/security/pwquality.conf /etc/security/pwquality.conf.d/ - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PAM-002 Account Lockout Releases Too Quickly - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that a locked account stays locked for at least 15 minutes (or until an administrator unlocks it). - Why: This check and HOST-PWD-004 split one control so a single root cause is never counted twice: HOST-PWD-004 covers whether lockout exists and its deny threshold (how many failures lock the account); this check covers only unlock_time (how long the lock lasts). How long the lock lasts decides whether it slows an attacker: pam_faillock's default unlock_time of 600 seconds lets guessing resume every ten minutes. Fifteen minutes or more, or 0 (manual unlock), makes online guessing impractical. - How detected: Reads unlock_time from the pam_faillock lines of /etc/pam.d/common-auth, /etc/pam.d/system-auth and /etc/pam.d/password-auth, then from /etc/security/faillock.conf, else uses the module default of 600. A value of 0 or at least 900 passes. No pam_faillock at all is skipped, because HOST-PWD-004 already reports the missing lockout. Skipped when no authentication stack exists; Incomplete when one exists but cannot be read. - Fix: In /etc/security/faillock.conf set unlock_time = 900 (or 0 to require an administrator to run faillock --reset), keeping deny between 3 and 10. Keep a root session open and test in a second session before closing the first: a wrong PAM line can lock every user out, and unlock_time = 0 means locked accounts, administrators included, wait for faillock --reset. - Verify: grep -E 'unlock_time' /etc/security/faillock.conf /etc/pam.d/common-auth /etc/pam.d/system-auth 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PAM-003 su Not Restricted to an Administrative Group - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N - What: Checks that only members of a named group (wheel or sudo) may use su to become another user. - Why: Without pam_wheel, every local account, including a compromised web service user, can run su and start guessing the root password. Restricting su to an administrative group removes that path and leaves sudo, which logs each command, as the way to gain privilege. - How detected: Reads /etc/pam.d/su for an active line auth required pam_wheel.so (any control value, with or without use_uid and group=). Skipped when the file does not exist; Incomplete when it cannot be read. - Fix: In /etc/pam.d/su uncomment or add auth required pam_wheel.so use_uid group=sudo (group=wheel on the RHEL family), and make sure the group contains only administrators. Caution: add every administrator who uses su to that group first and test from a second session, or they lose su access. - Verify: grep -E '^auth\s+required\s+pam_wheel.so' /etc/pam.d/su - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PAM-004 Password History Not Enforced - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that PAM remembers at least the last 5 passwords so a changed password cannot be changed straight back. - Why: When a password must be changed after a suspected compromise, the point is lost if the user can set the old one again. pam_pwhistory (or pam_unix remember=) keeps recent hashes and refuses reuse. Five is a common minimum. - How detected: Reads /etc/pam.d/common-password, /etc/pam.d/system-auth and /etc/pam.d/password-auth for remember=N on a pam_pwhistory or pam_unix line, or remember in /etc/security/pwhistory.conf. N of 5 or more passes. Skipped when no password stack exists; Incomplete when one exists but cannot be read. - Fix: Add password required pam_pwhistory.so remember=5 use_authtok before pam_unix in the password stack (pam-auth-update on Debian/Ubuntu, authselect enable-feature with-pwhistory on the RHEL family), or set remember = 5 in /etc/security/pwhistory.conf. Keep a root session open and test a password change in a second session before closing it: a wrong PAM line can block password changes or logins. - Verify: grep -E 'remember' /etc/pam.d/common-password /etc/pam.d/system-auth /etc/security/pwhistory.conf 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PAM-005 Minimum Password Age Not Set - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that a password cannot be changed again on the same day it was set. - Why: Password history (HOST-PAM-004) only works if users cannot cycle through several changes in a minute to get back to a favourite password. PASS_MIN_DAYS 1 closes that loop with no real cost to users. - How detected: Reads PASS_MIN_DAYS from /etc/login.defs; 1 or more passes. An unset value fails, because the built-in default does not meet the requirement. Skipped when the file does not exist; Incomplete when it cannot be read. - Fix: Set PASS_MIN_DAYS 1 in /etc/login.defs, and for existing accounts chage --mindays 1 . - Verify: grep '^PASS_MIN_DAYS' /etc/login.defs - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PAM-006 Password Expiry Warning Too Short - Domain: host, severity: info, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Reports whether users are warned at least 7 days before a password expires. - Why: Where passwords do expire, a short or missing warning means users are locked out without notice and support staff reset passwords under pressure, which is when weak temporary passwords and social engineering succeed. This is informational: with no expiry (HOST-PWD-005) the warning never fires. - How detected: Reads PASS_WARN_AGE from /etc/login.defs; 7 or more passes. An unset value fails, because the built-in default does not meet the requirement. Skipped when the file does not exist; Incomplete when it cannot be read. - Fix: Set PASS_WARN_AGE 7 in /etc/login.defs, and for existing accounts chage --warndays 7 . - Verify: grep '^PASS_WARN_AGE' /etc/login.defs - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PAM-007 Inactive Accounts Never Locked - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that new accounts are set to lock automatically after a password has been expired and unused for at most 30 days. - Why: Accounts of people who left, or test accounts nobody remembers, stay usable for years unless something disables them. The INACTIVE default in /etc/default/useradd makes new accounts lock themselves a fixed number of days after their password expires. - How detected: Reads INACTIVE from /etc/default/useradd. A value from 0 to 30 passes; -1 (never) or an unset value fails, because useradd's built-in default is -1. A missing file also fails for the same reason; Incomplete when the file cannot be read. - Fix: useradd -D -f 30 (writes INACTIVE=30 to /etc/default/useradd), and for existing accounts chage --inactive 30 . Caution: this locks accounts whose password has expired for 30 days, so exempt break-glass and rarely used administrator accounts or keep their passwords current. - Verify: useradd -D | grep INACTIVE - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PAM-008 Root PATH Contains Unsafe Entries - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H - What: Checks that the PATH set for root does not include the current directory, empty entries, relative paths or directories other users can write to. - Why: When root's PATH contains . or an empty element, typing ls in a directory an attacker controls runs the attacker's ls as root. A world-writable directory on the PATH has the same effect for every command name that is not found earlier. This is one of the oldest Unix privilege escalations and still common in copied dotfiles. - How detected: Reads PATH= assignments from /root/.bashrc, /root/.profile, /root/.bash_profile, /etc/environment, /etc/profile and /etc/profile.d/*.sh. Entries that are ., empty (a leading, trailing or doubled colon) or relative fail; absolute directories are stat-ed (metadata only) and fail if world-writable or not owned by root. Entries that expand variables ($PATH, $HOME) are not judged. Incomplete when no readable file sets PATH. - Fix: Edit the listed file so PATH holds only absolute, root-owned directories, for example PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" , remove any . or trailing colon, and chmod o-w any listed directory. Log out and in again. - Verify: sudo -i sh -c 'echo $PATH' should show no . or empty element; ls -ld $(echo $PATH | tr : ' ') - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PAM-009 No Shell Idle Timeout - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N - What: Checks that interactive shells log out after at most 15 minutes without input (TMOUT). - Why: A root shell left open on an unattended terminal, or a forgotten SSH session on a shared workstation, is free privilege for whoever sits down next. TMOUT makes bash and ksh close an idle session automatically; readonly stops users from switching it off. - How detected: Reads /etc/profile, /etc/profile.d/*.sh, /etc/bash.bashrc and /etc/bashrc for a TMOUT=N assignment (optionally readonly, export, typeset -x or declare -x). N from 1 to 900 passes; 0, a larger value or no assignment fails. Incomplete when none of the files can be read. - Fix: Create /etc/profile.d/99-tmout.sh with TMOUT=900 ; readonly TMOUT ; export TMOUT (mode 644, owned by root). Sessions that must stay open should use a multiplexer such as tmux with its own lock, not a longer TMOUT. Caution: a readonly TMOUT ends idle interactive shells, including foreground jobs left waiting in them; run long jobs under nohup, systemd-run or a multiplexer. - Verify: grep -rE '^\s*(readonly |export )?TMOUT=' /etc/profile /etc/profile.d/ /etc/bash.bashrc /etc/bashrc 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pam_login.go ### HOST-PKG-001 Security Updates Waiting to Be Installed - Domain: host, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks the package manager for published security updates that are not installed yet. - Why: Most real-world compromises use vulnerabilities for which a fix was already available. Pending security updates are known, public weaknesses. - How detected: Runs the package manager in simulation mode only (apt-get -s upgrade, or dnf/yum updateinfo --security). Nothing is downloaded or installed. Lines that come from a security repository are counted. - Fix: Debian/Ubuntu: apt-get update && apt-get upgrade . RHEL family: dnf upgrade --security . Consider unattended-upgrades or dnf-automatic for security patches. - Verify: Re-run openvapt; this check should pass. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/os_patch.go ### HOST-PKG-002 Repository Signature Checks Disabled - Domain: host, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks that the package manager verifies repository signatures, so a tampered mirror or intercepted download cannot install code as root. - Why: Package installation runs as root. apt and dnf verify a signature over the repository index before trusting any package in it; an entry marked trusted=yes, AllowUnauthenticated or gpgcheck=0 switches that off, and a mirror operator or anyone on the network path can then deliver a modified package at the next update. - How detected: apt: reads /etc/apt/sources.list, /etc/apt/sources.list.d/*.list (entries with [trusted=yes]) and *.sources (Trusted: yes), plus /etc/apt/apt.conf and /etc/apt/apt.conf.d/* for AllowUnauthenticated or AllowInsecureRepositories set to true. dnf and yum: reads /etc/yum.repos.d/*.repo, /etc/dnf/dnf.conf and /etc/yum.conf for gpgcheck=0 in any section. Skipped when no repository files exist; Incomplete when some exist but cannot be read. - Fix: Remove trusted=yes (or Trusted: yes) from the listed source, install the repository's signing key under /etc/apt/keyrings and reference it with signed-by=; remove AllowUnauthenticated and AllowInsecureRepositories settings. On dnf and yum set gpgcheck=1 (and repo_gpgcheck=1 where supported) in each listed repository and import its key with rpm --import. - Verify: grep -r 'trusted=yes' /etc/apt/ ; grep -r 'gpgcheck=0' /etc/yum.repos.d/ /etc/dnf/dnf.conf 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pkg_depth.go ### HOST-PKG-003 Third-Party Package Repositories Present - Domain: host, severity: info, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Lists package repositories that are not run by the distribution vendor, so each one can be justified. - Why: Every repository can install software as root at the next update, so each one is a supplier you trust completely. Personal package archives, vendor repositories and forgotten test repositories are a common way for unpatched or abandoned software to stay on a server. This is informational: a third-party repository is a decision to review, not a defect by itself. - How detected: Reads /etc/apt/sources.list, /etc/apt/sources.list.d/*.list and *.sources, and /etc/yum.repos.d/*.repo (baseurl, mirrorlist, metalink), and lists repository host names that do not end in a known distribution domain (ubuntu.com, canonical.com, debian.org, fedoraproject.org, centos.org, redhat.com, rockylinux.org, almalinux.org, oracle.com, amazonaws.com, opensuse.org, suse.com, raspberrypi.com). Only host names are reported. - Fix: For each listed repository, record who runs it and why it is needed; remove the ones nobody can justify (delete the file under /etc/apt/sources.list.d or /etc/yum.repos.d and apt-get update or dnf clean all). Keep the rest pinned to the packages they are for (apt preferences, dnf includepkgs). - Verify: ls /etc/apt/sources.list.d/ /etc/yum.repos.d/ 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pkg_depth.go ### HOST-PKG-004 Many Old Kernels Retained - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - What: Checks that no more than three kernel versions are installed. - Why: Every installed kernel stays bootable from the boot menu, with every vulnerability it had. Old kernels also fill /boot until updates fail to install, which is a quiet way to stop receiving kernel patches. Keeping the running kernel plus one or two fallbacks is enough. - How detected: Counts kernel versions among installed packages: linux-image- packages whose /var/lib/dpkg/status entry reads 'install ok installed' on Debian and Ubuntu (the /var/lib/dpkg/info/*.list files are used only when the status file cannot be read, because removed but not purged packages keep them), kernel and kernel-core from the allow-listed rpm -qa on the RHEL family. More than three fails. Skipped when no kernel package is installed (containers); Incomplete when no package database could be read. - Fix: Debian/Ubuntu: apt-get autoremove --purge removes kernels outside the keep list. RHEL family: set installonly_limit=3 in /etc/dnf/dnf.conf and run dnf remove --oldinstallonly . Never remove the running kernel (uname -r). - Verify: dpkg -l 'linux-image-*' | grep '^ii' or rpm -q kernel kernel-core - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pkg_depth.go ### HOST-PKG-005 Running Kernel Older Than Installed Kernel - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - What: Checks that the kernel in memory is the newest kernel installed, so kernel security fixes are actually in effect. - Why: A kernel update protects nothing until the machine boots into it. The package manager reports the update as installed, so the gap is invisible unless the running version is compared with the installed one. HOST-UPD-002 reads the Debian reboot marker; this check works on every distribution by comparing versions. - How detected: Runs the allow-listed command uname -r and compares it with the installed kernel versions (linux-image- packages, or kernel and kernel-core from rpm -qa). If the newest installed version is newer than the running one, the check fails. Incomplete when the running version or the package list cannot be read; skipped when no kernel package is installed. - Fix: Schedule a maintenance window and reboot into the newest kernel. Where downtime is a problem, use the distribution's live patching service and still plan regular reboots. - Verify: uname -r should match the newest version in dpkg -l 'linux-image-*' | grep ^ii or rpm -q kernel-core . - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pkg_depth.go ### HOST-PKG-006 Package Lists Not Refreshed Automatically - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that the package manager downloads fresh repository metadata on a schedule, so pending updates can be seen at all. - Why: apt and dnf only know about updates listed in the metadata they last downloaded. Without a timer that refreshes it, HOST-PKG-001 and the administrator's own apt list --upgradable both report a clean system that is weeks behind. The refresh is read-only and separate from installing anything. - How detected: apt: reads /etc/apt/apt.conf.d/* for APT::Periodic::Update-Package-Lists "1" or asks the allow-listed systemctl is-enabled whether apt-daily.timer is enabled. dnf: asks whether dnf-makecache.timer is enabled. Skipped when neither package manager is present; Incomplete when apt configuration cannot be read. - Fix: Debian/Ubuntu: create /etc/apt/apt.conf.d/20auto-upgrades with APT::Periodic::Update-Package-Lists "1"; (dpkg-reconfigure unattended-upgrades writes it) and systemctl enable apt-daily.timer . RHEL family: systemctl enable --now dnf-makecache.timer . - Verify: systemctl is-enabled apt-daily.timer or systemctl is-enabled dnf-makecache.timer - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/pkg_depth.go ### HOST-PWD-001 Weak Password Hashing Algorithm Configured - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N - What: Checks that new passwords are hashed with a modern, slow algorithm (SHA-512 or yescrypt). - Why: If the shadow file ever leaks, fast or obsolete hashes such as MD5 or DES can be cracked in minutes. Slow, salted algorithms make offline cracking far more expensive. - How detected: Reads ENCRYPT_METHOD from /etc/login.defs. SHA512 and YESCRYPT pass; MD5, DES, SHA256 or an unset value on systems that default to weaker hashing fail. - Fix: Set ENCRYPT_METHOD YESCRYPT (or SHA512 on older systems) in /etc/login.defs, and make sure pam_unix uses the same algorithm. Ask users to change passwords so existing hashes are upgraded. - Verify: grep -i '^ENCRYPT_METHOD' /etc/login.defs - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-PWD-002 Accounts With Obsolete Password Hashes - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N - What: Checks /etc/shadow for passwords still stored with MD5 or traditional DES crypt. - Why: Old hashes survive configuration changes until the user changes the password. MD5-crypt and DES hashes can be cracked quickly with commodity hardware. - How detected: Reads /etc/shadow (needs root) and inspects only the hash prefix of each password field: $1$ (MD5) or a 13-character field without a $ prefix (DES) fail. The hash itself is never copied. - Fix: Configure a modern algorithm (HOST-PWD-001), then force a password change for each listed account (chage -d 0 ) or lock unused accounts (passwd -l ). - Verify: Re-run this check; no accounts should be listed. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-PWD-003 Short Minimum Password Length - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that the password quality policy requires at least 15 characters. - Why: Length is the single most effective property against guessing. NIST SP 800-63B-4 (2025) requires at least 15 characters for a password used as the only authentication factor, and recommends allowing long passphrases. - How detected: Reads minlen from /etc/security/pwquality.conf and /etc/security/pwquality.conf.d/*.conf. A value of 15 or more passes; a value below 15, or no pwquality configuration, fails. - Fix: Install libpam-pwquality (Debian/Ubuntu) or libpwquality (RHEL family) and set minlen = 15 (or more) in /etc/security/pwquality.conf. - Verify: grep -r '^minlen' /etc/security/pwquality.conf /etc/security/pwquality.conf.d/ - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-PWD-004 No Account Lockout After Failed Logins - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks that PAM temporarily locks accounts after repeated failed logins. - Why: Without lockout, local and PAM-backed remote logins can be guessed indefinitely. A temporary lock after a few failures slows attacks and makes them visible. This check covers whether lockout exists and its deny threshold; HOST-PAM-002 covers only how long the lock lasts (unlock_time), so one missing lockout is reported once, here. - How detected: Reads the PAM authentication stacks (/etc/pam.d/common-auth, /etc/pam.d/system-auth, /etc/pam.d/password-auth) and requires pam_faillock in both the preauth and authfail positions (or pam_tally2) with deny set to 10 or fewer failures (pam_faillock defaults to 3; deny is read from the module line or /etc/security/faillock.conf). - Fix: Enable pam_faillock (authselect enable-feature with-faillock on RHEL family; pam-auth-update or a faillock profile on Debian/Ubuntu) with settings such as deny=5 unlock_time=900 in /etc/security/faillock.conf. Keep a root session open and test a login in a second session before closing it: a wrong PAM line can lock every user out, including administrators. Do not lock root (no even_deny_root) unless console access is assured. - Verify: grep -E 'pam_faillock|pam_tally2' /etc/pam.d/common-auth /etc/pam.d/system-auth - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-PWD-005 Forced Periodic Password Rotation Configured - Domain: host, severity: info, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Reports whether new accounts are forced to change passwords on a short fixed schedule. - Why: NIST SP 800-63B-4 (section 3.1.1.2) says verifiers should not require periodic password changes, and should force a change only on evidence of compromise. Frequent forced rotation pushes users toward predictable variations. CIS Controls v8 safeguard 5.2 asks for unique passwords with a minimum length (8 characters where multi-factor authentication is used, 14 where it is not) and does not ask for periodic changes. This finding is informational: a short PASS_MAX_DAYS is not a vulnerability, but it is worth reviewing against current guidance. - How detected: Reads PASS_MAX_DAYS from /etc/login.defs. A value below 365 days is reported. Unset, 99999 (no expiry) or 365 and above passes. - Fix: Prefer long passphrases, a breached-password blocklist and multi-factor authentication over forced rotation. If policy allows, set PASS_MAX_DAYS 365 or 99999 in /etc/login.defs and change passwords when compromise is suspected. - Verify: grep '^PASS_MAX_DAYS' /etc/login.defs - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-SEC-001 Secrets in World-Readable Files - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N - What: Looks inside world-readable configuration files under /etc and the web roots for private keys, cloud access keys and password assignments. - Why: A password or key in a file every local account can read is available to every website, service account and compromised process on the machine. Secrets found this way are the usual first step from a small web compromise to the database, the cloud account or root. - How detected: Walks /etc and each web document root (metadata only, symlinks not followed), picks regular files that are readable by others and have a configuration-like name (.env*, *.conf, *.ini, *.yml, *.json, *.pem, *.key, id_*, up to 300 files of at most 256 KiB), reads them without following symlinks and matches private key headers, AWS access key ids and credential assignments (password, secret, API key or token settings with a literal value). The report names the file and the kind of match only; values are never copied. - Fix: chmod o-r each listed file (640 with the service's group, or 600). Move secrets into root-only files, systemd credentials or a secrets manager, and rotate every key or password that was readable. - Verify: find /etc /var/www -type f -perm -0004 -name '.env*' -o -type f -perm -0004 -name '*.key' then re-run this check. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hygiene.go ### HOST-SSH-001 SSH Allows Direct Root Login - Domain: host, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether the SSH daemon accepts logins as root with a password. - Why: 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 detected: 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. - 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 - Verify: sshd -T | grep -i permitrootlogin should print 'permitrootlogin no'. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/ssh_root_login.go ### HOST-SSH-002 SSH Password Authentication Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N - What: Checks whether SSH accepts passwords instead of only keys. - Why: Internet-facing SSH receives constant automated password guessing. Public-key authentication cannot be guessed, phished as easily, or reused from another breached site. - How detected: Reads PasswordAuthentication and KbdInteractiveAuthentication (ChallengeResponseAuthentication on older versions) from the effective sshd configuration. Either set to 'yes' fails. A risky value set inside a Match block in the config files fails as a conditional finding, with the Match condition in the evidence. - Fix: Install your public key for each admin user (ssh-copy-id), confirm key login works in a second session, then set PasswordAuthentication no and KbdInteractiveAuthentication no in /etc/ssh/sshd_config and reload sshd. - Verify: sshd -T | grep -Ei 'passwordauthentication|kbdinteractive' should show 'no' for both. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/ssh_password_auth.go ### HOST-SSH-003 SSH Permits Empty Passwords - Domain: host, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether SSH lets accounts with an empty password log in. - Why: Any account without a password becomes an open door to anyone on the network. - How detected: Reads PermitEmptyPasswords from the effective sshd configuration; 'yes' fails. A risky value set inside a Match block in the config files fails as a conditional finding, with the Match condition in the evidence. - Fix: Set PermitEmptyPasswords no in /etc/ssh/sshd_config, reload sshd, and give every account a password or lock it (passwd -l ). - Verify: sshd -T | grep -i permitemptypasswords should print 'no'. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/ssh_empty_passwords.go ### HOST-SSH-004 SSH Offers Weak Cryptography - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks the SSH ciphers, MACs, key exchange and host key algorithms for outdated choices. - Why: CBC-mode ciphers, RC4, MD5/SHA-1 MACs and 1024-bit Diffie-Hellman have known practical weaknesses. Offering them lets a network attacker downgrade or weaken the session. - How detected: Reads ciphers, macs, kexalgorithms and hostkeyalgorithms from sshd -T and flags any algorithm whose name contains a known-weak fragment, plus the SHA-1 based ssh-rsa host key signature algorithm. - Fix: Remove the listed algorithms. On OpenSSH 8.x and later the defaults are already strong: delete any custom Ciphers/MACs/KexAlgorithms lines, or set explicit modern lists such as Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com then reload sshd. - Verify: sshd -T | grep -Ei '^(ciphers|macs|kexalgorithms)' should list no cbc, arcfour, md5 or sha1 entries, and hostkeyalgorithms should not list ssh-rsa. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/ssh_weak_crypto.go ### HOST-SSH-005 SSH X11 Forwarding Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N - What: Checks whether SSH forwards graphical X11 sessions. - Why: Servers rarely need X11. Forwarding it exposes the connecting user's display to anyone with root on the server, and widens the attack surface of sshd. - How detected: Reads X11Forwarding from the effective sshd configuration; 'yes' fails. A risky value set inside a Match block in the config files fails as a conditional finding, with the Match condition in the evidence. - Fix: Set X11Forwarding no in /etc/ssh/sshd_config and reload sshd. - Verify: sshd -T | grep -i x11forwarding should print 'no'. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/ssh_misc.go ### HOST-SSH-006 SSH Allows Many Authentication Attempts - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether MaxAuthTries is above 4. - Why: Each connection may try this many passwords or keys. A low limit slows guessing and makes attacks show up in the logs sooner. - How detected: Reads MaxAuthTries (OpenSSH default 6); a value above 4 fails. - Fix: Set MaxAuthTries 4 (or lower) in /etc/ssh/sshd_config and reload sshd. Consider fail2ban or sshguard for rate limiting. - Verify: sshd -T | grep -i maxauthtries should print 4 or lower. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/ssh_misc.go ### HOST-SSH-007 SSH Forwarding Features Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N - What: Checks effective SSH Match contexts for TCP, agent, tunnel and gateway forwarding. - Why: Forwarding can turn an SSH account into a route to protected services or expose a user's authentication agent. - How detected: Evaluates sshd -T for root and an unprivileged user from loopback and a representative remote address. Fails when TCP or agent forwarding is enabled (both default to yes), PermitTunnel is yes, point-to-point or ethernet, or GatewayPorts is yes or clientspecified (the defaults for the last two are no). A risky value set inside a Match block in the config files fails as a conditional finding, with the Match condition in the evidence. - Fix: Set AllowTcpForwarding no, AllowAgentForwarding no, PermitTunnel no and GatewayPorts no globally or in every applicable Match block, then reload sshd. - Verify: Run sshd -T -C user=nobody,host=localhost,addr=192.0.2.1 and confirm each forwarding option is no. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/ssh_forwarding.go ### HOST-SSH-008 SSH Authentication Controls Too Permissive - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks effective SSH authentication method and login grace settings. - Why: Long login windows and unconstrained single-factor authentication give online guessing and stolen credentials more opportunity. - How detected: Evaluates applicable Match contexts and flags LoginGraceTime over 60 seconds, or password or keyboard-interactive login enabled without an AuthenticationMethods list that requires a second method. - Fix: Set LoginGraceTime 60 or lower and choose AuthenticationMethods appropriate to the server, such as publickey, then restrict access with AllowUsers or AllowGroups where practical. - Verify: Use sshd -T -C for each allowed user and source context and inspect logingracetime, authenticationmethods, allowusers and allowgroups. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/ssh_forwarding.go ### HOST-SSH-009 SSH Host Private Keys Readable by Others - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - What: Checks that SSH host private keys are readable only by root. - Why: Anyone who can read a host private key can impersonate the server and intercept SSH sessions from users who trust it. - How detected: Stats every existing file matching /etc/ssh/ssh_host_*_key (metadata only, contents are never read) and fails if it is readable or writable by others. - Fix: chown root:root /etc/ssh/ssh_host_*_key && chmod 600 /etc/ssh/ssh_host_*_key (0640 with group ssh_keys is acceptable on RHEL family). - Verify: stat -c '%a %U:%G %n' /etc/ssh/ssh_host_*_key - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-SSH-010 SSH Server Configuration Writable by Non-Root Users - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks that the SSH daemon configuration cannot be modified by group or other users. - Why: Whoever can edit sshd_config can enable root login, add their own keys file location or weaken authentication. - How detected: Stats every existing file matching /etc/ssh/sshd_config, /etc/ssh/sshd_config.d/*.conf (metadata only, contents are never read) and fails if it is writable by group or others. - Fix: chown root:root /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf && chmod 600 /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf - Verify: stat -c '%a %U %n' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/* - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-SSH-011 SSH Legacy Trust Options Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - What: Checks for host-based trust and user environment options that weaken SSH authentication. - Why: HostbasedAuthentication and ignoring .rhosts trust other machines instead of users. PermitUserEnvironment lets users set variables such as LD_PRELOAD that can bypass restrictions. - How detected: Reads hostbasedauthentication, ignorerhosts and permituserenvironment from the effective sshd configuration (all evaluated Match contexts). - Fix: Set HostbasedAuthentication no , IgnoreRhosts yes and PermitUserEnvironment no in /etc/ssh/sshd_config, then reload sshd. - Verify: sshd -T | grep -Ei 'hostbasedauthentication|ignorerhosts|permituserenvironment' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-SSH-012 SSH Logging Level Too Low - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that sshd logs at INFO or VERBOSE level. - Why: QUIET, FATAL or ERROR levels drop login records, so failed and successful logins cannot be investigated. VERBOSE also records the key fingerprint used. - How detected: Reads loglevel from the effective sshd configuration; INFO and VERBOSE pass. - Fix: Set LogLevel VERBOSE (or INFO) in /etc/ssh/sshd_config and reload sshd. - Verify: sshd -T | grep -i loglevel - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-SSH-013 SSH Authorized Keys Without Restrictions or Protection - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - What: Checks root's authorized_keys for keys without from= or command= options, and every authorized_keys file for permissive modes. - Why: A key in root's authorized_keys is a root password that never expires. Restricting it with from= (allowed source addresses) or command= (one fixed command) limits what a stolen key can do. An authorized_keys file that others can write lets them add their own key; one that others can read tells them which keys to steal. - How detected: Reads /root/.ssh/authorized_keys and lists key lines with neither from= nor command=; stats /root/.ssh/authorized_keys and /home/*/.ssh/authorized_keys (metadata only) and flags group or other write, and other read, permissions. Key material is never reported. - Fix: Prefix each root key with options, for example from="203.0.113.0/24",no-port-forwarding,no-agent-forwarding or command="/usr/local/bin/backup",restrict ; chmod 600 ~/.ssh/authorized_keys and 700 ~/.ssh for every account. Prefer sudo from a personal account over direct root keys. - Verify: cut -c1-40 /root/.ssh/authorized_keys ; stat -c '%a %n' /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hygiene.go ### HOST-SUDO-001 Sudo Commands Not Logged to a Dedicated File - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that sudo uses a pseudo-terminal and keeps its own log file. - Why: Defaults use_pty stops background processes from a sudo session lingering after it ends. A dedicated logfile keeps an easily reviewed record of privileged commands. - How detected: Reads /etc/sudoers and /etc/sudoers.d/* for 'Defaults use_pty' and 'Defaults logfile='. - Fix: With visudo add: Defaults use_pty and Defaults logfile="/var/log/sudo.log" - Verify: grep -rE 'use_pty|logfile' /etc/sudoers /etc/sudoers.d - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-SVC-001 Unnecessary Legacy Services Running - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks for desktop and legacy network services that servers rarely need. - Why: Each running service adds attack surface. Zeroconf discovery, printing, RPC portmapping and inetd-style super-servers are common, long-forgotten exposures. - How detected: Asks systemd whether avahi-daemon, cups, rpcbind, xinetd, telnet.socket, rsh.socket, rlogin.socket or vsftpd is active. - Fix: systemctl disable --now for each listed service you do not need, or remove its package. Caution: check first that nothing depends on it (systemctl list-dependencies --reverse ) and keep console access when disabling network or remote-access services. - Verify: systemctl is-active avahi-daemon cups rpcbind xinetd - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-SVC-002 Telnet Server Installed or Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks whether a Telnet server is installed or set to start at boot. - Why: Telnet sends user names, passwords and every keystroke in the clear. Anyone on the network path reads them. SSH replaced it decades ago; a Telnet server on a modern server is almost always forgotten rather than needed. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for telnetd, inetutils-telnetd, telnetd-ssl, telnet-server. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for telnet.socket, telnetd.service, inetutils-telnetd.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge telnetd inetutils-telnetd or dnf remove telnet-server , and systemctl disable --now telnet.socket . Use SSH. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l telnetd inetutils-telnetd telnetd-ssl telnet-server 2>/dev/null | grep '^ii' ; systemctl is-enabled telnet.socket telnetd.service inetutils-telnetd.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-003 rsh, rlogin, rexec or finger Server Installed or Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks whether the Berkeley r-services (rsh, rlogin, rexec) or a finger server is installed or enabled. - Why: The r-services trust the client's claim of who it is and send everything unencrypted; .rhosts files (HOST-USR-007) exist for them. finger lists account names and login times to anyone who asks, which is reconnaissance for password guessing. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for rsh-server, rsh-redone-server, fingerd, finger-server, efingerd, cfingerd. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for rsh.socket, rlogin.socket, rexec.socket, finger.socket, rsh-redone-server.service, fingerd.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge rsh-server rsh-redone-server fingerd or dnf remove rsh-server finger-server , and systemctl disable --now rsh.socket rlogin.socket rexec.socket finger.socket . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l rsh-server rsh-redone-server fingerd finger-server efingerd cfingerd 2>/dev/null | grep '^ii' ; systemctl is-enabled rsh.socket rlogin.socket rexec.socket finger.socket rsh-redone-server.service fingerd.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-004 TFTP Server Installed or Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks whether a TFTP server is installed or set to start at boot. - Why: TFTP has no authentication and no encryption: any client that can reach it can read, and often write, every file in its directory. It is needed only on network boot (PXE) servers and for some network equipment, and is a frequent source of leaked configuration files. If this machine is one of the network boot (PXE) servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for tftpd-hpa, atftpd, tftp-server, tftpd. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for tftpd-hpa.service, atftpd.service, tftp.socket, tftp.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge tftpd-hpa atftpd or dnf remove tftp-server , and systemctl disable --now tftpd-hpa.service tftp.socket . Where PXE is required, serve only that directory, read-only, from a dedicated host. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l tftpd-hpa atftpd tftp-server tftpd 2>/dev/null | grep '^ii' ; systemctl is-enabled tftpd-hpa.service atftpd.service tftp.socket tftp.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-005 FTP Server Installed or Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks whether an FTP server (vsftpd, ProFTPD or Pure-FTPd) is installed or set to start at boot. - Why: Plain FTP sends passwords and files unencrypted and needs a wide range of ports open. SFTP over SSH, which the SSH server already provides, does the same job encrypted. An FTP server is usually a leftover from a web hosting panel. If this machine is one of the hosts that must serve legacy FTP clients that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for vsftpd, proftpd-core, proftpd-basic, proftpd, pure-ftpd, pure-ftpd-common. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for vsftpd.service, proftpd.service, pure-ftpd.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: Move users to SFTP (the OpenSSH internal-sftp subsystem), then apt purge vsftpd proftpd-core pure-ftpd or dnf remove vsftpd proftpd and systemctl disable --now vsftpd . If FTP must stay, enforce TLS (FTPS) and restrict it to named users. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l vsftpd proftpd-core proftpd-basic proftpd pure-ftpd pure-ftpd-common 2>/dev/null | grep '^ii' ; systemctl is-enabled vsftpd.service proftpd.service pure-ftpd.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-006 NIS Server or Client Installed or Enabled - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks whether the Network Information Service (NIS, ypserv or ypbind) is installed or enabled. - Why: NIS distributes account and password hash databases over the network without encryption or strong authentication; anyone who can reach the server can request the whole password map. HOST-USR-006 looks for its + entries in the account files; this check looks for the software itself. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for nis, ypserv, ypbind, yp-tools. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for ypserv.service, ypbind.service, nis.service, yppasswdd.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: Replace NIS with a modern directory (SSSD with LDAPS or FreeIPA), then apt purge nis or dnf remove ypserv ypbind and systemctl disable --now ypserv ypbind . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l nis ypserv ypbind yp-tools 2>/dev/null | grep '^ii' ; systemctl is-enabled ypserv.service ypbind.service nis.service yppasswdd.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-007 Avahi or CUPS Installed or Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether zero-configuration discovery (Avahi) or the printing system (CUPS) is installed or enabled. - Why: Avahi announces the machine and its services to the local network, and CUPS listens for print jobs and runs a web interface. Both are desktop features with a history of vulnerabilities and no purpose on a server. If this machine is one of the desktops and print servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for avahi-daemon, cups, cups-daemon. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for avahi-daemon.service, avahi-daemon.socket, cups.service, cups.socket, cups-browsed.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge avahi-daemon cups cups-daemon or dnf remove avahi cups , and systemctl disable --now avahi-daemon.socket avahi-daemon.service cups.socket cups.service . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l avahi-daemon cups cups-daemon 2>/dev/null | grep '^ii' ; systemctl is-enabled avahi-daemon.service avahi-daemon.socket cups.service cups.socket cups-browsed.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-008 NFS Server or rpcbind Installed or Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether the NFS server or the RPC port mapper (rpcbind) is installed or enabled. - Why: NFS exports whole directory trees with trust based on client IP address and numeric user ids, and rpcbind tells any caller which RPC services are running. Unless this machine is a file server, both are attack surface with no benefit. The rpcbind package is often pulled in as a dependency and left running. If this machine is one of the file servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for nfs-kernel-server, rpcbind. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for nfs-server.service, rpcbind.service, rpcbind.socket, nfs-kernel-server.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge nfs-kernel-server rpcbind or dnf remove nfs-utils rpcbind where NFS is not served, and systemctl disable --now nfs-server rpcbind.socket rpcbind.service . Where NFS is required, export read-only with root_squash to named hosts and firewall port 2049 and 111. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l nfs-kernel-server rpcbind 2>/dev/null | grep '^ii' ; systemctl is-enabled nfs-server.service rpcbind.service rpcbind.socket nfs-kernel-server.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-009 DHCP or DNS Server Installed or Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether a DHCP server or an authoritative or recursive DNS server (BIND) is installed or enabled on a machine that is not a network server. - Why: A rogue DHCP server hands out addresses and gateways to the whole network segment, and BIND is a large, privileged network service with a long vulnerability history. Both belong on dedicated infrastructure hosts. On a web or application server they are either forgotten or an attacker's foothold. If this machine is one of the dedicated network infrastructure hosts that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for isc-dhcp-server, dhcp-server, kea-dhcp4-server, kea-dhcp6-server, bind9, bind, bind9-dnsutils-server. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for isc-dhcp-server.service, dhcpd.service, kea-dhcp4-server.service, named.service, bind9.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge isc-dhcp-server bind9 or dnf remove dhcp-server bind , and systemctl disable --now isc-dhcp-server named . If this machine is meant to be a DNS or DHCP server, record that in the suppression file and review NET-SVC findings for its configuration. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l isc-dhcp-server dhcp-server kea-dhcp4-server kea-dhcp6-server bind9 bind bind9-dnsutils-server 2>/dev/null | grep '^ii' ; systemctl is-enabled isc-dhcp-server.service dhcpd.service kea-dhcp4-server.service named.service bind9.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-010 Samba File Server Installed or Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether the Samba SMB file server is installed or set to start at boot. - Why: SMB file sharing exposes files, printers and authentication to the network and has been the target of worms and several critical remote code execution flaws. It is needed only on file servers for Windows-style clients. If this machine is one of the file servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for samba. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for smbd.service, nmbd.service, smb.service, nmb.service, samba.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge samba or dnf remove samba , and systemctl disable --now smbd nmbd (smb nmb on the RHEL family). Where it is required, disable SMB1, require signing and restrict shares to named users. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l samba 2>/dev/null | grep '^ii' ; systemctl is-enabled smbd.service nmbd.service smb.service nmb.service samba.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-011 SNMP Agent or Squid Proxy Installed or Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether an SNMP agent or the Squid caching proxy is installed or enabled. - Why: SNMP v1 and v2c authenticate with a community string sent in clear text (public and private are the usual defaults) and reveal routing tables, interfaces, processes and installed software. Squid turns the machine into a relay for other traffic and is often left open. Both need a deliberate configuration to be anything but a liability. If this machine is one of the monitored hosts using SNMPv3 and dedicated proxy servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for snmpd, net-snmp, squid, squid-openssl. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for snmpd.service, squid.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge snmpd squid or dnf remove net-snmp squid , and systemctl disable --now snmpd squid . If monitoring needs SNMP, use version 3 with authPriv and bind the agent to the management network. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l snmpd net-snmp squid squid-openssl 2>/dev/null | grep '^ii' ; systemctl is-enabled snmpd.service squid.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SVC-012 inetd Super-Server Installed or Enabled - Domain: host, severity: low, CVSS: CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether xinetd or another inetd super-server is installed or enabled. - Why: inetd-style super-servers exist to launch small network services (echo, chargen, telnet, tftp) on demand from a shared configuration. On a systemd machine every service they would start has a native unit; the super-server adds a second, rarely reviewed, place where network services are switched on. - How detected: Reads installed packages from /var/lib/dpkg/status (only entries marked 'install ok installed'; /var/lib/dpkg/info/*.list is a fallback used only when the status file cannot be read) and the allow-listed rpm -qa, and looks for xinetd, openbsd-inetd, inetutils-inetd. Reads the *.wants links under /etc/systemd/system (and asks the allow-listed systemctl is-enabled) for xinetd.service, inetd.service, openbsd-inetd.service, inetutils-inetd.service. Either an installed package or an enabled unit fails. Incomplete when no package database and no systemd unit directory could be read. - Fix: apt purge xinetd openbsd-inetd or dnf remove xinetd , and systemctl disable --now xinetd . Review /etc/inetd.conf and /etc/xinetd.d first so nothing needed disappears. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related. - Verify: dpkg -l xinetd openbsd-inetd inetutils-inetd 2>/dev/null | grep '^ii' ; systemctl is-enabled xinetd.service inetd.service openbsd-inetd.service inetutils-inetd.service 2>/dev/null - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/legacy_services.go ### HOST-SYSD-001 Custom Services Run as Root - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - What: Checks whether long-running services written by the administrator run as root instead of a dedicated user. - Why: Application services (Node.js, Python, Java, custom daemons) are the code most exposed to attackers. If one runs as root, any bug in it gives the attacker full control of the machine instead of one unprivileged account. - How detected: Reads every service unit in /etc/systemd/system (symlinks skipped) together with its drop-in files, keeps units that start a long-running process (Type is not oneshot), and flags those with no User= (or User=root) and no DynamicUser=yes. - Fix: Create a dedicated account (useradd --system --no-create-home --shell /usr/sbin/nologin ), give it ownership of only the folders the app writes to, then add User= and Group= to the [Service] section (or DynamicUser=yes). Run systemctl daemon-reload and restart the service. Ports below 1024 can be allowed with AmbientCapabilities=CAP_NET_BIND_SERVICE instead of root. - Verify: systemctl show -p User,DynamicUser and ps -o user= -p $(systemctl show -p MainPID --value ) - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/systemd.go ### HOST-SYSD-002 Custom Services Lack Sandboxing - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - What: Checks whether custom long-running services use systemd's basic sandboxing options. - Why: systemd can confine a service with a few lines: block privilege gain through setuid programs, mount the operating system read-only, give it a private /tmp and hide home directories. These limit what an attacker can do after exploiting the service, at almost no cost. - How detected: For each long-running service unit in /etc/systemd/system, merges drop-ins and checks for NoNewPrivileges=yes, ProtectSystem=strict (or full/yes), PrivateTmp=yes and ProtectHome=yes (or read-only/tmpfs). Lists the missing settings per unit. - Fix: Add the missing lines with systemctl edit (a drop-in), for example NoNewPrivileges=yes, ProtectSystem=strict, ReadWritePaths=/var/lib/, PrivateTmp=yes, ProtectHome=yes. Restart the service and test it; add ReadWritePaths= for every folder the app must write. systemd-analyze security suggests further options. - Verify: systemd-analyze security shows a lower exposure score, and the service still works. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/systemd.go ### HOST-SYSD-003 Service Unit Files Writable by Non-Root Users - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks whether any systemd service unit or drop-in file can be changed by a user other than root. - Why: systemd starts services as root by default. Whoever can edit a unit file can change ExecStart= or User= and have their own program run as root at the next restart or reboot, a well-known privilege escalation and persistence technique. - How detected: Reads the metadata of every service unit and drop-in in /etc/systemd/system, /lib/systemd/system and /usr/lib/systemd/system (symlinks skipped) and flags files that are group- or world-writable or not owned by root. - Fix: chown root:root && chmod 644 for each listed file, then review its contents for changes you did not make (ExecStart=, User=, ExecStartPre=) and run systemctl daemon-reload. - Verify: stat -c '%U %a %n' /etc/systemd/system/*.service shows root and 644 (or stricter). - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/systemd.go ### HOST-SYSD-004 Service Programs in Writable Locations - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks whether a service starts a program from a temporary folder, or a program file that other users can replace. - Why: If the program a root service starts lives in /tmp or is writable by other users, any local user (or a compromised web application) can replace it and gain root the next time the service restarts. - How detected: Takes the program path of each long-running service's ExecStart= line and flags paths under /tmp, /var/tmp or /dev/shm, program files that are world-writable, and, for services running as root, program files not owned by root. - Fix: Install the program in a root-owned location (/usr/local/bin or /opt/), chown root:root and chmod 755 it, point ExecStart= at it, then systemctl daemon-reload and restart the service. - Verify: stat -c '%U %a %n' $(systemctl show -p ExecStart --value | awk '{print $2}' | sed 's/path=//;s/;//') - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/systemd.go ### HOST-SYSD-005 Secrets Written Into Service Unit Files - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - What: Checks whether passwords, tokens or keys are set with Environment= in systemd unit files. - Why: Unit files are normally readable by every user, and systemctl show prints a service's Environment= values to any local user. A database password or API token placed there is effectively public on the machine. - How detected: Reads Environment= lines of every service unit and its drop-ins and flags variables whose names suggest a secret (PASSWORD, SECRET, TOKEN, API_KEY, PRIVATE_KEY, CREDENTIAL, AUTH). Only the variable names are reported; values are never copied. - Fix: Move secrets to a root-owned file readable only by root (chmod 600) and load it with EnvironmentFile=, or better use LoadCredential=/SetCredentialEncrypted= so the secret is only visible to the service. Then rotate every secret that was exposed. - Verify: systemctl show -p Environment should show no secret values. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/systemd.go ### HOST-TIME-001 Time Synchronisation Not Active - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N - What: Checks that the clock is kept in sync (chrony, ntpd or systemd-timesyncd). - Why: Accurate time is needed to correlate logs during an investigation, and TLS certificates and one-time-password codes fail when the clock drifts. - How detected: Asks systemd whether chronyd, chrony, ntpd, ntp or systemd-timesyncd is active. - Fix: Enable one time service, for example: systemctl enable --now systemd-timesyncd (or install chrony). - Verify: timedatectl should show 'System clock synchronized: yes'. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/logging_time.go ### HOST-UPD-001 Automatic Security Updates Not Configured - Domain: host, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks that security updates are installed automatically. - Why: Many compromises use vulnerabilities fixed days or weeks earlier. Automatic security updates close that window without relying on someone remembering. - How detected: Debian/Ubuntu: reads /etc/apt/apt.conf.d/20auto-upgrades (and 50unattended-upgrades) for APT::Periodic::Unattended-Upgrade "1". RHEL family: asks systemd whether dnf-automatic.timer or dnf-automatic-install.timer is active. - Fix: Debian/Ubuntu: apt install unattended-upgrades && dpkg-reconfigure -plow unattended-upgrades . RHEL family: dnf install dnf-automatic, set apply_updates = yes and upgrade_type = security, then systemctl enable --now dnf-automatic.timer . - Verify: Re-run this check. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-UPD-002 Reboot Required to Apply Updates - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L - What: Checks whether installed updates (often kernel or core library fixes) are waiting for a reboot. - Why: Updated packages do not protect anything until the running kernel and libraries are replaced. A pending reboot means known-fixed vulnerabilities are still live. - How detected: Checks for /var/run/reboot-required (Debian/Ubuntu) and reads the package list in /var/run/reboot-required.pkgs when present. - Fix: Schedule a maintenance window and reboot (or use live patching where supported). - Verify: ls /var/run/reboot-required should not exist after the reboot. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hardening.go ### HOST-USR-001 Extra Accounts With Root Privileges (UID 0) - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks /etc/passwd for accounts other than root that have user id 0. - Why: Any account with UID 0 is root, whatever its name. Extra UID 0 accounts are a classic backdoor and bypass sudo logging. - How detected: Reads /etc/passwd and lists every entry whose third field (UID) is 0, other than 'root'. - Fix: Investigate who created each account. Remove it (userdel ) or give it a normal UID (usermod -u ). Grant admin rights through sudo instead. - Verify: awk -F: '$3==0' /etc/passwd should print only the root line. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/accounts.go ### HOST-USR-002 Accounts Without a Password - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks /etc/shadow for accounts whose password field is empty. - Why: An empty password field means the account can log in with no password at all on any service that allows password logins. The score uses a local attack vector: any local user can switch to the account (su) without a password, while the SSH daemon refuses empty passwords unless PermitEmptyPasswords is yes. If HOST-SSH-003 also fails, the same account is open from the network and should be treated as Critical. - How detected: Reads /etc/shadow (needs root) and lists entries whose second field is empty. Locked ('!' or '*') entries pass. Hashes are never copied into the report. - Fix: For each listed user either set a password (passwd ) or lock the account (passwd -l ). - Verify: awk -F: '$2==""' /etc/shadow should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/accounts.go ### HOST-USR-003 Sudo Rules Without Password (NOPASSWD) - Domain: host, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks sudoers files for rules that grant root without asking for a password. - Why: NOPASSWD means anyone who takes over that user (a stolen SSH key, a hijacked session, a web shell running as that user) becomes root instantly. - How detected: Reads /etc/sudoers and /etc/sudoers.d/* and lists active lines containing NOPASSWD. - Fix: Edit with visudo and remove NOPASSWD, or restrict it to one specific, harmless command for automation accounts. - Verify: grep -r NOPASSWD /etc/sudoers /etc/sudoers.d should print nothing, or only narrowly scoped commands. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/accounts.go ### HOST-USR-004 Duplicate User IDs - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N - What: Checks that no two accounts share the same numeric user id. - Why: Accounts with the same UID are the same user to the kernel: they own each other's files and processes, and audit logs cannot tell them apart. - How detected: Reads /etc/passwd and reports any UID used by more than one account. - Fix: Give each account a unique UID (usermod -u ) and fix file ownership (find / -xdev -uid ). - Verify: cut -d: -f3 /etc/passwd | sort | uniq -d should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-USR-005 System Accounts With Login Shells - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N - What: Checks that service accounts (UID below UID_MIN, usually 1000, other than root) cannot log in interactively. - Why: Service accounts should never need a shell. A login shell gives an attacker who compromises the service, or guesses its password, an interactive foothold. - How detected: Reads UID_MIN from /etc/login.defs (default 1000) and /etc/passwd, and lists accounts with UID 1 to UID_MIN minus 1 whose shell is not nologin, false, sync, halt or shutdown. - Fix: usermod -s /usr/sbin/nologin for each listed account (use /sbin/nologin on RHEL family). - Verify: awk -F: '$3>0 && $3<1000 {print $1, $7}' /etc/passwd - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-USR-006 Legacy NIS Entries in Account Files - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks /etc/passwd, /etc/shadow and /etc/group for legacy '+' NIS inclusion entries. - Why: '+' entries pull accounts from NIS, an unencrypted legacy directory service. If NIS is gone, the entry can still be abused to create unexpected accounts. - How detected: Reads the account files and lists lines beginning with '+'. - Fix: Remove the '+' lines (with vipw / vigr) and use a modern directory service such as SSSD with LDAPS if central accounts are needed. - Verify: grep '^+' /etc/passwd /etc/shadow /etc/group should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-USR-007 Insecure Trust Files in Home Directories - Domain: host, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - What: Looks for .rhosts, .shosts and .netrc files in user home directories. - Why: .rhosts and .shosts grant password-less trust to other hosts. .netrc files usually hold plain-text passwords for FTP or other services. - How detected: Reads home directories from /etc/passwd and checks each for .rhosts, .shosts and .netrc (file existence only; contents are never read). - Fix: Delete .rhosts and .shosts files. Replace .netrc credentials with a secret manager or key-based authentication, then delete the file and rotate the passwords it contained. - Verify: Re-run this check; no files should be listed. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/passwords.go ### HOST-USR-008 Duplicate Group IDs or Account Names - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N - What: Checks /etc/group for duplicate GIDs and group names, and /etc/passwd for duplicate user names. - Why: Two groups with one GID are the same group to the kernel, so file permissions granted to one are granted to both. Duplicate names make tools and audit logs act on the wrong entry, and are a sign that the account files were edited by hand or by a broken script. - How detected: Reads /etc/group and /etc/passwd and reports any GID, group name or user name that appears more than once (HOST-USR-004 covers duplicate UIDs). - Fix: groupmod -g for one of each duplicate pair, then fix file ownership (find / -xdev -gid ); remove or rename the duplicate name entries with vigr and vipw, which lock the files while editing. - Verify: cut -d: -f3 /etc/group | sort | uniq -d ; cut -d: -f1 /etc/group /etc/passwd | sort | uniq -d print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hygiene.go ### HOST-USR-009 Password and Shadow Files Out of Step - Domain: host, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - What: Checks that every /etc/passwd account has a /etc/shadow entry, that no password hash sits in /etc/passwd, and that /etc/shadow has no orphan entries. - Why: A hash in the world-readable /etc/passwd can be cracked offline by any user. An account with 'x' in passwd but no shadow line has an undefined password state that differs between tools, and shadow lines for deleted accounts are leftovers of a removal that did not finish. - How detected: Reads /etc/passwd and /etc/shadow (root only) and reports accounts missing from shadow, passwd password fields other than x, *, ! or empty, and shadow entries with no passwd account. - Fix: pwconv moves hashes into /etc/shadow and creates missing entries; userdel or vipw/vigr removes orphan lines; run pwck and grpck to confirm the files are consistent. - Verify: pwck -r reports no errors. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hygiene.go ### HOST-USR-010 Home Directories Accessible to Other Users - Domain: host, severity: low, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N - What: Checks that the home directory of each interactive account denies read, write and execute to others. - Why: Home directories hold shell history, SSH keys, application configuration and downloads. With others allowed in, any account on the machine can read another user's history and keys or, with write access, plant a .bashrc that runs when that user logs in. - How detected: Reads interactive accounts from /etc/passwd (UID at or above UID_MIN, a real shell), stats each home directory (metadata only) and fails when others have any permission bit. Missing home directories are noted but do not fail. - Fix: chmod 750 /home/ for each listed account (700 if group access is not needed), and set HOME_MODE 0750 in /etc/login.defs or DIR_MODE=0750 in /etc/adduser.conf so new accounts start that way. - Verify: stat -c '%a %n' /home/* shows 750 or 700. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/host/hygiene.go ### NET-AI-001 AI Model Server or Dashboard Reachable From the Network - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L - What: Checks whether a self-hosted AI model server, AI web interface, AI cluster dashboard or vector database listens on a network-reachable address. - Why: Many self-hosted AI tools have no login enabled by default. Reached from the network, anyone can use the models and the computing power, read stored documents and embeddings, pull or delete models, and on some dashboards submit jobs that run code. - How detected: Matches network-reachable listening TCP sockets from /proc/net against the documented default ports of Ollama, LM Studio, Gradio, ComfyUI, the Ray dashboard, Qdrant, Milvus and TensorBoard, and records how each is bound. No request, prompt or model call is sent. - Fix: Bind each service to 127.0.0.1 (Ollama: OLLAMA_HOST=127.0.0.1:11434 in its service environment; Ray: --dashboard-host=127.0.0.1; Gradio: server_name="127.0.0.1"), and if others need it, publish it only through a reverse proxy with authentication and TLS. - Verify: ss -tlnp shows these ports on 127.0.0.1 or ::1 only. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/ai_services.go ### NET-AI-002 Jupyter Configuration Weakens Authentication - Domain: network, severity: high, CVSS: CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether a Jupyter configuration blanks the login token without setting a password, or turns off cross-site request protection. - Why: Jupyter runs any code typed into it with the rights of the account that started it. With the token blanked and no password, anyone who can reach the port, or any local user, gets that account. With XSRF checks off, a web page a user visits can send it commands. - How detected: Reads Jupyter configuration files under /etc/jupyter, /usr/local/etc/jupyter, /root/.jupyter and /home/*/.jupyter and looks for token, password and disable_check_xsrf assignments. Only whether a value is empty is recorded; no token or password is copied. - Fix: Remove the empty token line so Jupyter generates a random token, or set a password with jupyter server password . Remove disable_check_xsrf = True. Bind to 127.0.0.1 and reach it through an SSH tunnel. - Verify: grep -nE 'token|password|disable_check_xsrf' shows no empty token and no disabled XSRF check, and the server log prints a URL with a token at start-up. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/ai_services.go ### NET-APP-001 Debugger or Application Control Ports Exposed - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L - What: Checks whether the Node.js inspector, Tomcat AJP or shutdown port, PHP-FPM FastCGI or Jupyter listen on network-reachable addresses. - Why: These ports are not web pages: the Node.js inspector and Jupyter run code for whoever connects, Tomcat AJP has been abused for file read and code execution (Ghostcat), the shutdown port stops the server, and PHP-FPM executes scripts handed to it. None of them is designed to face a network. - How detected: Matches network-reachable listening TCP sockets from /proc/net against the default ports of these debuggers and control protocols, recording how each is bound. Plain application HTTP ports are rated separately by NET-APP-002. No packets are sent. - Fix: Bind each to 127.0.0.1 or a Unix socket (node --inspect=127.0.0.1:9229; Tomcat Connector address="127.0.0.1" or remove the AJP connector; PHP-FPM listen = /run/php/php-fpm.sock; jupyter --ip=127.0.0.1) and never expose a debugger. - Verify: ss -tlnp shows these ports on 127.0.0.1 or ::1 only. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/firewall_exposure.go ### NET-APP-002 Application Server Ports Reachable Without a Reverse Proxy - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L - What: Checks whether application servers and developer tools (Node.js, Tomcat, Django, Flask, Vite) serve HTTP on network-reachable addresses. - Why: Application servers are designed to sit behind a reverse proxy that adds TLS, security headers, logging and request limits. Reached directly they bypass all of that, and development servers on these ports often run with debug pages on. The port number alone does not prove a weakness, so this is rated Medium and should be reviewed. - How detected: Matches network-reachable listening TCP sockets from /proc/net against the default HTTP ports of common application servers and developer tools, recording how each is bound and whether a web server listens on port 80 or 443 on this machine (which suggests the application was meant to be proxied). No packets are sent. - Fix: Bind the application to 127.0.0.1 (Node.js: app.listen(3000, '127.0.0.1'); Gunicorn: --bind 127.0.0.1:8000; Tomcat: address="127.0.0.1" on the Connector) and publish it only through nginx or Apache. - Verify: ss -tlnp shows these ports on 127.0.0.1 or ::1 only. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/firewall_exposure.go ### NET-DB-001 Database or Cache Service Exposed to the Network - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether databases, caches, search engines or the Docker API listen on a network-reachable address. - Why: Exposed databases and caches (Redis, MongoDB, Elasticsearch, Memcached) are among the most common causes of mass data breaches and ransom wipes, often because they ship without authentication. - How detected: Matches network-reachable listening ports from /proc/net against a list of well-known data service ports (IANA registry). - Fix: Bind the service to 127.0.0.1 (MySQL: bind-address = 127.0.0.1; Redis: bind 127.0.0.1 ::1 and protected-mode yes; MongoDB: net.bindIp: 127.0.0.1) and restart it. Caution: confirm first that no application on another machine connects to it, or that application loses its database. If remote access is needed, allow only specific source IPs in the firewall and require TLS and authentication. - Verify: ss -tlnp | grep -E ':(3306|5432|6379|27017|9200|11211)' should show only 127.0.0.1 or ::1. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/listeners.go ### NET-DB-002 Redis Authentication and Network Binding - Domain: network, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Redis lacks protected mode, authentication, or a loopback-only bind. - Why: An unauthenticated network-reachable Redis instance can disclose or alter data and may permit dangerous administrative operations. - How detected: Parses redis.conf for protected-mode, requirepass or ACL users, and bind. It does not connect or authenticate. - Fix: Set protected-mode yes, configure ACL users with strong secrets, bind to 127.0.0.1 and ::1, and restrict remote access with a firewall. - Verify: Review redis-server --test-memory only for health, then inspect the effective configuration and listening socket after reload. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_services.go ### NET-DB-003 MongoDB Authorization and Network Binding - Domain: network, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: MongoDB authorization is disabled or it binds beyond loopback. - Why: MongoDB without authorization or with broad exposure can permit unauthenticated database access. - How detected: Parses mongod configuration for security.authorization and net.bindIp. - Fix: Set security.authorization to enabled and net.bindIp to 127.0.0.1, then allow only approved clients through a controlled network path. - Verify: Run mongod --config --configExpand none in a maintenance workflow and confirm port 27017 is loopback-only. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_services.go ### NET-DB-004 PostgreSQL Trust and Broad Client Access - Domain: network, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: PostgreSQL accepts trust authentication or unrestricted client networks. - Why: Trust authentication and all-address client rules can allow database access without a password from untrusted networks. - How detected: Reads postgresql.conf and pg_hba.conf, flags listen_addresses '*' or 0.0.0.0, trust methods, and 0.0.0.0/0 or ::/0 client ranges. - Fix: Bind PostgreSQL to required interfaces only and replace trust or unrestricted pg_hba.conf rules with narrow CIDRs and scram-sha-256. Caution: users whose passwords are stored as md5 cannot log in with scram-sha-256 until their password is set again with password_encryption = scram-sha-256, so reset them first and reload, not restart, to test. - Verify: Run pg_ctl reload through normal operations, then use pg_hba_file_rules and ss -tlnp to verify the effective rules. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_services.go ### NET-DB-005 MySQL or MariaDB Unsafe Network Options - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: MySQL or MariaDB enables local file loading, skips grants, or listens broadly with networking enabled. - Why: These options can bypass authorization, expand file access, or expose the database to untrusted networks. - How detected: Parses MySQL and MariaDB option files for local_infile, skip-grant-tables, skip-networking, and bind-address. - Fix: Disable local_infile and skip-grant-tables. Bind to 127.0.0.1 or enable skip-networking when TCP access is unnecessary. - Verify: Inspect SHOW VARIABLES through an approved administrator session and confirm the listening address locally. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_services.go ### NET-DB-006 Memcached UDP or Broad Listen Address - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Memcached enables UDP or listens on a non-loopback address. - Why: Unauthenticated Memcached exposure can disclose cached data and UDP mode has been abused for amplification attacks. - How detected: Parses memcached.conf (one option per line) and /etc/sysconfig/memcached (OPTIONS and UDP_PORT) for -U or UDP_PORT and -l. A UDP port other than 0 fails; a listen address fails unless every address is loopback (127.0.0.1, ::1 or localhost); no -l at all means every interface and fails. The actual values are the evidence. - Fix: Set UDP_PORT=0 or -U 0 and bind with -l 127.0.0.1. Use a firewall for any explicitly required clients. - Verify: Confirm ss -ltnup shows Memcached only on loopback and no UDP port 11211. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_services.go ### NET-DB-007 Elasticsearch Security and Network Binding - Domain: network, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Elasticsearch security is disabled or network.host is broadly reachable. - Why: An exposed Elasticsearch cluster without authentication can disclose, alter, or delete indexed data. - How detected: Parses elasticsearch.yml for xpack.security.enabled and network.host. - Fix: Set xpack.security.enabled true, bind network.host to loopback or an approved private interface, require TLS, and filter access. - Verify: Use the local node configuration API with approved credentials and confirm port 9200 is not broadly exposed. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_services.go ### NET-DB-008 Unsafe NFS Export Options - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: NFS exports are world-accessible or use no_root_squash or insecure. - Why: These export options can grant untrusted clients filesystem access or preserve remote root privileges. - How detected: Reads /etc/exports and active exports.d fragments, ignoring comments, and flags *, no_root_squash, and insecure. - Fix: Restrict every export to explicit client CIDRs, use root_squash, remove insecure, and reload exports. - Verify: Run exportfs -v during approved administration and confirm only intended networks and safe options appear. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/services.go ### NET-DB-009 Unsafe Samba Guest or SMB1 Configuration - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Samba permits guest access, maps unknown users to guest, or allows SMB1. - Why: Guest shares weaken accountability, while SMB1 lacks modern protocol protections and has a long history of severe flaws. - How detected: Reads smb.conf and flags guest ok = yes, permissive map to guest modes, or a server min protocol below SMB2. - Fix: Set guest ok = no, map to guest = Never, and server min protocol = SMB2_02 or newer. Review each share ACL. - Verify: Run testparm -s during approved administration and confirm the effective values. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/services.go ### NET-DB-010 BIND Open Recursion - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H - What: BIND enables recursion without restricting recursive clients. - Why: An open recursive resolver can be abused for reflection attacks and permits outsiders to consume resolver resources. - How detected: Reads named.conf and flags recursion yes when neither allow-recursion nor allow-query-cache restricts clients. - Fix: Disable recursion on authoritative-only servers, or set allow-recursion and allow-query-cache to explicit trusted networks. - Verify: Run named-checkconf and query recursion from an untrusted test network under an approved test plan. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/services.go ### NET-DB-011 BIND Zone Transfers Allowed to Any - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: BIND permits zone transfers to any client. - Why: Unrestricted transfers disclose the complete DNS namespace and provide attackers with a detailed target inventory. - How detected: Reads named.conf and flags allow-transfer clauses containing any. - Fix: Set allow-transfer to none or explicit secondary server addresses, preferably protected with TSIG. - Verify: Run named-checkconf and verify AXFR is refused from an unauthorized test host. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/services.go ### NET-DB-012 Postfix Open Relay Controls - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L - What: Postfix relay restrictions are missing or mynetworks trusts overly broad ranges. - Why: An open relay enables spam and phishing, damages domain reputation, and can expose the host to blocklisting. - How detected: Parses main.cf for smtpd_relay_restrictions with reject_unauth_destination and flags 0.0.0.0/0 or ::/0 in mynetworks. - Fix: Set smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination and narrow mynetworks. - Verify: Run postconf -n during approved administration and perform only an authorized relay test. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/services.go ### NET-DB-013 Mail Cleartext Authentication Allowed - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N - What: Postfix or Dovecot permits plaintext authentication without requiring TLS. - Why: Credentials sent before transport encryption can be observed by any party able to intercept the connection. - How detected: Reads Postfix smtpd_tls_auth_only and Dovecot disable_plaintext_auth, and flags no values. - Fix: Set smtpd_tls_auth_only = yes and disable_plaintext_auth = yes. Use implicit TLS on ports 465, 993, and 995. STARTTLS assessment is out of scope. - Verify: Confirm the effective mail configuration and that unauthenticated plaintext sessions do not advertise AUTH. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/services.go ### NET-DB-014 MySQL Symbolic Links Allowed for Tables - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - What: MySQL or MariaDB does not disable symbolic links for table files (skip-symbolic-links or symbolic-links=0). - Why: With symlinks allowed, a user who can create tables can point table files at paths outside the data directory and read or overwrite them with the server's privileges. The MySQL manual recommends disabling the feature unless it is needed. - How detected: Parses the MySQL and MariaDB option files for skip-symbolic-links, symbolic-links or skip_symbolic_links under [mysqld]. When none disables the feature, reads the server flavour and version (the MariaDB binary or option folder, installed packages in the dpkg status file or the allow-listed rpm -qa): fails on MariaDB and MySQL before 8.0, passes with a note on MySQL 8.0 and later (off by default), and reports Incomplete when the version cannot be read. - Fix: Add symbolic-links=0 (or skip-symbolic-links ) under [mysqld] and restart the service in a maintenance window. - Verify: SHOW VARIABLES LIKE 'have_symlink'; from an administrator session should print DISABLED. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-015 MySQL secure_file_priv Not Restricted - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:L/A:N - What: secure_file_priv is not set, or is set to an empty value, so LOAD DATA and SELECT ... INTO OUTFILE can read and write anywhere the server process can. - Why: File import and export statements run with the database server's filesystem rights. Limiting them to one folder, or disabling them with NULL, stops an injected query from reading /etc files or dropping a web shell into the document root. - How detected: Parses the option files for secure_file_priv (or secure-file-priv) under [mysqld]. NULL or a folder path passes; an explicit empty value fails. When the setting is absent, reads the server flavour and version (the MariaDB binary or option folder, installed packages in the dpkg status file or the allow-listed rpm -qa): fails on MariaDB and MySQL before 8.0, passes with a note on packaged MySQL 8.0 and later (restricted by default), and reports Incomplete when the version cannot be read. - Fix: Add secure_file_priv = NULL (no file operations) or secure_file_priv = /var/lib/mysql-files under [mysqld] and restart in a maintenance window. - Verify: SHOW VARIABLES LIKE 'secure_file_priv'; should print NULL or the chosen folder. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-016 MySQL Does Not Require Encrypted Client Connections - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: require_secure_transport is not ON, so clients may log in and query over plaintext TCP. - Why: Without the requirement, a client that is misconfigured or downgraded sends credentials and data in the clear across the network. The setting is harmless for Unix socket connections and makes TLS a server-side rule rather than a client choice. When bind-address keeps the server on loopback (127.0.0.1, ::1 or localhost) the plaintext path never leaves the machine and only a local root user could read it, so the check reports Pass with a note instead of a Medium finding; the Medium applies to servers reachable from the network. - How detected: Parses the option files for require_secure_transport under [mysqld]. Fails unless it is ON. Passes with a note when skip-networking is on (no TCP) or every bind-address value is a loopback address, since no network path exists to protect. - Fix: Provision a server certificate (MySQL creates one by default), then add require_secure_transport = ON under [mysqld] and restart in a maintenance window. Test every application connection string first. - Verify: SHOW VARIABLES LIKE 'require_secure_transport'; should print ON, and a client that disables TLS (ssl-mode DISABLED) connecting to 127.0.0.1 should be refused. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-017 MySQL Error Log Not Configured - Domain: network, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: log_error is not set, so server errors, failed logins and startup warnings go to the console or syslog without a dedicated, retained file. - Why: The error log is the first place to see authentication failures, crashes and plugin warnings. A named file with rotation makes those events available to incident response and to the audit checks that read it. - How detected: Parses the option files for log_error (or log-error) under [mysqld]. Fails when absent. - Fix: Add log_error = /var/log/mysql/error.log under [mysqld], make the folder writable by the mysql user, and add a logrotate rule. - Verify: SHOW VARIABLES LIKE 'log_error'; should print the file path. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-018 MySQL Resolves Client Host Names - Domain: network, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: skip-name-resolve is not set, so grants that name hosts depend on DNS answers and every connection waits on a reverse lookup. - Why: Host-based grants that trust names can be satisfied by whoever controls DNS or the resolver path. With skip-name-resolve the server matches IP addresses only, which is both faster and not spoofable through DNS. Grants that use host names must be rewritten first. - How detected: Parses the option files for skip-name-resolve, skip_name_resolve or host_cache_size=0 under [mysqld]. Fails when none is present. - Fix: Rewrite grants that use host names to IP addresses or '%', then add skip-name-resolve under [mysqld] and restart in a maintenance window. - Verify: SHOW VARIABLES LIKE 'skip_name_resolve'; should print ON. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-019 PostgreSQL Uses md5 or password Authentication - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: pg_hba.conf has active rules with the md5 or password method instead of scram-sha-256. - Why: The password method sends the password in clear text unless the connection is encrypted; md5 is a fast unsalted construction whose stored hashes work as passwords themselves (pass-the-hash) and which the PostgreSQL documentation describes as obsolete. SCRAM-SHA-256 fixes both. - How detected: Reads pg_hba.conf, ignores comments, and flags every rule whose method is md5 or password. NET-DB-004 already covers trust and open address ranges. - Fix: Set password_encryption = scram-sha-256 in postgresql.conf, have every role reset its password so a SCRAM verifier is stored, then change the pg_hba.conf methods to scram-sha-256 and reload. - Verify: SELECT * FROM pg_hba_file_rules WHERE auth_method IN ('md5','password'); should return no rows. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-020 PostgreSQL SSL Not Enabled - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: postgresql.conf does not set ssl = on, so TCP clients cannot encrypt their sessions and hostssl rules cannot apply. - Why: Without SSL every query and result crosses the network in the clear. Enabling it costs one certificate and lets pg_hba.conf require hostssl for remote clients. When listen_addresses keeps the server on loopback (the PostgreSQL default, localhost) the plaintext path never leaves the machine and only a local root user could read it, so the check reports Pass with a note instead of a Medium finding; the Medium applies to servers reachable from the network. - How detected: Reads postgresql.conf and flags ssl absent or off. Passes with a note when listen_addresses is empty (no TCP at all) or names only loopback addresses (localhost, 127.0.0.1, ::1), or when it is unset, since the PostgreSQL default is localhost. - Fix: Install a certificate and key readable only by the postgres user, set ssl = on , ssl_cert_file and ssl_key_file, reload, then change remote pg_hba.conf rules from host to hostssl. - Verify: SHOW ssl; should print on, and psql "sslmode=require host=127.0.0.1" should connect. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-021 PostgreSQL Connection Logging Off - Domain: network, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N - What: log_connections and log_disconnections are off, so successful logins leave no record. - Why: Failed logins are logged by default, but successful ones are not. Knowing which role connected from where and for how long is the base evidence for spotting credential misuse and for any later investigation. - How detected: Reads postgresql.conf and flags log_connections not on. log_disconnections is reported as evidence. - Fix: Set log_connections = on and log_disconnections = on in postgresql.conf and reload. On PostgreSQL 18 and newer, log_connections accepts a list such as receipt,authentication,authorization . - Verify: SHOW log_connections; should print on (or a non-empty list). - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-022 PostgreSQL Password Encryption Not SCRAM - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: password_encryption is set to md5, so new and changed passwords are stored as md5 verifiers that work as pass-the-hash credentials. - Why: The stored md5 value is itself sufficient to authenticate, so a leaked catalog is a leaked set of passwords. SCRAM-SHA-256 stores a salted, iterated verifier that is not usable directly. PostgreSQL 14 and newer default to scram-sha-256; older clusters often carry an explicit md5 line. - How detected: Reads postgresql.conf and flags password_encryption = md5 (or on, the legacy alias). Absent passes on the assumption of the modern default and is noted as evidence. - Fix: Set password_encryption = scram-sha-256 , reload, and ask every role to reset its password so the verifier is regenerated. Then move pg_hba.conf to scram-sha-256 (NET-DB-019). - Verify: SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%'; should return no rows after the resets. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DB-023 Redis Dangerous Commands Not Restricted - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - What: redis.conf neither renames nor disables CONFIG, FLUSHALL, FLUSHDB, DEBUG and KEYS, and no ACL user excludes the @dangerous category. - Why: An attacker who reaches Redis with any credential can wipe data with FLUSHALL, rewrite the configuration with CONFIG SET to write files (the classic path to a cron job or SSH key), or stall the server with KEYS *. Removing or renaming these for application users limits what a stolen credential can do. - How detected: Parses redis.conf for rename-command lines covering CONFIG, FLUSHALL, FLUSHDB, DEBUG and KEYS, or an ACL definition (user lines, or an aclfile) that excludes @dangerous or @admin. Fails when neither protection exists, unless every bind address is loopback and protected-mode is not off: then only local processes can reach Redis and the result is Pass with a note (Low exposure). - Fix: Prefer ACLs on Redis 6 and newer: user app on >secret ~app:* -@all +@read +@write -@dangerous . Otherwise add rename-command FLUSHALL "" (and the same for FLUSHDB, CONFIG, DEBUG, KEYS) and restart. - Verify: redis-cli -a ACL LIST or redis-cli CONFIG GET dir should be refused for the application user. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DNS-001 BIND Version String Disclosed - Domain: network, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: named.conf has no version statement in options, so a CHAOS class query for version.bind returns the exact BIND release. - Why: The version string lets an attacker match advisories to the server without any guessing. Replacing it costs nothing and does not break resolution. - How detected: Reads named.conf and named.conf.options and looks for a version statement inside options. Fails when absent. - Fix: Add version "not disclosed"; inside the options block and run named-checkconf, then rndc reconfig. - Verify: dig @127.0.0.1 version.bind txt chaos +short should print the chosen string. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-DNS-002 BIND DNSSEC Validation Disabled - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N - What: named.conf sets dnssec-validation no, so the resolver accepts forged answers for signed zones. - Why: DNSSEC validation lets a recursive resolver reject spoofed records for zones that are signed, which blocks cache poisoning of those names. BIND 9.16 and newer default to auto with the built-in root trust anchor, so an explicit no is a deliberate weakening. - How detected: Reads named.conf and named.conf.options for dnssec-validation. Fails on no . Absent passes with a note (the default is auto on supported releases); auto or yes passes. - Fix: Set dnssec-validation auto; in options (remove any no ), run named-checkconf and rndc reconfig. Authoritative-only servers with recursion off may leave it unset. - Verify: dig @127.0.0.1 dnssec-failed.org a should return SERVFAIL, and a signed name should show the ad flag. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-FW-001 No Active Host Firewall - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L - What: Checks whether a host firewall (nftables, iptables, ufw or firewalld) is filtering incoming traffic. - Why: A default-deny host firewall is a safety net: a service that is installed or misconfigured by mistake is still not reachable from the internet. - How detected: Runs read-only status commands in order: ufw status, firewall-cmd --state, nft list ruleset, iptables -S. Passes if ufw/firewalld report active, or if nftables/iptables contain a drop/reject rule or a DROP input policy. - Fix: Ubuntu: ufw default deny incoming && ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable . RHEL family: systemctl enable --now firewalld . Always allow SSH before enabling, or you will lock yourself out. - Verify: ufw status verbose or firewall-cmd --list-all or nft list ruleset - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/firewall.go ### NET-FW-002 IPv6 Listeners Without IPv6 Firewall - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Network-reachable IPv6 listeners exist without a default-deny IPv6 firewall policy. - Why: An IPv4-only firewall does not protect IPv6 services, creating an alternate path that bypasses intended filtering. - How detected: Parses /proc/net/tcp6 and udp6, then inspects nft list ruleset and ip6tables -S for an input drop or reject policy. - Fix: Apply equivalent default-deny filtering for IPv6 with nftables or ip6tables and explicitly allow required services. Caution: allow SSH and ICMPv6 neighbour discovery and router messages before applying the default deny, or remote IPv6 sessions and IPv6 connectivity are cut. - Verify: Run ip6tables -S and nft list ruleset, then confirm the input chain has a default drop or reject policy. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/network_controls.go ### NET-FW-003 Firewall Allows Sensitive Services From Anywhere - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether the host firewall explicitly opens a listening database, administrative or application server port to every address on the internet. - Why: A default-deny firewall only protects what it does not allow. A single "allow 3306 from anywhere" rule, often added while debugging, makes a database or admin panel as exposed as if there were no firewall at all. - How detected: Reads the incoming allow rules of ufw (ufw status verbose), nftables (nft list ruleset, input-hook chains) or iptables (iptables -S, INPUT and ufw chains), and matches rules with no source restriction against network-reachable listening sockets on well-known data, administrative and application server ports. - Fix: Delete the rule or restrict it to the addresses that need it, for example: ufw delete allow 3306 && ufw allow from 203.0.113.10 to any port 3306 proto tcp . Better, bind the service to 127.0.0.1 and reach it through an SSH tunnel or VPN. - Verify: ufw status numbered or nft list ruleset shows no rule opening that port to Anywhere. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/firewall_exposure.go ### NET-FW-004 Firewall Rules Open Ports With No Service - Domain: network, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks for firewall allow rules whose port has no listening service on this machine. - Why: Stale rules are holes waiting for a service. Anything later started on that port, by an administrator, a package or an attacker, becomes reachable without anyone deciding it should be. Rule sets also become impossible to review when they no longer match reality. - How detected: Compares each single-port incoming allow rule from ufw, nftables or iptables with the listening sockets in /proc/net (tcp, tcp6, udp, udp6). Port ranges are not judged, because they are often used for passive FTP or media and may be idle. - Fix: Remove rules for services that no longer exist (ufw status numbered, then ufw delete ), or note why a rule must stay. Review the firewall whenever a service is retired. - Verify: Re-run this check; every allowed port should have a listening service. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/firewall_exposure.go ### NET-FW-005 Container Ports Bypass the Host Firewall - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L - What: Checks whether Docker publishes container ports through its own iptables chains with no DOCKER-USER filtering. - Why: Docker inserts its NAT and FORWARD rules ahead of ufw and of the INPUT chain, so a port published with -p 8080:80 is reachable from the internet even when ufw says the port is denied. Administrators routinely believe the firewall protects these containers when it does not. - How detected: Reads iptables -S and looks for ACCEPT rules with a destination port in the DOCKER chain (published container ports). Fails when there are such rules and the DOCKER-USER chain contains no DROP or REJECT rule. - Fix: Publish container ports on loopback only (-p 127.0.0.1:8080:80) and put a reverse proxy in front, or add filtering to DOCKER-USER, for example: iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.0/24 -j DROP (persist it with your firewall tooling). Caution: test from an allowed address first; a wrong interface name or source range cuts access to every published container. - Verify: docker ps --format '{{.Ports}}' shows 127.0.0.1 bindings, or iptables -S DOCKER-USER shows DROP rules. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/firewall_exposure.go ### NET-FW-006 Firewall Exposure Map of Listening Services - Domain: network, severity: info, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Shows, for every network-reachable listening socket, what the host firewall does with traffic to it. - Why: Exposure is the combination of what listens and what the firewall lets through. Seeing both side by side is how assessors find services that are open by accident and confirm that the rest are blocked on purpose. - How detected: Combines the listening sockets in /proc/net with the firewall's default policy and allow rules (ufw, nftables or iptables). Each socket is labelled allowed from anywhere, allowed from restricted sources, blocked by default deny, or reachable because no filtering firewall is active. - Fix: For each socket labelled allowed from anywhere or reachable, decide whether it must be public. If not, bind it to 127.0.0.1 or restrict the firewall rule. - Verify: Re-run this check after changes and compare the map. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/firewall_exposure.go ### NET-LSN-001 Inventory of Listening Network Services - Domain: network, severity: info, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Lists every TCP and UDP port this machine listens on, and whether it is reachable from other machines. - Why: Attack surface starts with what is listening. Every exposed port should be known and justified (NIST SP 800-115, discovery phase). - How detected: Parses /proc/net/tcp, tcp6, udp and udp6. Sockets bound to 127.0.0.1 or ::1 are local only; anything else (0.0.0.0, ::, a public address) is reachable from the network unless a firewall blocks it. - Fix: Stop or uninstall services you do not need (systemctl disable --now ). Bind internal services to 127.0.0.1. Block the rest at the firewall. - Verify: ss -tulpn shows the same list with process names. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/listeners.go ### NET-MAIL-001 Postfix SMTP TLS Not Offered or Has No Certificate - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: smtpd_tls_security_level is none or unset, or is may or encrypt without a server certificate, so incoming SMTP sessions cannot be encrypted. - Why: Without STARTTLS every message and every authentication exchange that other servers and clients send to this host crosses the network in the clear. Opportunistic TLS (may) with a valid certificate is the baseline that other mail servers expect. - How detected: Reads main.cf for smtpd_tls_security_level (or the legacy smtpd_use_tls) and smtpd_tls_cert_file or smtpd_tls_chain_files. Fails when the level is none or unset, or when TLS is enabled but no certificate file is named. - Fix: Set smtpd_tls_security_level = may , smtpd_tls_chain_files = /etc/letsencrypt/live//privkey.pem, /etc/letsencrypt/live//fullchain.pem (or smtpd_tls_cert_file and smtpd_tls_key_file), then postfix reload. - Verify: postconf smtpd_tls_security_level smtpd_tls_chain_files and an authorised openssl s_client -starttls smtp -connect 127.0.0.1:25 showing a certificate. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-MAIL-002 Postfix Allows TLS 1.0 or 1.1 - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: smtpd_tls_protocols or smtpd_tls_mandatory_protocols is unset or still allows TLSv1 or TLSv1.1. - Why: The Postfix default for opportunistic TLS still admits TLS 1.0 and 1.1, which RFC 8996 deprecates. Setting a floor of TLS 1.2 is compatible with every current mail server and client. Severity is Medium rather than High because SMTP opportunistic TLS already tolerates fallback to plaintext. Postfix uses the system OpenSSL, so when its configuration already sets a floor of TLS 1.2 (MinProtocol in openssl.cnf on Debian 12 and Ubuntu 22.04 and later, or a crypto policy of DEFAULT, FUTURE or FIPS on the RHEL family) the old versions cannot be negotiated whatever main.cf says; the check then reports Pass and names the floor, and the directives remain worth setting so the configuration says what it does. - How detected: When smtpd_tls_security_level is not none, reads smtpd_tls_protocols and smtpd_tls_mandatory_protocols. Fails when either is unset or, in list form, does not exclude both TLSv1 and TLSv1.1, or, in >= form, names a version below TLSv1.2, unless /etc/crypto-policies/config or openssl.cnf (with its .include files) shows the system library already refuses TLS 1.0 and 1.1, in which case the result is Pass with the floor as evidence. Also passes with a note when inet_interfaces is loopback only (localhost, loopback-only or loopback addresses), since no network client can connect. - Fix: Add smtpd_tls_protocols = >=TLSv1.2 and smtpd_tls_mandatory_protocols = >=TLSv1.2 (Postfix 3.6 and newer; on older releases use !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 ), then postfix reload. - Verify: postconf smtpd_tls_protocols smtpd_tls_mandatory_protocols - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-MAIL-003 Postfix VRFY Command Enabled - Domain: network, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: disable_vrfy_command is not yes, so anyone who connects can ask the server whether a mailbox exists. - Why: VRFY lets a sender enumerate valid addresses one by one, which feeds spam lists and targeted phishing. Legitimate mail flow never needs it. - How detected: Reads main.cf for disable_vrfy_command. Fails when absent (default no) or set to no, unless inet_interfaces is loopback only (localhost, loopback-only or loopback addresses): then no network client can connect and the result is Pass with a note. - Fix: Add disable_vrfy_command = yes to main.cf and postfix reload. - Verify: postconf disable_vrfy_command should print yes; an authorised VRFY postmaster over a local SMTP session should return 502. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-MAIL-004 Dovecot Does Not Require TLS - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Dovecot's ssl setting is yes or no instead of required, so IMAP and POP3 clients may use unencrypted sessions. - Why: With ssl = yes, TLS is offered but a client may keep the whole session in the clear, and non-plaintext mechanisms still travel unprotected. ssl = required refuses any command before STARTTLS or implicit TLS, which makes disable_plaintext_auth (NET-DB-013) effective. - How detected: Reads dovecot.conf, conf.d/10-ssl.conf and local.conf for ssl. Fails unless the last value is required. ssl_min_protocol is reported as evidence. - Fix: Set ssl = required and ssl_min_protocol = TLSv1.2 in conf.d/10-ssl.conf, then doveadm reload. Prefer implicit TLS ports 993 and 995 for clients. Caution: clients set to plaintext stop connecting, so confirm every mail client uses TLS first. - Verify: doveconf ssl ssl_min_protocol should print required and TLSv1.2. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/data_depth.go ### NET-SVC-001 Cleartext Legacy Services Listening - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - What: Checks for Telnet, FTP, rsh/rlogin, TFTP, plain POP3/IMAP/LDAP, SNMP v1/v2c and rsync daemons on network-reachable addresses. - Why: These protocols send passwords and data unencrypted, so anyone on the path can read or alter them. Most also have weak or no authentication. - How detected: Matches network-reachable listening ports from /proc/net against well-known cleartext service ports. - Fix: Replace with encrypted equivalents: SSH/SFTP instead of Telnet/FTP/rsh, IMAPS (993)/POP3S (995), LDAPS (636), SNMPv3. Disable the old service: systemctl disable --now . - Verify: ss -tulpn should not show ports 21, 23, 69, 110, 143, 161, 389, 512-514 or 873 on public addresses. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/listeners.go ### NET-SVC-002 Control Plane or Remote Desktop Ports Network-Reachable - Domain: network, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Docker API, Kubernetes API, kubelet, etcd, VNC or RDP listen on network-reachable addresses. - Why: These services hand out administrative control of the host or the cluster. The unauthenticated Docker API and etcd give code execution or every cluster secret to whoever connects; RDP and VNC are the most attacked remote access services. - How detected: Parses local TCP and UDP socket tables and matches listeners bound to a non-loopback address against the default ports of these control planes and remote desktops. Severity follows the service identity: management panels and exporters are rated separately by NET-SVC-004. The binding (all interfaces, private or public address) is recorded as evidence. No packets are sent. - Fix: Bind control planes to loopback or a dedicated management address, require mutual TLS or strong authentication, and filter access to approved source networks or a VPN. - Verify: Review ss -tulpn and the host firewall from a trusted administrative session. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/services.go ### NET-SVC-003 Network Time Service Amplification Risk - Domain: network, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - What: NTP or chrony serves broad clients or permits legacy monitoring commands that can amplify traffic. - Why: Internet-reachable time services with broad client access or legacy mode 6 or 7 controls can be abused for reflection and amplification. - How detected: When UDP port 123 is network-reachable, reads ntp.conf or chrony.conf and flags broad allow directives or missing noquery restrictions. - Fix: Bind the time daemon to required interfaces, restrict clients to explicit networks, disable monitoring, and use noquery or equivalent controls. - Verify: Confirm UDP port 123 is filtered from untrusted networks and review the daemon's effective access restrictions. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/network_controls.go ### NET-SVC-004 Management Panel or Monitoring Ports Network-Reachable - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Webmin, cPanel, WHM, RabbitMQ Management, Consul, the Docker API with TLS or the Prometheus node exporter listen on network-reachable addresses. - Why: Management panels and exporters publish host metrics, service configuration and often a login form. They are a reconnaissance gift and a password guessing target, but they sit behind their own authentication or TLS, so the risk is lower than an open control plane. - How detected: Parses local TCP and UDP socket tables and matches listeners bound to a non-loopback address against the default ports of these management panels, exporters and discovery APIs. The binding (all interfaces, private or public address) is recorded as evidence. No packets are sent. - Fix: Bind the panel or exporter to 127.0.0.1 or a management address, put it behind a reverse proxy with authentication, or restrict the port to monitoring hosts in the firewall. - Verify: ss -tulpn shows these ports on 127.0.0.1, ::1 or a management address only. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/services.go ### NET-TLS-001 Deprecated TLS 1.0 or 1.1 Accepted - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks whether local TLS services still accept TLS 1.0 or TLS 1.1. - Why: TLS 1.0 and 1.1 were formally deprecated by RFC 8996 in 2021. They rely on outdated constructions (e.g. SHA-1 in the handshake) and fail PCI DSS and most compliance baselines. - How detected: For each local TLS port and site name, attempts a handshake with 127.0.0.1 limited to TLS 1.0 only, then TLS 1.1 only. A completed handshake fails the check. - Fix: Apache: SSLProtocol -all +TLSv1.2 +TLSv1.3 . nginx: ssl_protocols TLSv1.2 TLSv1.3; . Postfix/Dovecot: set the minimum protocol to TLSv1.2. Reload the service afterwards. - Verify: openssl s_client -connect 127.0.0.1:443 -tls1_1 2>/dev/null | openssl x509 -noout -enddate - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/network/tls.go ### NET-TLS-003 Self-Signed or Mismatched TLS Certificate - Domain: network, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N - What: Checks whether each site presents a self-signed certificate, or one that does not cover its name. - Why: Browsers reject these certificates, so visitors either leave or learn to ignore warnings, which removes protection against interception. - How detected: Reads the leaf certificate from a loopback handshake per site name. Issuer equal to subject means self-signed; the site name must appear in the certificate's DNS names (wildcards honoured). - Fix: Issue a certificate from a public CA covering every site name, for example with certbot --apache or certbot --nginx -d example.com -d www.example.com. - Verify: openssl s_client -connect 127.0.0.1:443 -servername / | grep -i '^server:' should print Server: Apache with nothing after it. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-002 Apache TRACE Method Enabled - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: TraceEnable is not set to Off, so the HTTP TRACE method echoes requests back to the client. - Why: TRACE reflects request headers, including cookies and authorisation, which older cross-site tracing attacks used to read them from the browser. No production site needs it. - How detected: Reads the Apache configuration tree for TraceEnable. Fails when the directive is absent (default On) or set to On or Extended. - Fix: Add TraceEnable Off to the server configuration and reload. - Verify: curl -s -o /dev/null -w '%{http_code}' -X TRACE https:/// should print 405. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-003 Apache Directory Indexes Enabled in Configuration - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: An Options directive enables Indexes (or All), so folders without an index page list their files. - Why: Directory listings publish uploads, backups and scripts that were never linked. Debian ships Indexes on in the default /var/www block, so a fresh install fails this until changed. - How detected: Reads every Options directive in the Apache configuration tree and flags values that enable Indexes as a plain token, +Indexes or All. - Fix: Change the block to Options -Indexes +FollowSymLinks (or remove Indexes). Where a listing is wanted, protect that Directory with Require. - Verify: apachectl -t -D DUMP_CONFIG 2>/dev/null | grep -n Options should show no Indexes except with a leading minus. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-004 Apache AllowOverride All Permits .htaccess Control - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: A Directory block sets AllowOverride All, so any .htaccess file under it can change handlers, options and access rules. - Why: With AllowOverride All, whoever can write a file into the document root (an upload bug, a compromised CMS) can turn images into executable scripts or lift access controls. Limiting overrides keeps those decisions in root-owned configuration. - How detected: Reads every AllowOverride directive in the Apache configuration tree and flags the value All. None or a short explicit list passes. - Fix: Set AllowOverride None and move needed rules into the vhost, or allow only what the application needs, for example AllowOverride FileInfo Indexes Limit for WordPress permalinks. - Verify: grep -rn AllowOverride /etc/apache2 /etc/httpd 2>/dev/null should show no All. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-005 Apache Allows TLS 1.0 or 1.1 - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - What: SSLProtocol permits SSLv3, TLSv1 or TLSv1.1, or is not set on a TLS-enabled server (the default all -SSLv3 allows TLS 1.0 and 1.1). - Why: TLS 1.0 and 1.1 are deprecated by RFC 8996 and lack the protections of TLS 1.2 and 1.3. Apache's default still allows them, so an explicit SSLProtocol is required. The directive is not the last word: mod_ssl uses the system OpenSSL, and when its configuration already sets a floor of TLS 1.2 (MinProtocol in openssl.cnf on Debian 12 and Ubuntu 22.04 and later, or a crypto policy of DEFAULT, FUTURE or FIPS on the RHEL family) the old versions cannot be negotiated whatever the directive lists. In that case the check reports Pass and names the floor, because rating a server High for a line that has no effect would contradict the live handshake checks (WEB-TLS). The directive is still worth tidying so the configuration says what it does. - How detected: Reads every SSLProtocol directive and expands all , + and - tokens. Any resulting version below TLSv1.2 fails, as does an absent SSLProtocol when SSLEngine on or SSLCertificateFile is present, unless /etc/crypto-policies/config or openssl.cnf (with its .include files) shows the system library already refuses TLS 1.0 and 1.1, in which case the result is Pass with the floor as evidence. - Fix: Set SSLProtocol -all +TLSv1.2 +TLSv1.3 (Mozilla intermediate) in mods-enabled/ssl.conf or ssl.conf, then apachectl configtest and reload. - Verify: openssl s_client -connect 127.0.0.1:443 -servername -tls1_1 should fail to connect. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-006 Apache Cipher Suite Includes Weak Suites - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - What: SSLCipherSuite includes NULL, export, DES or 3DES, RC4, MD5, anonymous or other legacy suites, or SSLHonorCipherOrder forces a legacy order. - Why: Weak suites allow recorded traffic to be decrypted or sessions to be hijacked. The Mozilla guidelines give a short explicit list that modern clients all support. - How detected: Reads every SSLCipherSuite directive and flags OpenSSL tokens naming NULL, EXPORT, DES, RC4, MD5, aNULL, ADH, AECDH, PSK, SRP, IDEA, SEED or CAMELLIA, and bare ALL or DEFAULT. Tokens excluded with ! or - are ignored. SSLHonorCipherOrder on is reported as evidence. - Fix: Use the Mozilla intermediate list for SSLCipherSuite and set SSLHonorCipherOrder off , as the Mozilla SSL Configuration Generator produces for Apache. - Verify: Run the nmap ssl-enum-ciphers script against 127.0.0.1 port 443 under your own test plan; it should list only grade A suites. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-007 Apache OCSP Stapling Not Enabled - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: A TLS-enabled Apache does not set SSLUseStapling on. - Why: Without stapling, every browser asks the certificate authority whether the certificate is revoked, which slows the handshake and reveals the visited site to the CA. Stapling lets the server attach a fresh signed status itself. - How detected: When SSLEngine on or SSLCertificateFile is present, reads SSLUseStapling and SSLStaplingCache. Fails unless SSLUseStapling is on. - Fix: Add SSLUseStapling On inside the vhost and SSLStaplingCache "shmcb:logs/ssl_stapling(32768)" outside it, as the Mozilla generator produces. - Verify: openssl s_client -connect 127.0.0.1:443 -servername -status should print OCSP Response Status: successful. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-008 Apache Has No Request Filtering Module - Domain: web, severity: info, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Neither mod_security (ModSecurity) nor mod_evasive is loaded, so Apache has no request filtering or flood control in front of the application. - Why: A web application firewall or flood limiter is a defence-in-depth layer, not a requirement, and a misconfigured one breaks sites. The result is informational so you can decide with the application owner. - How detected: Looks for LoadModule security2_module or evasive20_module in the configuration tree, or security2.load and evasive.load under mods-enabled. - Fix: Consider ModSecurity with the OWASP Core Rule Set in detection-only mode first, or mod_evasive for simple rate limiting, and tune before blocking. - Verify: apachectl -M 2>/dev/null | grep -E 'security2|evasive' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-009 Apache ETag Reveals Inode Numbers - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: FileETag includes INode (or All), so ETag headers leak filesystem inode numbers of served files. - Why: Inode numbers help distinguish servers behind a load balancer and have been used to infer file identity across hosts (CVE-2003-1418). The Apache 2.4 default of MTime Size does not include them. - How detected: Reads every FileETag directive and flags values containing INode or All. - Fix: Set FileETag MTime Size or FileETag None and reload. - Verify: curl -sI https:///robots.txt | grep -i etag should show a two-part value, not three. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-010 Apache Serves Repository and Environment Files - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: No DirectoryMatch, FilesMatch or RedirectMatch rule denies .git, .svn, .hg or .env paths, so they are served if present under a document root. - Why: Repository folders rebuild the source code and its history; .env files hold database passwords and application keys. Debian's default only protects .ht* files. One rule for every dot path removes the whole class, including files deployed by mistake later. The rating is Low because this is a missing safety net, not an exposure: when such a file is actually present under a document root, WEB-APP-009 and WEB-DOC-001 report it at their own higher severity, and when none is present the check passes and only notes the missing rule. - How detected: Looks for a block or rule whose pattern covers dot-prefixed names: DirectoryMatch or LocationMatch with a dot pattern, FilesMatch starting with ^\. , or RedirectMatch for .git or .env. When none exists, checks each configured document root (metadata only, symlinks not followed) for a .git, .svn, .hg, .env or .env.* entry; Fails at Low when one is found and the rule is missing, Passes with a note when none is found. - Fix: Add to the server configuration: Require all denied and Require all denied , then reload. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///.git/HEAD should print 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APA-011 Apache Has No Slow Request Protection - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L - What: mod_reqtimeout does not appear to be loaded and no RequestReadTimeout is set, so a client that sends its request very slowly can hold a worker for the whole Timeout. - Why: Apache keeps one worker busy for each open request. Without minimum read rates, a few clients sending headers a byte at a time (the Slowloris technique) can use up every worker and take the site offline. The module is enabled by default on most distributions, so its absence usually means it was removed. - How detected: Looks for LoadModule reqtimeout_module or a RequestReadTimeout directive in the configuration tree, or reqtimeout.load under mods-enabled. A module compiled into the server binary does not appear in the configuration, so confirm with the Verify command. Configuration is only read; no slow request is sent. - Fix: Enable the module (Debian and Ubuntu: a2enmod reqtimeout; other systems: LoadModule reqtimeout_module modules/mod_reqtimeout.so) and keep or set RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500 , then reload. - Verify: apachectl -M 2>/dev/null | grep reqtimeout - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-APP-001 Laravel Debug Mode On or Application Key Missing - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks Laravel .env files for APP_DEBUG=true or an empty APP_KEY. - Why: With APP_DEBUG on, Laravel's error page prints the environment, including database and mail passwords, to any visitor who triggers an error. APP_KEY encrypts sessions and cookies; with it empty or default the application refuses to run or signs nothing. - How detected: Finds .env files in /srv, /opt, /var/www, home folders and web roots (three folders deep) that contain APP_KEY (the Laravel marker) and fails when APP_DEBUG is true or APP_KEY is empty. Values are never reported. - Fix: Set APP_DEBUG=false and APP_ENV=production in .env, run php artisan key:generate if APP_KEY is empty, then php artisan config:cache. Keep .env outside the web root (Laravel's public/ folder) and readable only by the application user. - Verify: grep -E '^APP_(DEBUG|KEY)=' .env shows APP_DEBUG=false and a base64 key. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-002 Application .env File Readable by All Users - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N - What: Checks the permission bits of framework .env files for world-read access. - Why: .env files hold database passwords, API tokens and signing keys in plain text. A world-readable .env hands every account on the server, including other websites' PHP-FPM pools, the keys to this application. - How detected: Stats each .env file found in the project folders (metadata only) and fails when the 'other read' permission bit is set. - Fix: chmod 640 .env && chown : .env (or 600 when the application runs as its own user). Rotate the secrets if other people have had shell access. - Verify: stat -c '%a %U:%G %n' .env shows 640 or 600. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-003 Symfony Running in the Development Environment - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks Symfony .env files for APP_ENV=dev or APP_DEBUG=1. - Why: The dev environment enables the web profiler and debug toolbar, which show requests, queries, configuration, the container and sometimes credentials to any visitor, and it skips the caches that production relies on. - How detected: Finds .env files that contain APP_SECRET (the Symfony marker) and fails when APP_ENV is dev or test, or APP_DEBUG is 1 or true. A .env.local or .env.prod override is not evaluated, so confirm the final value on the server. - Fix: Set APP_ENV=prod and APP_DEBUG=0 in .env (or .env.local), run composer dump-env prod, clear the cache (php bin/console cache:clear) and remove the web profiler bundle from production. - Verify: php bin/console about shows Environment prod and Debug false. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-004 Rails Master Key or Secrets File Exposed - Domain: web, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - What: Checks Rails config/master.key, credentials key files and secrets.yml for world-read permissions or a location inside a web root. - Why: master.key decrypts credentials.yml.enc, which holds every production secret, and secrets.yml holds the secret_key_base that signs sessions. Anyone who reads them can forge sessions and read the database password. Both are meant to stay out of version control and out of any public folder. - How detected: Finds config/master.key, config/credentials/*.key and config/secrets.yml in the project folders and fails when the file is readable by others or sits inside a web document root. Contents are never read. - Fix: chmod 600 config/master.key config/credentials/*.key; keep them out of git (.gitignore) and out of public/; load the key from RAILS_MASTER_KEY in the environment on servers. Rotate the credentials if the key was ever exposed. - Verify: stat -c '%a %n' config/master.key shows 600, and git ls-files config/master.key prints nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-005 Rails Production Does Not Force HTTPS - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N - What: Checks config/environments/production.rb for config.force_ssl = true. - Why: force_ssl redirects plain HTTP to HTTPS, marks cookies Secure and sends Strict-Transport-Security. Without it a session cookie can travel over HTTP once and be read on the network, even when the site also works over HTTPS. - How detected: Finds config/environments/production.rb in the project folders and fails when config.force_ssl is false or absent (comments removed). An assume_ssl-only setup still fails because cookies are not marked Secure. - Fix: Set config.force_ssl = true in config/environments/production.rb (and config.assume_ssl = true behind a TLS-terminating proxy), then restart the application. - Verify: grep -n force_ssl config/environments/production.rb shows true, and curl -I http:/// returns a 301 to https. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-006 Spring Boot Actuator Endpoints Fully Exposed - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks Spring Boot application properties for management.endpoints.web.exposure.include=* and probes the local site for an open /actuator/env. - Why: Actuator's env, configprops, heapdump and threaddump endpoints print environment variables, configuration (including credentials) and process memory. Exposing them all over HTTP without authentication is one of the most common Java application leaks. - How detected: Reads application.properties, application.yml and their -prod variants in the project folders for exposure.include set to * (or a list containing env, heapdump or configprops) with no management.server.port moving them off the public port. Also requests /actuator/env on each local site and fails on a JSON reply containing propertySources. - Fix: Set management.endpoints.web.exposure.include=health,info (and exclude env,heapdump,configprops), move management.server.port to a port that is not published, and require authentication for every actuator endpoint with Spring Security. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///actuator/env prints 401, 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-007 Flask Debug Mode Enabled in Application Code - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Looks for app.run(debug=True) or app.debug = True in Flask entry files. A heuristic; the service check WEB-PY-004 covers the environment switches. - Why: Debug mode in code follows the application wherever it is deployed. If the file is ever run directly instead of through a WSGI server, the Werkzeug debugger and its code console come up for every visitor. - How detected: Reads app.py, wsgi.py, run.py, main.py and server.py in the project folders (files that import flask) and flags debug=True inside app.run(...) or an app.debug = True assignment, ignoring commented lines. Rated Low because a production WSGI server ignores app.run(). - Fix: Remove debug=True from app.run() and read it from the environment for local work only (app.run(debug=os.environ.get('FLASK_DEBUG') == '1')); serve production with Gunicorn or uWSGI, which never call app.run(). - Verify: grep -n 'debug' app.py shows no hard-coded True. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-008 Dependency Lock Files Older Than a Year - Domain: web, severity: info, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Reports composer.lock, package-lock.json, yarn.lock, Gemfile.lock and poetry.lock files not modified for 365 days or more (dependency freshness). Informational. - Why: A lock file pins exact library versions. One untouched for a year means the application has had no dependency updates in that time, so every library vulnerability published since is still present. The check cannot tell whether a specific vulnerability applies; the known-vulnerability checks and composer audit or npm audit do that. - How detected: Stats each lock file found in the project folders (metadata only) and reports those whose modification time is 365 days or older. Files whose time cannot be read are reported as incomplete. - Fix: Run composer update / npm update / bundle update / poetry update in a test environment, run the audit command of each ecosystem (composer audit, npm audit, bundle audit, pip-audit) and deploy. Schedule dependency updates at least quarterly. - Verify: stat -c '%y %n' composer.lock package-lock.json shows a recent date. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-009 Git Repository Stored Inside a Web Root - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Looks on disk for a .git folder directly inside each web document root. - Why: A .git folder in the document root is one web server rule away from being downloadable, which rebuilds the full source and its history, often with committed credentials. WEB-EXP-001 tests whether it is already reachable; this check finds it before a configuration change exposes it. - How detected: Stats /.git/HEAD for each local document root (metadata only). Fails when it exists, even if the web server currently blocks dot-folders. - Fix: Deploy from a build step that copies only the needed files (git archive, rsync --exclude .git, or a CI artefact), or point the document root at a public/ sub-folder as Laravel, Symfony and Rails expect. Remove the .git folder from the web root and rotate any secret ever committed. - Verify: ls -d /.git prints nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-APP-010 Framework Writable Folders Open to All Users - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L - What: Checks Laravel and Symfony cache, log and storage folders for world-write permission. - Why: chmod -R 777 storage is a common fix for permission errors. Laravel's bootstrap/cache and Symfony's var/cache hold compiled PHP that the framework includes on every request, so any local user who can write there runs code as the web server. Logs and sessions in storage are readable and forgeable too. - How detected: For each Laravel (.env with APP_KEY) or Symfony (.env with APP_SECRET) project, stats storage, storage/logs, storage/framework, bootstrap/cache, var/cache and var/log next to the .env file (metadata only) and fails when any has the 'other write' bit set. - Fix: chmod -R o-w storage bootstrap/cache var and give the web server group write access instead: chgrp -R www-data storage bootstrap/cache && chmod -R g+w storage bootstrap/cache. - Verify: find storage bootstrap/cache var -perm -0002 -type d prints nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/frameworks.go ### WEB-CK-001 Cookies Missing Secure, HttpOnly or SameSite - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N - What: Checks the security flags on cookies the home page sets. - Why: Without Secure, cookies leak over plain HTTP. Without HttpOnly, any XSS bug can steal sessions. Without SameSite, cross-site request forgery is easier. - How detected: Inspects every Set-Cookie header on each site's home page for the Secure (HTTPS sites), HttpOnly and SameSite attributes. Cookies being deleted (expired or Max-Age 0) are ignored, and anti-CSRF cookies such as XSRF-TOKEN are not required to be HttpOnly because scripts must read them. Cookies set on redirect responses are evaluated too, because browsers store them. Cookie values are never recorded. - Fix: PHP: session.cookie_secure=1, session.cookie_httponly=1, session.cookie_samesite=Lax in php.ini . Frameworks have equivalent settings (e.g. Laravel config/session.php). Use HttpOnly for every cookie JavaScript does not need to read. - Verify: curl -sI https:/// | grep -i set-cookie - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/transport.go ### WEB-CMS-001 WordPress Version Disclosed - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether WordPress sites reveal their exact version in the page or in readme.html. - Why: The version number lets automated tools pick matching exploits for outdated cores and plugins. - How detected: Looks for the WordPress generator meta tag on the home page and requests /readme.html, which counts only when it carries the WordPress readme signature (the logo heading and a Version line), so a soft-404 page that merely mentions WordPress does not fire. - Fix: Keep WordPress updated (automatic minor updates on). Remove the generator tag (remove_action('wp_head','wp_generator')) and delete readme.html after each update, or block it at the web server. - Verify: curl -s https:/// | grep -i 'name="generator"' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms.go ### WEB-CMS-002 WordPress XML-RPC Interface Enabled - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - What: Checks whether xmlrpc.php answers on WordPress sites. - Why: XML-RPC allows many password guesses in one request (system.multicall) and is abused for pingback amplification. Few sites still need it. - How detected: Sends one GET to /xmlrpc.php; the standard reply 'XML-RPC server accepts POST requests only.' confirms it is enabled. No XML-RPC call is made. - Fix: Block it at the web server ( Require all denied ) or disable it with a security plugin, unless Jetpack or the mobile app needs it. - Verify: curl -s https:///xmlrpc.php should return 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms.go ### WEB-CMS-003 WordPress User Accounts Enumerable - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether the WordPress REST API lists user accounts to anonymous visitors. - Why: A list of login names halves the work of password guessing and phishing against administrators. - How detected: Requests /wp-json/wp/v2/users and fails when it returns a JSON array with user slugs. No login is attempted. - Fix: Restrict the users endpoint to authenticated requests with a security plugin or a small rest_endpoints filter, and use login names that differ from public display names. - Verify: curl -s https:///wp-json/wp/v2/users should return 401, 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-CMS-004 CMS Configuration Readable by All Users - Domain: web, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - What: Checks whether wp-config.php, Joomla configuration.php or Drupal settings.php can be read by every local user. - Why: These files hold the database password and the keys that sign logins. If every account on the machine can read them, any other site, user or compromised process on the server can take over the CMS and its data. - How detected: Finds the configuration files through the local web roots (WordPress also one folder above the root) and reads their permission bits. Fails when the world-read bit is set. - Fix: chmod 640 and chown it to the site owner with the web server's group (for example chown deploy:www-data wp-config.php); on PHP-FPM pools that run as the site user, 600 or 400 also works. Rotate the database password if other people have shell access. - Verify: stat -c '%a %U:%G %n' shows 640, 600 or 400. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_more.go ### WEB-CMS-005 CMS Configuration Writable by Other Users - Domain: web, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - What: Checks whether wp-config.php, Joomla configuration.php or Drupal settings.php is group- or world-writable. - Why: These files are PHP code that runs on every request. Whoever can write them can add a backdoor that runs as the web server, which is how many shared-hosting compromises spread from one site to the next. - How detected: Finds the configuration files through the local web roots and fails when the group-write or world-write permission bit is set. - Fix: chmod 640 (or 440) . Drupal also expects sites/default to be 555 after installation. Review the file for code you did not add. - Verify: stat -c '%a %n' shows no write bit for group or others. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_more.go ### WEB-CMS-006 WordPress Shows Debug Output to Visitors - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks wp-config.php for WP_DEBUG enabled without WP_DEBUG_DISPLAY set to false. - Why: With WP_DEBUG on and display not turned off, PHP notices and errors are printed into pages, revealing server paths, plugin names and sometimes database details to anyone browsing the site. - How detected: Reads define('WP_DEBUG', ...) and define('WP_DEBUG_DISPLAY', ...) from each wp-config.php found through the web roots. - Fix: In production set define('WP_DEBUG', false);. If you need logs, use define('WP_DEBUG_LOG', '/path/outside/webroot/wp-debug.log'); together with define('WP_DEBUG_DISPLAY', false);. - Verify: grep -E "WP_DEBUG(_DISPLAY)?'" wp-config.php - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_more.go ### WEB-CMS-007 WordPress Dashboard File Editor Enabled - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N - What: Checks whether wp-config.php leaves the built-in theme and plugin file editor enabled. - Why: The editor lets any administrator account change PHP files from the browser. A stolen or phished admin session then becomes code execution on the server in one step. - How detected: Reads each wp-config.php found through the web roots and fails unless DISALLOW_FILE_EDIT or DISALLOW_FILE_MODS is defined as true. - Fix: Add define('DISALLOW_FILE_EDIT', true); to wp-config.php above the line that says to stop editing. - Verify: The Theme File Editor and Plugin File Editor menus disappear from the dashboard. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_more.go ### WEB-CMS-008 WordPress Automatic Updates Disabled - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L - What: Checks whether wp-config.php turns off WordPress background updates, including security releases. - Why: WordPress ships security fixes as automatic minor updates. Turning the updater off means the site stays vulnerable until someone remembers to update by hand, which is how most compromised WordPress sites fall behind. - How detected: Reads each wp-config.php found through the web roots and fails when AUTOMATIC_UPDATER_DISABLED is true or WP_AUTO_UPDATE_CORE is false. - Fix: Remove those lines, or set define('WP_AUTO_UPDATE_CORE', 'minor'); so at least security releases install automatically. Keep backups and enable plugin auto-updates where practical. - Verify: Dashboard, Updates shows that automatic updates for maintenance and security releases are on. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_more.go ### WEB-CMS-009 Joomla Debug or Verbose Error Reporting Enabled - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks Joomla configuration.php for debug mode or maximum/development error reporting. - Why: Joomla's debug console and verbose error reporting print queries, file paths and configuration details into pages that visitors can see. - How detected: Reads configuration.php found through the web roots and fails when $debug is 1 or $error_reporting is maximum or development. - Fix: In System, Global Configuration set Debug System to No and Error Reporting to None (or System Default), or set public $debug = false; and public $error_reporting = 'none'; in configuration.php. - Verify: grep -E 'debug|error_reporting' configuration.php - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_more.go ### WEB-CMS-010 Joomla Version Disclosed - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether a Joomla site serves its version manifest at /administrator/manifests/files/joomla.xml. - Why: The manifest gives the exact Joomla version, which lets automated tools pick exploits for known vulnerabilities, such as the unauthenticated information disclosure CVE-2023-23752. - How detected: Sends one GET for /administrator/manifests/files/joomla.xml and fails on a 200 response that contains a element. - Fix: Block the folder at the web server (Apache: Require all denied ; nginx: location ^~ /administrator/manifests/ { deny all; }) and keep Joomla updated. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///administrator/manifests/files/joomla.xml prints 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_more.go ### WEB-CMS-011 Drupal Version Disclosed by Changelog - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether a Drupal site serves CHANGELOG.txt, which names the exact Drupal version. - Why: The changelog tells attackers precisely which Drupal security advisories apply, as was used at scale for Drupalgeddon 2 (CVE-2018-7600). - How detected: Sends one GET each for /CHANGELOG.txt and /core/CHANGELOG.txt and fails on a 200 response that mentions Drupal. - Fix: Delete the changelog files after each update, or deny *.txt files in the Drupal root and core folders at the web server, and keep Drupal updated. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///core/CHANGELOG.txt prints 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_more.go ### WEB-CMS-012 Drupal Core Past End of Life - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Reads the installed Drupal core version from disk and compares its major release with the Drupal security coverage dates. - Why: Drupal 7, 8 and 9 no longer receive security advisories from the Drupal Security Team. Drupalgeddon (CVE-2014-3704) and Drupalgeddon 2 (CVE-2018-7600) showed how quickly unpatched Drupal sites are taken over at scale. - How detected: Reads core/lib/Drupal.php (const VERSION) or includes/bootstrap.inc (define('VERSION')) under each local web root and fails when the major release's end-of-life date has passed: 7 (2025-01-05), 8 (2020-11-02), 9 (2023-11-01). - Fix: Upgrade to a supported Drupal major with composer (drupal/core-recommended), run drush updatedb and test the site; for Drupal 7, migrate with the Migrate module or commission a rebuild. - Verify: drush status --field=drupal-version or grep VERSION core/lib/Drupal.php - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_depth.go ### WEB-CMS-013 Drupal Trusted Host Patterns Not Set - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N - What: Checks Drupal settings.php for the trusted_host_patterns setting. - Why: Without trusted_host_patterns Drupal builds absolute links, including password reset links, from whatever Host header the request carries. An attacker who sends a crafted Host header can poison caches and redirect reset links to a domain they control. - How detected: Reads each sites/*/settings.php found through the web roots (comments removed) and fails when $settings['trusted_host_patterns'] is not assigned. - Fix: Add to settings.php: $settings['trusted_host_patterns'] = ['^www\.example\.com$', '^example\.com$']; then clear caches (drush cr). The status report page should no longer warn about trusted hosts. - Verify: drush status-report | grep -i 'trusted host' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_depth.go ### WEB-CMS-014 Joomla Core Past End of Life - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Reads the installed Joomla version from administrator/manifests/files/joomla.xml on disk and compares it with the Joomla support dates. - Why: Joomla 3 left security support in August 2023 and Joomla 4 in October 2025. Sites on those series receive no fixes for new core vulnerabilities, while the exact version remains easy to fingerprint. - How detected: Reads from administrator/manifests/files/joomla.xml under each local web root and fails when the series' end-of-support date has passed (1.5, 1.6, 1.7, 2.5, 3.x, 4.x). Joomla 5 and later pass. - Fix: Update to the current Joomla series through the Joomla Update component (back up first, check extension compatibility, then update), and keep the one-click updater enabled. - Verify: grep '' administrator/manifests/files/joomla.xml - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_depth.go ### WEB-CMS-015 Magento Not in Production Mode - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks app/etc/env.php for a MAGE_MODE other than production. - Why: Developer and default modes show exceptions with stack traces and file paths to shoppers, disable static file signing and compile code on the fly. Adobe documents production as the only mode for a live store. - How detected: Reads app/etc/env.php in each local web root and its parent folder (Magento serves from pub/) and fails when 'MAGE_MODE' is developer or default. - Fix: bin/magento deploy:mode:set production (which also deploys static content and compiles DI), then clear caches. Keep developer mode for staging only. - Verify: bin/magento deploy:mode:show prints production. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_depth.go ### WEB-CMS-016 Ghost Configured With a Plain HTTP Site URL - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N - What: Checks Ghost config.production.json for a url that starts with http://. - Why: Ghost builds every link, including the admin login and member sign-in links, from the configured url. With http:// the admin session and members' tokens travel in the clear even when HTTPS is available, and browsers see mixed content. - How detected: Finds config.production.json files in the project folders that look like Ghost configuration (database and server keys) and fails when url begins with http://. - Fix: ghost config url https:// (or edit config.production.json), set up TLS with ghost setup ssl or your reverse proxy, then ghost restart. - Verify: ghost config get url prints an https:// address. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_depth.go ### WEB-CMS-017 WordPress Cron Reachable by Visitors - Domain: web, severity: info, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Reports whether /wp-cron.php answers to anonymous requests on WordPress sites. Informational. - Why: WP-Cron runs scheduled tasks when visitors load pages, and wp-cron.php can be requested directly. On busy or targeted sites that lets anyone make the server do work on demand, and it is a frequent target for resource exhaustion; a system cron job is the documented production setup. - How detected: Sends one GET for /wp-cron.php on each local site and reports a 200 response with an empty or non-HTML body (what wp-cron.php returns). Sites whose wp-config.php sets DISABLE_WP_CRON to true and block the file pass. - Fix: Add define('DISABLE_WP_CRON', true); to wp-config.php, run */5 * * * * www-data php /var/www//wp-cron.php from the system cron (or curl the URL from cron), and deny direct requests to wp-cron.php at the web server. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///wp-cron.php prints 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_depth.go ### WEB-CMS-018 WordPress Author Archive Reveals Login Names - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether /?author=1 redirects to an author archive, which exposes the first account's login name in the address. - Why: The author archive address contains the user's slug, which by default equals the login name. Together with the REST API listing (WEB-CMS-003) this gives attackers the administrator's user name for password guessing without touching the login form. - How detected: Sends one GET for /?author=1 and fails on a 301 or 302 whose Location header contains /author/. The name itself is not written to the report. - Fix: Return 404 for author queries by id (a small redirect rule or a security plugin's user enumeration protection), and give administrators a nickname and display name that differ from the login name. - Verify: curl -sI https:///?author=1 | grep -i location prints nothing or a 404 page. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_depth.go ### WEB-CMS-019 Drupal update.php Open Without Authentication - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H - What: Checks Drupal settings.php for $settings['update_free_access'] = TRUE. - Why: update_free_access lets anyone run update.php, which executes database update hooks as the site. It exists for one-off maintenance and Drupal's own status report warns when it is left on. - How detected: Reads each sites/*/settings.php found through the web roots (comments removed) and fails when update_free_access is TRUE. - Fix: Set $settings['update_free_access'] = FALSE; in settings.php (the default) and run updates as user 1 or with drush updatedb. - Verify: grep update_free_access sites/default/settings.php shows FALSE. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cms_depth.go ### WEB-COR-001 Cross-Origin Resource Sharing Allows Any Origin With Credentials - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N - What: Checks the home page's CORS headers for a wildcard origin combined with credentials, or an origin of null. - Why: A permissive CORS policy can let any website read responses on behalf of a logged-in visitor, exposing personal data and tokens. - How detected: Reads Access-Control-Allow-Origin and Access-Control-Allow-Credentials on each site's home page. '*' with credentials true, or 'null', fails. Origin reflection needs crafted requests and is outside this read-only check. - Fix: Return Access-Control-Allow-Origin only for an explicit allow-list of trusted origins, never '*' or 'null' together with credentials, and add Vary: Origin. - Verify: curl -sI https:/// | grep -i access-control - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/docroot.go ### WEB-CVE-001 Vulnerable Web Application Libraries - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Matches local web library versions against offline OSV advisories; WordPress core is inventoried and reported Incomplete because OSV publishes no WordPress feed. - Why: Unpatched web libraries can expose users and server data to known vulnerabilities. - How detected: Reads lockfiles (Packagist composer.lock, npm package-lock.json, PyPI poetry.lock, Pipfile.lock and exact pins in requirements.txt, RubyGems Gemfile.lock, Go go.mod and crates.io Cargo.lock; only package names and versions are kept), wp-includes/version.php (inventoried as WordPress:Core; OSV publishes no WordPress export, so a real snapshot has no data for it) and jQuery file headers under configured document roots, and matches them against the snapshot. Each match names its matcher: exact version, range, or heuristic for a version read from a file header. Inventory the snapshot has no advisory data for (for example WordPress core, for which OSV publishes no feed, or the test fixture snapshot of a source build) is reported Incomplete, never Pass. - Fix: Update the affected library or WordPress core and deploy a refreshed lockfile or asset. For a source build, run 'make vulndb' to embed a generated snapshot. - Verify: Re-run OpenVAPT and verify that the vulnerable version is no longer deployed. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/cve.go ### WEB-DOC-001 Secrets or Backups Stored Inside a Web Root - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Looks on disk inside each web document root for environment files, database dumps, backups, archives and private keys. - Why: Anything inside a document root may be downloadable, now or after a configuration change. Dumps, backups and keys there are a leading cause of data breaches; finding them on disk catches files the URL checks cannot guess. - How detected: Reads document roots from the local Apache, nginx and LiteSpeed configuration, walks each one (metadata only, up to 50000 files, symlinks not followed) and matches file names such as .env, *.sql, *.bak, backup*.tar.gz, id_rsa, *key*.pem and *.key. File contents are never read. Plain archives and certificates (*.tar.gz, *.pem) are reviewed separately at Medium by WEB-DOC-003, because they are often published on purpose. - Fix: Move each listed file outside the document root (for example to a 700 folder in the account's home), delete what is not needed, and rotate any secret or key that was exposed. Keep backups in a dedicated location outside every web root. - Verify: Re-run this check; no files should be listed. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/docroot.go ### WEB-DOC-002 Web Root Files Writable by Other Users - Domain: web, severity: high, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - What: Looks for world-writable files inside web document roots. - Why: A world-writable PHP or HTML file lets any local user, or any other compromised site on the same server, plant a web shell or inject malware into pages served to visitors. - How detected: Walks each document root (metadata only, up to 50000 files, symlinks not followed) and lists regular files with the 'other write' permission bit. - Fix: chmod o-w for each listed file (find -type f -perm -0002 -exec chmod o-w {} +). Use 644 for files and 755 for folders, owned by the site account, and grant the web server write access only to upload folders. - Verify: find -type f -perm -0002 should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/docroot.go ### WEB-DOC-003 Archives or Certificates Inside a Web Root - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Lists archives (*.tar.gz, *.tgz, *.tar, *.7z) and certificate files (*.pem, *.crt) inside each web document root for review, outside download folders. - Why: A tarball inside a web root is often a site or database backup left after a migration, and a .pem file sometimes carries the private key next to the certificate. Both are downloadable by anyone who guesses the name. But download sites publish archives on purpose and public certificates are harmless, so this is a Medium review item, not a High finding; names that clearly mean backup, dump or key are rated High by WEB-DOC-001. - How detected: Walks each document root (metadata only, up to 50000 files, symlinks not followed) and lists files matching the archive and certificate patterns that WEB-DOC-001 does not already rate High. Files under /download/, /downloads/, /releases/, /dist/ or /.well-known/ are noted as expected publications and do not fail. File contents are never read. - Fix: Confirm each file is meant to be public. Move backups and anything holding a private key outside the document root, rotate any exposed key, and keep published downloads in a dedicated folder such as /downloads/. - Verify: Re-run this check; only files you have confirmed as intended downloads should remain, in a download folder. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/docroot.go ### WEB-EXP-001 Git Repository Exposed on the Website - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks whether the .git folder can be downloaded from each site. - Why: An exposed .git folder lets anyone rebuild the full source code and history, often including passwords, API keys and database credentials committed by mistake. - How detected: Requests /.git/HEAD and /.git/config. A 200 response containing 'ref:' or '[core]' confirms exposure. - Fix: Block dot-folders in the web server (Apache: RedirectMatch 404 /\.git ; nginx: location ~ /\.(?!well-known) { deny all; }) and deploy without the .git folder in the web root. Rotate any secret that was ever committed. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///.git/HEAD should print 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-002 Environment or Secrets File Exposed - Domain: web, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether .env and similar configuration files can be downloaded. - Why: Framework .env files hold database passwords, mail credentials, encryption keys and API tokens. Downloading one usually means full compromise of the application and its data. - How detected: Requests /.env, /.env.production, /config.php.bak and /wp-config.php.bak. A non-HTML 200 response containing typical key=value secrets (DB_, APP_KEY, DB_PASSWORD) confirms exposure. Contents are redacted in the report. - Fix: Move secrets out of the web root, block dot-files at the web server, delete backup copies, then rotate every secret in the file because it must be treated as leaked. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///.env should print 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-003 PHP Information Page Exposed - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks for phpinfo() pages left on the site. - Why: phpinfo() reveals PHP and module versions, file paths, environment variables and sometimes credentials, giving attackers a detailed map of the server. - How detected: Requests /phpinfo.php, /info.php, /php_info.php and /test.php; a 200 response containing the 'PHP Version' heading of phpinfo() confirms it. - Fix: Delete the file. Never leave diagnostic pages on a production server. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///phpinfo.php should print 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-004 Backup or Database Dump Files Downloadable - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks for common backup archives and SQL dumps left in the web root. - Why: Backups contain the whole site and often the whole database, including customer data and password hashes. - How detected: Requests a short fixed list of common names (backup.zip, backup.tar.gz, site.zip, db.sql, dump.sql, database.sql). A 200 response that is not an HTML page is reported. Only the first 64 KiB are read. - Fix: Move backups outside the web root (for example /home//deploy-backups/ with mode 700), delete the exposed copies and treat their contents as leaked. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///backup.zip should print 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-005 Directory Listing Enabled - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether the web server lists folder contents when there is no index page. - Why: Directory listings reveal every file in a folder, including backups, uploads and scripts never meant to be linked. - How detected: Requests /, /uploads/, /images/, /files/, /backup/ and /wp-content/uploads/ and looks for the auto-index page markers of Apache ('Index of /'), nginx and LiteSpeed. - Fix: Apache: Options -Indexes . nginx: autoindex off; (the default). Add an empty index.html to folders that must stay browsable-free. - Verify: curl -s https:///uploads/ | grep -i 'index of' should print nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-006 Web Server Status Page Exposed - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether Apache server-status/server-info or nginx stub_status are reachable for each site. - Why: Status pages show live requests (including URLs with tokens), client IPs, virtual hosts and modules. - How detected: Requests /server-status, /server-info and /nginx_status for each site through 127.0.0.1. Because the scan itself comes from 127.0.0.1, a page that answers is checked against the web server configuration: when the status location carries Require local, Require ip 127.0.0.1 or allow 127.0.0.1 with deny all, the page is reachable from this machine only and the check passes with that noted (confirm from another machine); otherwise it fails. - Fix: Restrict to administrators: Require local (Apache), or allow 127.0.0.1; deny all; (nginx). Remove the module if unused. - Verify: From another machine: curl -s -o /dev/null -w '%{http_code}' https:///server-status should print 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-007 Other Version Control Metadata Exposed - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks for exposed Subversion, Mercurial and Bazaar metadata folders. - Why: Like .git, these folders let anyone reconstruct source code and history, including secrets that were committed by mistake. - How detected: Requests /.svn/entries, /.svn/wc.db, /.hg/requires and /.bzr/README; a 200 response with the tool's characteristic content confirms exposure. - Fix: Block dot-folders at the web server and deploy without version control metadata in the web root. Rotate any secret ever committed. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///.svn/entries should print 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-008 Log Files Downloadable From the Website - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks for application and debug logs published under the web root. - Why: Logs expose file paths, SQL queries, user data, session identifiers and sometimes credentials or API keys. - How detected: Requests /error_log, /debug.log, /wp-content/debug.log, /storage/logs/laravel.log and /logs/error.log; a non-HTML 200 response containing typical log markers confirms exposure. - Fix: Move logs outside the web root, deny access to *.log at the web server, disable WP_DEBUG_LOG in production, and review the exposed logs for leaked secrets. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///wp-content/debug.log should print 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-009 Dependency Manifests Exposed - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether package manifests and lock files are downloadable. - Why: Lock files list exact library versions, giving attackers a ready list to match against public vulnerabilities, and often reveal internal package names. - How detected: Requests /composer.json, /composer.lock, /package.json, /package-lock.json, /yarn.lock and /Gemfile.lock; a non-HTML 200 response with the file's characteristic structure confirms exposure. - Fix: Keep manifests outside the public web root (for example serve only a public/ subfolder), or deny them at the web server. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///composer.lock should print 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-010 Default Web Server Page Served - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether a site shows the web server's default installation page. - Why: Default pages reveal the server software and suggest an unmaintained or forgotten virtual host, which attackers prioritise. - How detected: Inspects each site's home page for the default page markers of Apache (It works!, Apache2 Default Page), nginx (Welcome to nginx!), LiteSpeed and IIS. - Fix: Remove the default site or replace its index page, and disable unused virtual hosts. - Verify: Open the site in a browser; the default page should not appear. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-011 Database Administration Tool Exposed - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks whether phpMyAdmin or Adminer login pages are publicly reachable. - Why: Database admin tools are among the most attacked paths on the internet: they invite password guessing and their own vulnerabilities give direct database access. - How detected: Requests /phpmyadmin/, /pma/, /phpMyAdmin/ and /adminer.php; a 200 page containing the tool's name confirms exposure. No login is attempted. - Fix: Remove the tool, or restrict it to administrators by IP allow-list, VPN or an extra authentication layer, and keep it updated. - Verify: From outside your network, the paths should return 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-EXP-012 Framework Debug Pages Exposed - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N - What: Checks for development debug tools and verbose error pages left enabled in production. - Why: Debug pages such as the Symfony profiler, Laravel's Whoops/Ignition pages or Django's debug 404 reveal source code, configuration, environment variables and sometimes allow code execution. - How detected: Requests /_profiler/ and a random non-existent path (/openvapt-probe-404-x7q) and looks for framework debug markers (Symfony Profiler, Whoops, Ignition, Django URLconf debug text, Werkzeug debugger). - Fix: Disable debug mode in production (APP_DEBUG=false, DEBUG = False, APP_ENV=prod), remove profiler routes, and show generic error pages. - Verify: Re-run this check; no debug pages should be detected. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/exposure.go ### WEB-HDR-001 Missing HTTP Strict Transport Security (HSTS) - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N - What: Checks that HTTPS sites tell browsers to always use HTTPS, for at least six months. - Why: Without HSTS, the first visit or any typed http:// link can be intercepted and downgraded to plain HTTP (SSL stripping) on public Wi-Fi. - How detected: Reads the Strict-Transport-Security header from each HTTPS site's home page, including a redirect response, because browsers honour HSTS on any HTTPS response. Plain HTTP responses are not evaluated. Missing, or max-age below 15552000 seconds (180 days), fails. - Fix: Apache: Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" . nginx: add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; . Only add 'preload' once every subdomain supports HTTPS. - Verify: curl -sI https:/// | grep -i strict-transport-security - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/headers.go ### WEB-HDR-002 Missing Content Security Policy - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - What: Checks that each site sends a Content-Security-Policy header. - Why: A CSP limits where scripts may load from, turning many cross-site scripting (XSS) bugs from account takeover into a blocked request. - How detected: Reads Content-Security-Policy from each site's home page. Missing fails. A policy containing 'unsafe-inline' in script-src (without a nonce or hash) or a bare * source is reported as weak. - Fix: Start with a report-only policy to find what the site loads, then enforce, for example: Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self' - Verify: curl -sI https:/// | grep -i content-security-policy - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/headers.go ### WEB-HDR-003 Site Can Be Framed (Clickjacking) - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N - What: Checks that each site forbids being embedded in other sites' frames. - Why: If a page can be framed, an attacker can overlay it invisibly and trick users into clicking buttons (clickjacking), such as 'delete' or 'transfer'. - How detected: Passes if the CSP frame-ancestors directive restricts framing (a * or scheme-only source fails), or, without frame-ancestors, X-Frame-Options is DENY or SAMEORIGIN (repeated identical values are accepted; conflicting values fail). - Fix: Send Content-Security-Policy: frame-ancestors 'self' (modern) and X-Frame-Options: SAMEORIGIN (older browsers). - Verify: curl -sI https:/// | grep -Ei 'x-frame-options|frame-ancestors' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/headers.go ### WEB-HDR-004 Missing X-Content-Type-Options - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N - What: Checks that browsers are told not to guess (sniff) content types. - Why: Without nosniff, a browser may execute an uploaded text or image file as script or HTML, enabling XSS through file uploads. - How detected: Reads the X-Content-Type-Options header from each site's home page and checks that it is present with the value nosniff. - Fix: Send X-Content-Type-Options: nosniff on every response (Apache: Header always set X-Content-Type-Options nosniff; nginx: add_header X-Content-Type-Options nosniff always;). - Verify: curl -sI https:/// | grep -i x-content-type-options - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/headers.go ### WEB-HDR-005 Missing Referrer-Policy - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N - What: Checks that each site limits what URL information is leaked to other sites in the Referer header. - Why: Full URLs can contain session tokens, password-reset links or personal data, which would otherwise be sent to every third-party resource and outbound link. - How detected: Reads the Referrer-Policy header from each site's home page and checks that it is present. - Fix: Send Referrer-Policy: strict-origin-when-cross-origin (or no-referrer). Never use unsafe-url. - Verify: curl -sI https:/// | grep -i referrer-policy - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/headers.go ### WEB-HDR-006 Missing Permissions-Policy - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N - What: Checks that each site restricts powerful browser features (camera, microphone, geolocation) it does not use. - Why: Restricting features limits what injected or third-party scripts can do in your visitors' browsers. - How detected: Reads the Permissions-Policy header from each site's home page and checks that it is present. - Fix: Send, for example, Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=() adjusted to the features the site really uses. - Verify: curl -sI https:/// | grep -i permissions-policy - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/headers.go ### WEB-HDR-007 Server Software Version Disclosed - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether response headers reveal exact web server, PHP or framework versions. - Why: Exact versions let attackers match the site against public exploit lists in seconds. Hiding them does not fix vulnerabilities but removes free reconnaissance. - How detected: Looks for a version number in the Server header and for the X-Powered-By, X-AspNet-Version and X-Generator headers. - Fix: Apache: ServerTokens Prod and ServerSignature Off . nginx: server_tokens off; . PHP: expose_php = Off in php.ini . Remove X-Powered-By in the application or proxy. - Verify: curl -sI https:/// | grep -Ei '^(server|x-powered-by)' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/headers.go ### WEB-NGX-001 nginx Version Shown in Server Header - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: nginx does not set server_tokens off, so responses and error pages carry the exact nginx version. - Why: The version string lets an attacker pick advisories that match the server instead of guessing. Hiding it is not a fix for old software, but there is no reason to publish it. - How detected: Reads nginx.conf and its includes under /etc/nginx and looks for server_tokens. Fails when the directive is absent (default on) or set to on or build. - Fix: In the http block of nginx.conf add server_tokens off; then run nginx -t and reload. - Verify: curl -sI https:/// | grep -i '^server:' should print Server: nginx with no version. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-NGX-002 nginx Directory Listing Enabled in Configuration - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: An nginx server or location block sets autoindex on. - Why: Directory listings publish every file in a folder, including uploads, backups and scripts that were never linked. The configuration view finds this even for folders the probe check did not request. - How detected: Reads the nginx configuration tree and flags every autoindex on; directive. The nginx default is off, so an absent directive passes. - Fix: Remove autoindex on; or set autoindex off; in that block. Where a listing is really needed, protect the location with auth_basic or an allow list. - Verify: nginx -T | grep -n autoindex should show only off or nothing. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-NGX-003 nginx Allows TLS 1.0 or 1.1 - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - What: ssl_protocols lists SSLv3, TLSv1 or TLSv1.1, or is not set on a TLS-enabled server, so the allowed version depends on the nginx build. - Why: TLS 1.0 and 1.1 are deprecated by RFC 8996 and lack the protections of TLS 1.2 and 1.3. Clients that negotiate them are exposed to downgrade and known cipher weaknesses. The directive is not the last word: nginx uses the system OpenSSL, and when its configuration already sets a floor of TLS 1.2 (MinProtocol in openssl.cnf on Debian 12 and Ubuntu 22.04 and later, or a crypto policy of DEFAULT, FUTURE or FIPS on the RHEL family) the old versions cannot be negotiated whatever the directive lists. In that case the check reports Pass and names the floor, because rating a server High for a line that has no effect would contradict the live handshake checks (WEB-TLS). The directive is still worth tidying so the configuration says what it does. - How detected: Skipped when no server block is served over TLS (no ssl_certificate and no listen ... ssl). Otherwise reads every ssl_protocols directive in the nginx configuration tree. Any listed version below TLSv1.2 fails, unless /etc/crypto-policies/config or openssl.cnf (with its .include files) shows the system library already refuses TLS 1.0 and 1.1, in which case the result is Pass with the floor as evidence. When no ssl_protocols is set and the floor is unknown, the result is Incomplete because the default changed between nginx releases. - Fix: Set ssl_protocols TLSv1.2 TLSv1.3; in the http block (the Mozilla intermediate profile), run nginx -t and reload. - Verify: openssl s_client -connect 127.0.0.1:443 -servername -tls1_1 should fail to connect. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-NGX-004 nginx Cipher List Includes Weak Suites - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - What: ssl_ciphers includes NULL, export, DES or 3DES, RC4, MD5, anonymous or other legacy suites, or ssl_prefer_server_ciphers forces a legacy order. - Why: Weak suites allow recorded traffic to be decrypted or sessions to be hijacked. Modern clients already prefer strong suites, so the Mozilla guidelines recommend a short explicit list and ssl_prefer_server_ciphers off. - How detected: Reads every ssl_ciphers directive and flags OpenSSL tokens that name NULL, EXPORT, DES, RC4, MD5, aNULL, ADH, AECDH, PSK, SRP, IDEA, SEED or CAMELLIA suites, and bare ALL or DEFAULT. Tokens excluded with ! or - are ignored. ssl_prefer_server_ciphers on is reported as evidence only. - Fix: Use the Mozilla intermediate list, for example ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; and ssl_prefer_server_ciphers off; - Verify: Run the nmap ssl-enum-ciphers script against 127.0.0.1 port 443 under your own test plan; it should list only grade A suites. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-NGX-005 nginx OCSP Stapling Off or Session Tickets On - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: A TLS-enabled nginx does not enable ssl_stapling, or leaves ssl_session_tickets on. - Why: Without stapling, every browser asks the certificate authority whether the certificate is revoked, which slows the handshake and leaks the visited site to the CA. Session tickets encrypted with a long-lived key weaken forward secrecy when the key is never rotated; the Mozilla profiles turn them off. - How detected: When TLS is configured, reads ssl_stapling, ssl_stapling_verify and ssl_session_tickets. Fails unless ssl_stapling is on and ssl_session_tickets is off. - Fix: Add ssl_stapling on; ssl_stapling_verify on; ssl_session_tickets off; and a resolver directive, as the Mozilla generator produces. - Verify: openssl s_client -connect 127.0.0.1:443 -servername -status should print OCSP Response Status: successful. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-NGX-006 nginx Request Limits Not Configured - Domain: web, severity: info, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N - What: nginx sets no client_max_body_size, limit_req_zone or limit_conn_zone, so request size and rate rely on defaults alone. - Why: Explicit limits bound what one client can upload or how fast it can call login and search pages. They are an availability and brute-force control, informational here because the right values depend on the application. - How detected: Reads the configuration tree for client_max_body_size, limit_req_zone and limit_conn_zone. Reports Info when none is present. - Fix: Set client_max_body_size to what the application needs (the default is 1m) and add limit_req_zone for login and API locations with a sensible rate. See the nginx limit_req and limit_conn module documentation. - Verify: nginx -T | grep -nE 'client_max_body_size|limit_(req|conn)_zone' - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-NGX-007 nginx Serves Hidden Files and Folders - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: No location block denies paths that begin with a dot, so .git, .env, .svn and .htaccess files under the document root are served if present. - Why: Dot files hold repository history, credentials and server rules. One deny rule for every path segment starting with a dot removes a whole class of leaks, including files deployed by mistake later. - How detected: Looks for a location block whose pattern matches a dot after a slash (for example location ~ /\. ) in the nginx configuration tree. Fails when no such block exists and nginx serves files (a root or alias directive is present). - Fix: Add to every server block: location ~ /\. { deny all; access_log off; log_not_found off; } and keep .well-known reachable with a more specific location ^~ /.well-known/ block before it. - Verify: curl -s -o /dev/null -w '%{http_code}' https:///.git/HEAD should print 403 or 404. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/serverconf.go ### WEB-NODE-001 Node.js Version Past End of Life - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether the installed Node.js runtime is a release line that no longer receives security fixes. - Why: After end of life the Node.js project publishes no more security releases, so every later vulnerability in the runtime, its bundled OpenSSL or its HTTP parser stays open for good. - How detected: Runs node --version (or nodejs --version), a read-only command, and compares the major version with the Node.js release schedule's end-of-life dates. - Fix: Move to an Active or Maintenance LTS release (even-numbered majors), for example with the NodeSource or distribution packages, test the application, then restart its service. - Verify: node --version shows a supported LTS major. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/runtimes.go ### WEB-NODE-002 Node.js Debugger (Inspector) Enabled in a Service - Domain: web, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether a Node.js service is started with --inspect, which opens the V8 debugger. - Why: The inspector has no authentication: anyone who can connect to it can run any JavaScript inside the application, as the service's user. Bound to 0.0.0.0 that is remote code execution; even on 127.0.0.1 any local user or server-side request forgery bug can reach it. - How detected: Reads ExecStart= and Environment= (including NODE_OPTIONS) of long-running services that run node, npm, yarn, pnpm or pm2, and flags --inspect, --inspect-brk and --inspect-wait. A network-reachable listener on port 9229 is also reported by NET-APP-001. - Fix: Remove --inspect from the service and from NODE_OPTIONS, run systemctl daemon-reload and restart. Debug on a development machine, or temporarily over an SSH tunnel to 127.0.0.1:9229. - Verify: systemctl show -p ExecStart,Environment | grep -c inspect prints 0, and nothing listens on 9229. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/runtimes.go ### WEB-NODE-003 Node.js Service Not Running in Production Mode - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether Node.js services set NODE_ENV=production. - Why: Express and many other frameworks treat a missing NODE_ENV as development: error pages then include stack traces with file paths, and caching and other production safeguards are off. - How detected: For long-running services whose ExecStart= runs node, npm, yarn, pnpm or pm2, reads Environment= and fails when NODE_ENV is missing or not production. Variables loaded with EnvironmentFile= are read only for NODE_ENV. - Fix: Add Environment=NODE_ENV=production to the [Service] section (systemctl edit ), run systemctl daemon-reload and restart the service. - Verify: systemctl show -p Environment includes NODE_ENV=production. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/runtimes.go ### WEB-PHP-001 PHP Displays Errors to Visitors - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether PHP prints error messages into web pages. - Why: Error messages reveal file paths, SQL queries and code structure, and can leak data. They help attackers turn a small bug into a working exploit. - How detected: Asks the PHP command-line binary for ini_get('display_errors') (the only PHP code OpenVAPT may run). Web PHP-FPM pools can use a different php.ini; verify those too. - Fix: In php.ini set display_errors = Off and log_errors = On , then restart PHP-FPM or the web server. - Verify: php -r "echo ini_get('display_errors');" and check the FPM pool's php.ini or php_admin_value settings. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/php.go ### WEB-PHP-002 PHP Version Exposed in Headers - Domain: web, severity: low, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - What: Checks whether PHP adds an X-Powered-By header with its version. - Why: The header advertises the exact PHP version to every visitor and scanner. - How detected: Asks the PHP command-line binary for ini_get('expose_php') (the only PHP code OpenVAPT may run). Web PHP-FPM pools can use a different php.ini; verify those too. - Fix: In php.ini set expose_php = Off and restart PHP. - Verify: php -r "echo ini_get('expose_php');" and check the FPM pool's php.ini or php_admin_value settings. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/php.go ### WEB-PHP-003 PHP Allows Remote File Inclusion - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether include() and require() may load code from URLs. - Why: With allow_url_include on, a single file-inclusion bug lets an attacker run their own PHP code from their own server (remote file inclusion). - How detected: Asks the PHP command-line binary for ini_get('allow_url_include') (the only PHP code OpenVAPT may run). Web PHP-FPM pools can use a different php.ini; verify those too. - Fix: In php.ini set allow_url_include = Off (the default since PHP 5.2) and restart PHP. - Verify: php -r "echo ini_get('allow_url_include');" and check the FPM pool's php.ini or php_admin_value settings. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/php.go ### WEB-PHP-004 PHP Version Past End of Security Support - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether the installed PHP branch still receives security fixes from the PHP project. - Why: Unsupported PHP versions never get fixes for newly found vulnerabilities, some of which allow remote code execution. - How detected: Runs php -v, extracts the major.minor version and compares it with the PHP project's published end-of-security-support dates. Distribution vendors may backport fixes; the evidence notes this. - Fix: Upgrade to a supported PHP branch (see php.net/supported-versions) and test the applications, or confirm your distribution still backports security fixes to this version. - Verify: php -v - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/php.go ### WEB-PHP-005 PHP Session Cookies Not Hardened - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N - What: Checks PHP's session settings: HttpOnly and SameSite cookies and strict session id mode. - Why: Session cookies are the keys to logged-in accounts. Without HttpOnly any XSS bug can steal them; without strict mode PHP accepts attacker-chosen session ids (session fixation). - How detected: Asks the PHP command-line binary for ini_get('session.cookie_httponly'), ini_get('session.use_strict_mode') and ini_get('session.cookie_samesite'). PHP-FPM pools may use a different php.ini; verify those too. - Fix: In php.ini set session.cookie_httponly = 1 , session.use_strict_mode = 1 , session.cookie_samesite = Lax and, for HTTPS sites, session.cookie_secure = 1 , then restart PHP-FPM. - Verify: php -r "echo ini_get('session.cookie_httponly'), ini_get('session.use_strict_mode'), ini_get('session.cookie_samesite');" - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/php.go ### WEB-PY-001 Django Debug Mode Enabled - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - What: Checks Django settings files for DEBUG = True with no production override. - Why: With DEBUG on, every error page shows source code, local variables, SQL queries and most settings, and Django keeps every SQL query in memory. Attackers trigger errors on purpose to read them. - How detected: Finds settings.py and settings/*.py files that look like Django settings in /srv, /opt, /var/www, home directories and web roots (three folders deep), and fails when a folder sets DEBUG = True and no file in the same folder sets DEBUG = False or reads it from the environment. - Fix: Set DEBUG = False in production, preferably from the environment (DEBUG = os.environ.get('DJANGO_DEBUG') == '1'), set ALLOWED_HOSTS, and restart the application. Run python manage.py check --deploy. - Verify: python manage.py check --deploy reports no DEBUG warning. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/runtimes.go ### WEB-PY-002 Django Accepts Any Host Header - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N - What: Checks Django settings for ALLOWED_HOSTS = ['*']. - Why: ALLOWED_HOSTS protects against Host header attacks. With a wildcard, an attacker can make Django build password-reset and other absolute links that point at the attacker's domain, and can poison shared caches. - How detected: Reads the same Django settings files as WEB-PY-001 and flags an ALLOWED_HOSTS list or tuple that starts with '*'. - Fix: List the real domain names: ALLOWED_HOSTS = ['example.com', 'www.example.com'], and restart the application. - Verify: python manage.py check --deploy - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/runtimes.go ### WEB-PY-003 Django Uses the Generated Development Secret Key - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - What: Checks Django settings for a SECRET_KEY that starts with django-insecure-, the marker of the key generated by startproject. - Why: SECRET_KEY signs sessions, password-reset tokens and other data. The generated key sits in source files that are usually copied into repositories and backups; anyone who has it can forge signed data and, with some session backends, take over accounts. - How detected: Reads the same Django settings files as WEB-PY-001 and flags SECRET_KEY = 'django-insecure-...'. The key itself is never copied into the report. - Fix: Generate a new key (python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'), load it from the environment or a root-readable file (SECRET_KEY = os.environ['DJANGO_SECRET_KEY']), and restart. Changing the key logs all users out. - Verify: python manage.py check --deploy reports no SECRET_KEY warning. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/runtimes.go ### WEB-PY-004 Python Interactive Debugger Enabled in a Service - Domain: web, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether a service runs Flask or Werkzeug with the interactive debugger enabled. - Why: The Werkzeug debugger lets a visitor run Python code from an error page. Its PIN has been bypassed and brute-forced in practice, and with WERKZEUG_DEBUG_PIN=off there is no protection at all: this is remote code execution. - How detected: Reads ExecStart= and Environment= of long-running services and flags FLASK_DEBUG=1, FLASK_ENV=development, flask run --debug and WERKZEUG_DEBUG_PIN=off. - Fix: Remove the debug switches, run the application under a production WSGI server (Gunicorn, uWSGI or mod_wsgi) behind nginx or Apache, run systemctl daemon-reload and restart the service. - Verify: systemctl show -p ExecStart,Environment shows no debug switches. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/runtimes.go ### WEB-PY-005 Python Development Server Used in a Service - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - What: Checks whether a service runs a development web server such as manage.py runserver, flask run or python -m http.server. - Why: Development servers are documented as unsuitable for production: they have not been security-audited, handle load and slow clients poorly and often run with debugging features. python -m http.server publishes a whole folder. - How detected: Reads ExecStart= of long-running services and flags manage.py runserver, flask run, python -m http.server or SimpleHTTPServer, uvicorn --reload and streamlit run. - Fix: Serve the application with a production server (Gunicorn or uWSGI for WSGI, Uvicorn without --reload or Hypercorn for ASGI) bound to 127.0.0.1, behind nginx or Apache. Serve static files from the web server. - Verify: systemctl show -p ExecStart shows a production server. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/runtimes.go ### WEB-SEC-001 No security.txt Contact File - Domain: web, severity: info, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N - What: Checks whether each site publishes /.well-known/security.txt (RFC 9116). - Why: security.txt tells researchers how to report a vulnerability. Without it, reports may go unheard or be disclosed publicly instead. - How detected: Requests /.well-known/security.txt and looks for a Contact: field. This is informational and does not affect the risk summary. - Fix: Publish /.well-known/security.txt with at least Contact: and Expires: fields (see securitytxt.org). - Verify: curl -s https:///.well-known/security.txt - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/docroot.go ### WEB-TLS-001 Site Available Over Plain HTTP Without Redirect - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N - What: Checks that every site redirects http:// requests to https://. - Why: Pages served over plain HTTP can be read and changed by anyone on the network path, including injected malware and stolen session cookies. - How detected: Requests http:/// through 127.0.0.1 without following redirects. A 301/302/307/308 to an https:// URL passes, as does a refused connection (nothing listens on port 80). Any other response fails. A timeout or other error is Incomplete, because a slow or half-open port 80 proves nothing. - Fix: Apache: in the port 80 vhost add Redirect permanent / https:/// . nginx: return 301 https://$host$request_uri; . Get a free certificate with certbot if HTTPS is not set up yet. - Verify: curl -sI http:/// should show a 301 with Location: https://... - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/transport.go ### WEB-TOM-001 Tomcat Manager Reachable Without Address Restriction - Domain: web, severity: high, CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks whether the Tomcat Manager or Host Manager application is deployed without an address filter limiting it to localhost. - Why: The Manager deploys WAR files, which means it runs any code it is given. Exposed to the network it is one stolen or guessed password away from full compromise, and it is a standard target of automated attacks. - How detected: Looks for the manager and host-manager applications in each Tomcat installation's webapps folder (and /usr/share/tomcatN-admin on Debian and Ubuntu), then reads their META-INF/context.xml and conf/Catalina/localhost/.xml. Fails when no active RemoteAddrValve or RemoteCIDRValve restricts access. - Fix: If you do not use them, delete the manager and host-manager folders (or apt remove tomcatN-admin). Otherwise keep the default valve in META-INF/context.xml: and reach the Manager through an SSH tunnel. - Verify: grep -A2 RemoteAddrValve /manager/META-INF/context.xml - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/tomcat.go ### WEB-TOM-002 Tomcat Administrative Users With Default or Weak Passwords - Domain: web, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks tomcat-users.xml for manager or admin accounts with well-known default passwords, passwords equal to the user name, or short passwords. - Why: Default Tomcat credentials (tomcat/tomcat, admin/admin, tomcat/s3cret) are in every attack tool's word list. A Manager account with such a password lets anyone deploy code on the server. - How detected: Parses the elements of conf/tomcat-users.xml (comments ignored). For users with manager-gui, manager-script, manager-jmx, admin-gui or admin-script roles, compares the password in memory against a short list of documented defaults, the user name and a 12-character minimum. Passwords stored as digests are not judged. No password is ever written to the report. - Fix: Remove accounts you do not need. Give the remaining ones long random passwords and store them as digests: configure a CredentialHandler (for example SecretKeyCredentialHandler with PBKDF2) in the Realm and generate hashes with bin/digest.sh. Restart Tomcat. - Verify: Re-run this check; no administrative user should be listed. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/tomcat.go ### WEB-TOM-003 Tomcat Shutdown Port Uses the Default Command - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - What: Checks whether Tomcat's shutdown port is enabled with the well-known default command SHUTDOWN. - Why: Anyone who can connect to the shutdown port (8005 by default) and send the shutdown string stops Tomcat. With the default string, every local user and every compromised process on the machine can take the applications offline. - How detected: Reads the element of conf/server.xml and fails when port is not -1 and the shutdown attribute is SHUTDOWN. - Fix: Disable the port with and stop Tomcat through systemd, or set shutdown to a long random string. Restart Tomcat. - Verify: grep '/server.xml shows port="-1". - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/tomcat.go ### WEB-TOM-004 Tomcat AJP Connector Unprotected (Ghostcat) - Domain: web, severity: critical, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - What: Checks for an active AJP connector that has no shared secret and is not bound to loopback, or that disables the secret requirement. - Why: AJP is a trusted, unauthenticated protocol between a web server and Tomcat. CVE-2020-1938 (Ghostcat) showed that a reachable AJP port lets an attacker read any file in the web applications and, where uploads exist, execute code. It is listed in the CISA Known Exploited Vulnerabilities catalogue. - How detected: Reads the elements of conf/server.xml (comments ignored) and, for those with protocol AJP/1.3 or an AJP protocol class, fails when secretRequired="false" or when there is no secret (or requiredSecret) and the address is not a loopback address. - Fix: If you do not use AJP (most sites proxy over HTTP), delete or comment out the AJP . Otherwise set address="127.0.0.1", a long random secret="..." with secretRequired="true", and the same secret in the proxy (mod_proxy_ajp: ProxyPass ... secret=...). Upgrade Tomcat to a supported release. - Verify: grep -i ajp /server.xml and ss -tlnp | grep 8009 shows nothing or 127.0.0.1 only. - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/tomcat.go ### WEB-TOM-005 Tomcat Example and Documentation Applications Deployed - Domain: web, severity: medium, CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N - What: Checks whether the examples or docs web applications that ship with Tomcat are deployed. - Why: The sample applications are written to demonstrate features, not to be safe. Some (such as the session example) let visitors change server-side session data, and all of them reveal the exact Tomcat version. - How detected: Looks for examples/ and docs/ application folders (WEB-INF/web.xml or index.html) in each Tomcat installation's webapps folders, including the Debian and Ubuntu tomcatN-examples and tomcatN-docs packages. - Fix: Delete the examples and docs folders from webapps (or apt remove tomcatN-examples tomcatN-docs) and restart Tomcat. - Verify: ls lists only your own applications (and ROOT). - Source: https://github.com/OpenVAPT/openvapt/blob/574f7f3333c44b75a3c649e2d93a76d507f75adf/internal/checks/web/tomcat.go