Back to blog
Best PracticesCloud SecurityGCPNetworkingOperations & Compliance

Firewall Allows Public VNC: Securing GCP VNC Ports 5900 and 5500

Learn why exposing GCP VNC ports 5900/5500 to the internet is dangerous and how to fix it with IAP, SSH tunnels, source-range restrictions, and policy-as-code.

TL;DR

This check flags GCP firewall rules that expose VNC ports (5900 and 5500) to the public internet (0.0.0.0/0). VNC traffic is often unencrypted and a magnet for brute-force and screen-hijacking attacks. Lock the source ranges down to a bastion or VPN, or front VNC with SSH tunneling and Identity-Aware Proxy.

Remote desktop tools are handy when you need eyes on a graphical session running inside a virtual machine. VNC is one of the oldest and most common of these tools, and it shows up everywhere from CI runners with GUI test suites to legacy Linux desktops that someone never got around to decommissioning. The problem is not VNC itself. The problem is exposing it directly to the internet through a wide-open firewall rule.

The vpc_openvnc check in the Lensix vpc_checks module scans your GCP VPC firewall rules and raises a finding whenever a rule allows inbound traffic on TCP ports 5900 or 5500 from public source ranges.


What this check detects

Lensix inspects every ALLOW firewall rule in your GCP projects and flags any rule where all of the following are true:

  • Direction is INGRESS
  • Protocol is TCP and the port range includes 5900 (the default VNC display port) or 5500 (the VNC listening/reverse-connection port)
  • The sourceRanges field contains a public CIDR such as 0.0.0.0/0

Port 5900 maps to VNC display :0, with each additional display incrementing the port (5901, 5902, and so on). Many scanners look for the entire 5900 to 5910 block, so even if you only opened a single display, you are still visible. Port 5500 is used by viewers in listening mode, where the server connects back to the client, a pattern attackers love because it can punch through some egress controls.

Note: GCP firewall rules are stateful and apply at the VPC network level, not the individual instance. A single permissive rule can expose every VM that matches its target tags or service accounts, which is why one bad rule often shows up as a finding affecting dozens of machines.


Why it matters

VNC was designed in the late 1990s, and its security model reflects that. The base RFB protocol sends pixel data and input events in cleartext. Many VNC server implementations cap passwords at 8 characters and use a weak DES-based challenge-response scheme that is trivial to crack offline once captured. Put that on the public internet and you have handed attackers a graphical session on a silver platter.

Here is what actually happens to exposed VNC endpoints:

  • Mass scanning. Services like Shodan index open VNC ports continuously. Researchers have repeatedly found tens of thousands of internet-facing VNC servers with no authentication at all, dropping anyone straight onto a live desktop.
  • Brute-force attacks. The short password limit means automated tools can exhaust the keyspace quickly. Once in, an attacker controls the mouse and keyboard as if they were sitting at the machine.
  • Credential and session theft. Because traffic is unencrypted, anyone on the network path can capture keystrokes, view what is on screen, and lift credentials typed into the session.
  • Lateral movement. A graphical foothold on one VM is often the first hop. From there attackers harvest cloud metadata credentials, pivot to other internal hosts, or deploy crypto miners.

Danger: An unauthenticated or weakly authenticated VNC server reachable from 0.0.0.0/0 should be treated as a live incident, not a future risk. Assume it has already been scanned. Rotate any credentials that were entered through that session and review instance logs before you simply close the port.

For regulated environments, exposing remote desktop access to the public internet also runs afoul of frameworks like PCI DSS, SOC 2, and CIS Benchmarks, all of which expect administrative interfaces to be restricted to known, trusted sources.


How to fix it

The goal is to remove public access while keeping VNC usable for the people who legitimately need it. You have three good options, in rough order of preference: tunnel over SSH with IAP, restrict the source range to a bastion or VPN, or delete the rule entirely if VNC is no longer needed.

Step 1: Find the offending rule

List firewall rules that allow VNC ports from anywhere:

gcloud compute firewall-rules list \
  --filter="direction=INGRESS AND allowed.ports:(5900 OR 5500) AND sourceRanges:0.0.0.0/0" \
  --format="table(name, network, sourceRanges.list(), allowed[].map().firewall_rule().list())"

Inspect a specific rule to see exactly what it targets:

gcloud compute firewall-rules describe allow-vnc --format=json

Step 2: Restrict the source range

If VNC access has to remain reachable over the network, replace the public CIDR with the specific addresses that need it, such as your office egress IP or your VPN subnet.

Warning: Updating --source-ranges replaces the entire list, it does not append. Always pass the complete set of allowed ranges in one command or you will lock yourself out of intended sources.

gcloud compute firewall-rules update allow-vnc \
  --source-ranges=203.0.113.10/32,198.51.100.0/24

Step 3 (preferred): Remove the rule and use IAP TCP forwarding

