Firewall Allows Sensitive Services From Anywhere
Checks whether the host firewall explicitly opens a listening database, administrative or application server port to every address on the internet.
- Why It Matters
- 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 It Detects
- 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.
- How to 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.
- How to Verify
ufw status numbered or nft list ruleset shows no rule opening that port to Anywhere.- CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H- Control Category
- Network Exposure: CIS Controls v8 4, 12, 13; NIST CSF 2.0 PR.IR, PR.PS
- Fix Effort
- Low
- CIS Controls v8 Safeguard
- 4.4
- NIST CSF 2.0 Subcategory
- PR.IR-01
- CWE
- CWE-923, CWE-1327
- OWASP Top 10
- A05:2021
- NIST SP 800-53 Rev. 5
- SC-7, AC-4, CM-7
- Benchmark Topic
- Restrict firewall rules to required sources (topic only, no benchmark text)
- References
- NIST SP 800-41 Rev. 1, Guidelines on Firewalls and Firewall Policy
ufw(8) manual (Ubuntu)
Source Code
The complete file that defines this check. Run it with openvapt scan --only NET-FW-003, or read it offline with openvapt explain NET-FW-003.
internal/checks/network/firewall_exposure.go · Lines 1-296View on GitHub at This Commit
| 1 | package network |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "strings" |
| 7 | |
| 8 | "github.com/openvapt/openvapt/internal/check" |
| 9 | "github.com/openvapt/openvapt/internal/checks/collect" |
| 10 | "github.com/openvapt/openvapt/internal/sys" |
| 11 | ) |
| 12 | |
| 13 | // Checks that compare what the host firewall allows with what is actually |
| 14 | // listening. A firewall that is "on" can still let a database through, keep |
| 15 | // holes open for services long gone, or be bypassed by Docker. |
| 16 | |
| 17 | // codePorts are debuggers, management protocols and shutdown ports of |
| 18 | // application servers that give code execution or control to whoever |
| 19 | // connects. Sources: Node.js, Apache Tomcat, PHP-FPM and Jupyter |
| 20 | // documentation. |
| 21 | var codePorts = map[int]string{ |
| 22 | 9229: "Node.js inspector (debugger)", 8009: "Tomcat AJP connector", 8005: "Tomcat shutdown port", |
| 23 | 9000: "PHP-FPM FastCGI", 8888: "Jupyter Notebook", |
| 24 | } |
| 25 | |
| 26 | // appPorts are the default HTTP ports of application servers and developer |
| 27 | // tools that are meant to sit behind a reverse proxy, never face the |
| 28 | // network. Reaching one directly bypasses TLS, headers and limits, but the |
| 29 | // port number alone does not prove code execution, so the severity is lower |
| 30 | // than codePorts. Sources: Node.js, Django, Flask, Vite and Tomcat docs. |
| 31 | var appPorts = map[int]string{ |
| 32 | 3000: "Node.js / Express app server", 3001: "Node.js app server", 4000: "app server", 4200: "Angular dev server", |
| 33 | 5000: "Flask / Python dev server", 5173: "Vite dev server", 8000: "Django / Gunicorn / Uvicorn", 8008: "app server", |
| 34 | 8080: "Tomcat / Java HTTP connector", 8081: "app server", 8088: "app server", |
| 35 | } |
| 36 | |
| 37 | // sensitivePort names a data, administrative or application server port. |
| 38 | func sensitivePort(p int) (string, bool) { |
| 39 | for _, m := range []map[int]string{dataPorts, controlPorts, managementPorts, codePorts, appPorts, aiPorts} { |
| 40 | if n, ok := m[p]; ok { |
| 41 | return n, true |
| 42 | } |
| 43 | } |
| 44 | return "", false |
| 45 | } |
| 46 | |
| 47 | var refNIST41 = check.Ref{Title: "NIST SP 800-41 Rev. 1, Guidelines on Firewalls and Firewall Policy", URL: "https://csrc.nist.gov/pubs/sp/800/41/r1/final"} |
| 48 | |
| 49 | func publicListeners(s sys.System) ([]collect.Listener, []check.Result) { |
| 50 | ls, err := collect.Listeners(s) |
| 51 | if err != nil { |
| 52 | return nil, []check.Result{check.IncompleteR("/proc/net", "One or more socket tables were unreadable or partial")} |
| 53 | } |
| 54 | var pub []collect.Listener |
| 55 | for _, l := range ls { |
| 56 | if l.Public() { |
| 57 | pub = append(pub, l) |
| 58 | } |
| 59 | } |
| 60 | return pub, nil |
| 61 | } |
| 62 | |
| 63 | func init() { |
| 64 | check.Register(&check.Check{ |
| 65 | ID: "NET-FW-003", Title: "Firewall Allows Sensitive Services From Anywhere", Domain: check.DomainNetwork, |
| 66 | Severity: check.High, Category: check.CatNetwork, Effort: check.EffortLow, |
| 67 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", |
| 68 | Summary: "Checks whether the host firewall explicitly opens a listening database, administrative or application server port to every address on the internet.", |
| 69 | Rationale: "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.", |
| 70 | Detection: "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.", |
| 71 | 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.", |
| 72 | Verify: "ufw status numbered or nft list ruleset shows no rule opening that port to Anywhere.", |
| 73 | Refs: []check.Ref{refNIST41, {Title: "ufw(8) manual (Ubuntu)", URL: "https://manpages.ubuntu.com/manpages/noble/man8/ufw.8.html"}}, |
| 74 | Standards: check.Standards{CWE: []string{"CWE-923", "CWE-1327"}, OWASP: []string{"A05:2021"}, NIST: []string{"SC-7", "AC-4", "CM-7"}, CISTopic: "Restrict firewall rules to required sources"}, |
| 75 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 76 | fw := collect.ReadFirewall(ctx, s) |
| 77 | if len(fw.Rules) == 0 { |
| 78 | return []check.Result{check.SkipR("firewall", "No firewall allow rules found (see NET-FW-001 for whether a firewall is active)")} |
| 79 | } |
| 80 | pub, inc := publicListeners(s) |
| 81 | if inc != nil { |
| 82 | return inc |
| 83 | } |
| 84 | var bad []string |
| 85 | for _, l := range pub { |
| 86 | name, ok := sensitivePort(l.Port) |
| 87 | if !ok { |
| 88 | continue |
| 89 | } |
| 90 | for _, r := range fw.Rules { |
| 91 | if r.Any() && r.Covers(l.Proto, l.Port) { |
| 92 | bad = append(bad, fmt.Sprintf("%s (%s) allowed from anywhere by %s rule: %s", l.String(), name, r.Engine, r.Raw)) |
| 93 | break |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | if len(bad) > 0 { |
| 98 | return []check.Result{check.FailR("firewall", "Sensitive services are open to every address", bad...)} |
| 99 | } |
| 100 | return []check.Result{check.PassR("firewall", "No sensitive listening service is opened to every address")} |
| 101 | }, |
| 102 | }) |
| 103 | |
| 104 | check.Register(&check.Check{ |
| 105 | ID: "NET-FW-004", Title: "Firewall Rules Open Ports With No Service", Domain: check.DomainNetwork, |
| 106 | Severity: check.Low, Category: check.CatNetwork, Effort: check.EffortLow, |
| 107 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", |
| 108 | Summary: "Checks for firewall allow rules whose port has no listening service on this machine.", |
| 109 | Rationale: "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.", |
| 110 | Detection: "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.", |
| 111 | Fix: "Remove rules for services that no longer exist (ufw status numbered, then ufw delete <n>), or note why a rule must stay. Review the firewall whenever a service is retired.", |
| 112 | Verify: "Re-run this check; every allowed port should have a listening service.", |
| 113 | Refs: []check.Ref{refNIST41}, |
| 114 | Standards: check.Standards{CWE: []string{"CWE-1188"}, NIST: []string{"SC-7", "CM-7", "CM-3"}, CISTopic: "Review firewall rules periodically"}, |
| 115 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 116 | fw := collect.ReadFirewall(ctx, s) |
| 117 | if len(fw.Rules) == 0 { |
| 118 | return []check.Result{check.SkipR("firewall", "No firewall allow rules found")} |
| 119 | } |
| 120 | ls, err := collect.Listeners(s) |
| 121 | if err != nil { |
| 122 | return []check.Result{check.IncompleteR("/proc/net", "One or more socket tables were unreadable or partial")} |
| 123 | } |
| 124 | seen := map[string]bool{} |
| 125 | var bad []string |
| 126 | for _, r := range fw.Rules { |
| 127 | if r.From != r.To { |
| 128 | continue |
| 129 | } |
| 130 | used := false |
| 131 | for _, l := range ls { |
| 132 | if r.Covers(l.Proto, l.Port) { |
| 133 | used = true |
| 134 | break |
| 135 | } |
| 136 | } |
| 137 | key := fmt.Sprintf("%s/%d", r.Proto, r.From) |
| 138 | if !used && !seen[key] { |
| 139 | seen[key] = true |
| 140 | bad = append(bad, fmt.Sprintf("port %d%s allowed by %s but nothing listens on it: %s", r.From, protoSuffix(r.Proto), r.Engine, r.Raw)) |
| 141 | } |
| 142 | } |
| 143 | if len(bad) > 0 { |
| 144 | return []check.Result{check.FailR("firewall", fmt.Sprintf("%d allow rule(s) open ports with no service", len(bad)), bad...)} |
| 145 | } |
| 146 | return []check.Result{check.PassR("firewall", "Every single-port allow rule matches a listening service")} |
| 147 | }, |
| 148 | }) |
| 149 | |
| 150 | check.Register(&check.Check{ |
| 151 | ID: "NET-FW-005", Title: "Container Ports Bypass the Host Firewall", Domain: check.DomainNetwork, |
| 152 | Severity: check.Medium, Category: check.CatNetwork, Effort: check.EffortMedium, |
| 153 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", |
| 154 | Summary: "Checks whether Docker publishes container ports through its own iptables chains with no DOCKER-USER filtering.", |
| 155 | Rationale: "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.", |
| 156 | Detection: "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.", |
| 157 | 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.", |
| 158 | Verify: "docker ps --format '{{.Ports}}' shows 127.0.0.1 bindings, or iptables -S DOCKER-USER shows DROP rules.", |
| 159 | Refs: []check.Ref{{Title: "Docker documentation, Packet filtering and firewalls", URL: "https://docs.docker.com/engine/network/packet-filtering-firewalls/"}, refNIST41}, |
| 160 | Standards: check.Standards{CWE: []string{"CWE-923", "CWE-1327"}, OWASP: []string{"A05:2021"}, NIST: []string{"SC-7", "CM-7"}, CISTopic: "Filter container traffic"}, |
| 161 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 162 | fw := collect.ReadFirewall(ctx, s) |
| 163 | if len(fw.Docker) == 0 { |
| 164 | return []check.Result{check.PassR("iptables", "No published container ports found in the Docker chain")} |
| 165 | } |
| 166 | if fw.DockerUser { |
| 167 | return []check.Result{check.PassR("iptables", "Published container ports are filtered in DOCKER-USER")} |
| 168 | } |
| 169 | return []check.Result{check.FailR("iptables", fmt.Sprintf("%d published container port rule(s) are not filtered by the host firewall", len(fw.Docker)), fw.Docker...)} |
| 170 | }, |
| 171 | }) |
| 172 | |
| 173 | check.Register(&check.Check{ |
| 174 | ID: "NET-FW-006", Title: "Firewall Exposure Map of Listening Services", Domain: check.DomainNetwork, |
| 175 | Severity: check.Info, Category: check.CatNetwork, Effort: check.EffortLow, |
| 176 | CVSS: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N", |
| 177 | Summary: "Shows, for every network-reachable listening socket, what the host firewall does with traffic to it.", |
| 178 | Rationale: "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.", |
| 179 | Detection: "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.", |
| 180 | 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.", |
| 181 | Verify: "Re-run this check after changes and compare the map.", |
| 182 | Refs: []check.Ref{refNIST41, {Title: "NIST SP 800-115, Technical Guide to Information Security Testing and Assessment", URL: "https://csrc.nist.gov/pubs/sp/800/115/final"}}, |
| 183 | Standards: check.Standards{CWE: []string{"CWE-923"}, NIST: []string{"CM-7", "CM-8", "SC-7"}, CISTopic: "Network services inventory"}, |
| 184 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 185 | pub, inc := publicListeners(s) |
| 186 | if inc != nil { |
| 187 | return inc |
| 188 | } |
| 189 | fw := collect.ReadFirewall(ctx, s) |
| 190 | engine := fw.Engine |
| 191 | if engine == "" { |
| 192 | engine = "none detected" |
| 193 | } |
| 194 | ev := []string{fmt.Sprintf("firewall: %s, default deny: %v", engine, fw.DefaultDeny)} |
| 195 | open := 0 |
| 196 | for _, l := range pub { |
| 197 | state := "reachable (no default-deny firewall)" |
| 198 | if fw.DefaultDeny { |
| 199 | state = "blocked by default deny" |
| 200 | } |
| 201 | for _, r := range fw.Rules { |
| 202 | if r.Covers(l.Proto, l.Port) { |
| 203 | if r.Any() { |
| 204 | state = "allowed from anywhere" |
| 205 | } else { |
| 206 | state = "allowed from " + r.Source |
| 207 | } |
| 208 | break |
| 209 | } |
| 210 | } |
| 211 | if strings.HasPrefix(state, "allowed from anywhere") || strings.HasPrefix(state, "reachable") { |
| 212 | open++ |
| 213 | } |
| 214 | ev = append(ev, l.String()+portName(l.Port)+": "+state) |
| 215 | } |
| 216 | return []check.Result{check.PassR("firewall", fmt.Sprintf("%d network-reachable sockets, %d open to every address", len(pub), open), ev...)} |
| 217 | }, |
| 218 | }) |
| 219 | |
| 220 | check.Register(&check.Check{ |
| 221 | ID: "NET-APP-001", Title: "Debugger or Application Control Ports Exposed", Domain: check.DomainNetwork, |
| 222 | Severity: check.High, Category: check.CatNetwork, Effort: check.EffortLow, |
| 223 | CVSS: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L", |
| 224 | Summary: "Checks whether the Node.js inspector, Tomcat AJP or shutdown port, PHP-FPM FastCGI or Jupyter listen on network-reachable addresses.", |
| 225 | Rationale: "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.", |
| 226 | Detection: "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.", |
| 227 | 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.", |
| 228 | Verify: "ss -tlnp shows these ports on 127.0.0.1 or ::1 only.", |
| 229 | Refs: []check.Ref{{Title: "Node.js, Inspector security implications", URL: "https://nodejs.org/en/learn/getting-started/debugging#security-implications"}, |
| 230 | {Title: "Apache Tomcat 9, AJP Connector", URL: "https://tomcat.apache.org/tomcat-9.0-doc/config/ajp.html"}}, |
| 231 | Standards: check.Standards{CWE: []string{"CWE-489", "CWE-1327"}, OWASP: []string{"A05:2021"}, NIST: []string{"SC-7", "CM-7"}, CISTopic: "Expose applications only through a hardened front end"}, |
| 232 | Run: appPortRun(codePorts, "Debugger or application control ports are network-reachable", "No debugger or application control port is network-reachable"), |
| 233 | }) |
| 234 | |
| 235 | check.Register(&check.Check{ |
| 236 | ID: "NET-APP-002", Title: "Application Server Ports Reachable Without a Reverse Proxy", Domain: check.DomainNetwork, |
| 237 | Severity: check.Medium, Category: check.CatNetwork, Effort: check.EffortLow, |
| 238 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", |
| 239 | Summary: "Checks whether application servers and developer tools (Node.js, Tomcat, Django, Flask, Vite) serve HTTP on network-reachable addresses.", |
| 240 | Rationale: "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.", |
| 241 | Detection: "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.", |
| 242 | 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.", |
| 243 | Verify: "ss -tlnp shows these ports on 127.0.0.1 or ::1 only.", |
| 244 | Refs: []check.Ref{{Title: "OWASP Node.js Security Cheat Sheet", URL: "https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html"}, |
| 245 | {Title: "Gunicorn documentation, Deploying Gunicorn (behind a proxy)", URL: "https://gunicorn.org/deploy/"}}, |
| 246 | Standards: check.Standards{CWE: []string{"CWE-1188", "CWE-1327"}, OWASP: []string{"A05:2021"}, NIST: []string{"SC-7", "CM-7"}, CISTopic: "Expose applications only through a hardened front end"}, |
| 247 | Run: appPortRun(appPorts, "Application server ports are network-reachable", "No application server port is network-reachable"), |
| 248 | }) |
| 249 | } |
| 250 | |
| 251 | // appPortRun reports network-reachable TCP listeners on the given ports, |
| 252 | // noting whether a web server (80 or 443) is present to front them. |
| 253 | func appPortRun(ports map[int]string, failMsg, passMsg string) func(context.Context, sys.System) []check.Result { |
| 254 | return func(_ context.Context, s sys.System) []check.Result { |
| 255 | pub, inc := publicListeners(s) |
| 256 | if inc != nil { |
| 257 | return inc |
| 258 | } |
| 259 | var bad []string |
| 260 | web := false |
| 261 | for _, l := range pub { |
| 262 | if l.Port == 80 || l.Port == 443 { |
| 263 | web = true |
| 264 | } |
| 265 | } |
| 266 | for _, l := range pub { |
| 267 | if n, ok := ports[l.Port]; ok && strings.HasPrefix(l.Proto, "tcp") { |
| 268 | bad = append(bad, l.String()+" "+n+", bound to "+exposure(l)) |
| 269 | } |
| 270 | } |
| 271 | if len(bad) > 0 { |
| 272 | if web { |
| 273 | bad = append(bad, "a web server listens on port 80 or 443 on this machine: bind the application to 127.0.0.1 and publish it through that proxy") |
| 274 | } else { |
| 275 | bad = append(bad, "no web server listens on port 80 or 443 on this machine") |
| 276 | } |
| 277 | return []check.Result{check.FailR("sockets", failMsg, bad...)} |
| 278 | } |
| 279 | return []check.Result{check.PassR("sockets", passMsg)} |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | // portName labels a port with its well-known service, if any. |
| 284 | func portName(p int) string { |
| 285 | if n, ok := sensitivePort(p); ok { |
| 286 | return " (" + n + ")" |
| 287 | } |
| 288 | return label(p) |
| 289 | } |
| 290 | |
| 291 | func protoSuffix(p string) string { |
| 292 | if p == "" { |
| 293 | return "" |
| 294 | } |
| 295 | return "/" + p |
| 296 | } |
Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.
Cite This Page
If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.
OpenVAPT. (2026). NET-FW-003 Firewall Allows Sensitive Services From Anywhere (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/NET-FW-003.html
In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
@misc{openvapt_net_fw_003,
author = {{OpenVAPT}},
title = {{NET-FW-003 Firewall Allows Sensitive Services From Anywhere}},
year = {2026},
howpublished = {\url{https://openvapt.com/checks/NET-FW-003.html}},
note = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}
Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.