The cleanest fix is to take VNC off the public internet completely and reach it through Google's Identity-Aware Proxy, which authenticates users with IAM before any packet touches the VM.

Danger: Deleting a firewall rule is immediate and affects every VM the rule targets. Confirm that no production traffic depends on it before you run the command below.

# Remove the public VNC rule
gcloud compute firewall-rules delete allow-vnc

# Allow only IAP's source range to reach the VNC port
gcloud compute firewall-rules create allow-vnc-from-iap \
  --direction=INGRESS \
  --action=ALLOW \
  --rules=tcp:5900 \
  --source-ranges=35.235.240.0/20 \
  --target-tags=vnc-server

Then start a local tunnel from your workstation. Connect your VNC client to localhost:5900:

gcloud compute start-iap-tunnel my-vm 5900 \
  --local-host-port=localhost:5900 \
  --zone=us-central1-a

Note: 35.235.240.0/20 is the fixed CIDR that all IAP TCP forwarding traffic originates from. Allowing it does not open the port to the internet, because every connection still has to pass IAM authorization first via the roles/iap.tunnelResourceAccessor permission.

Alternative: SSH tunnel without IAP

If you already reach instances over SSH through a bastion, you can tunnel VNC over that existing encrypted channel and leave VNC bound to localhost on the VM:

gcloud compute ssh my-vm --zone=us-central1-a -- -L 5900:localhost:5900

This way port 5900 never needs a public firewall rule at all, and VNC traffic inherits SSH's encryption.


Fixing it with Infrastructure as Code

If your firewall rules live in Terraform, find the resource and tighten the source_ranges. Here is a corrected rule that only accepts IAP traffic:

resource "google_compute_firewall" "vnc_from_iap" {
  name      = "allow-vnc-from-iap"
  network   = google_compute_network.main.id
  direction = "INGRESS"

  allow {
    protocol = "tcp"
    ports    = ["5900"]
  }

  # IAP TCP forwarding range only, never 0.0.0.0/0
  source_ranges = ["35.235.240.0/20"]
  target_tags   = ["vnc-server"]
}

Tip: Search your whole Terraform codebase for the dangerous pattern before you assume one rule is the only offender: grep -rn "0.0.0.0/0" --include="*.tf" . then cross-reference any hit that also lists port 5900 or 5500.


How to prevent it from happening again

Closing one rule is firefighting. The durable fix is making it impossible, or at least loud, to ship a public VNC rule in the first place.

1. Block the pattern in CI with policy-as-code

Use Open Policy Agent (Conftest) or Sentinel to fail any plan that opens 5900 or 5500 to the world. A minimal OPA rule for Terraform plans:

package main

deny[msg] {
  rule := input.resource_changes[_]
  rule.type == "google_compute_firewall"
  some r in rule.change.after.source_ranges
  r == "0.0.0.0/0"
  some p in rule.change.after.allow[_].ports
  p == "5900"
  msg := sprintf("Firewall '%s' exposes VNC (5900) to the public internet", [rule.change.after.name])
}

Wire this into your pipeline so it runs against terraform plan -out output before any apply.

2. Enforce org-level guardrails

GCP Organization Policy can centrally restrict overly permissive ingress. Combine that with Hierarchical Firewall Policies at the folder or org level to deny known dangerous ports before project-level rules ever take effect.

Tip: Standardize on IAP TCP forwarding for all administrative access (SSH, RDP, VNC). Once teams are used to start-iap-tunnel, there is no reason for anyone to request a public management port, and you can confidently deny them at the org level.

3. Continuously monitor with Lensix

CI gates catch new code, but rules also get created by hand in the console during incidents and then forgotten. Keep vpc_openvnc running on a schedule so any drift, whether from a console click or an out-of-band script, surfaces as a finding within minutes rather than at the next audit.


Best practices

  • Never expose management ports to 0.0.0.0/0. This applies to VNC (5900/5500), RDP (3389), SSH (22), and any other administrative interface.
  • Default to zero-trust access. Use IAP or a VPN so identity is verified before network reachability is granted.
  • Encrypt remote desktop traffic. Tunnel VNC over SSH or IAP rather than relying on VNC's own weak or absent encryption.
  • Scope rules with target tags or service accounts. Avoid broad rules that apply to every VM in a network.
  • Set strong, unique VNC passwords where the server supports it, and prefer servers that support TLS or that you only ever reach over an encrypted tunnel.
  • Audit firewall rules regularly and remove rules for decommissioned services so they cannot become tomorrow's open door.
  • Prefer no graphical access at all on servers. If a workload only needs occasional GUI access, spin up VNC on demand behind IAP rather than leaving it running.

VNC has a legitimate place in plenty of environments. The line you never want to cross is putting it on the open internet. Lock it behind identity-aware access, keep your firewall rules tight, and let continuous checks like vpc_openvnc catch the drift before an attacker does.