Back to blog
Best PracticesCloud SecurityGCPNetworkingOperations & Compliance

GCP Firewall Allows Public Telnet: Why It Is Dangerous and How to Fix It

A GCP firewall rule open to public Telnet (port 23) hands attackers an unencrypted way in. Learn the risk and how to remediate it with CLI, Terraform, and IAP.

TL;DR

This check flags any GCP firewall rule that allows Telnet (TCP port 23) from 0.0.0.0/0. Telnet sends credentials in plaintext, so an open rule hands attackers an unencrypted door into your network. Remove the rule or restrict the source range, and switch to SSH instead.

Telnet has no business being open to the internet in 2024, yet it still shows up in firewall rules far more often than you would expect. Usually it is a leftover from a quick connectivity test, a copied template, or a legacy appliance that nobody wanted to touch. Whatever the reason, an open Telnet port is one of the easiest wins an attacker can find, and Lensix flags it as soon as it appears in a VPC firewall rule.

This post explains what the vpc_opentelnet check looks for, why a public Telnet port is genuinely dangerous, and how to close it down for good.


What this check detects

The vpc_opentelnet check scans your GCP VPC firewall rules and raises a finding when a rule meets all of these conditions:

  • Direction is INGRESS
  • Action is allow
  • The protocol and port allow TCP 23 (either explicitly, as part of a range, or via an "all ports" rule)
  • The source range includes 0.0.0.0/0 (or ::/0 for IPv6)

In plain terms: the firewall lets anyone on the public internet reach the Telnet service on your instances. The rule does not have to mention port 23 by name. A rule that opens tcp:0-65535 or tcp with no port restriction will also trip this check, because it implicitly includes 23.

Note: GCP firewall rules apply to instances based on network tags or service accounts. A rule with no target tag applies to every instance in the VPC, which makes a broad Telnet rule even more dangerous than it first appears.


Why it matters

Telnet is a clear-text protocol. Every byte you send over it, including your username and password, travels across the network unencrypted. Anyone who can observe the traffic can read your credentials directly. When the firewall opens port 23 to the world, you are combining the weakest possible remote-access protocol with the largest possible attack surface.

What an attacker actually does with it

Open Telnet ports are a favorite target of automated botnets. The Mirai botnet, which took down large chunks of the internet in 2016, spread almost entirely by scanning for open Telnet ports and trying a short list of default credentials. That playbook has not changed. Mass scanners sweep the entire IPv4 space looking for port 23 within minutes, and an exposed GCP instance will start receiving login attempts almost immediately.

The attack chain is short:

  1. A scanner finds your open port 23.
  2. It runs through a dictionary of default and common credentials.
  3. If it gets in, it drops a payload, enrolls the host in a botnet, or pivots deeper into your VPC.
  4. Even if it fails to log in, anyone sniffing the path to your server can capture any legitimate credentials you type.

Warning: A compromised instance inside your VPC is rarely the end goal. Attackers use it as a foothold to reach databases, internal APIs, and metadata endpoints that were never meant to be exposed. Closing the Telnet port is about protecting everything behind it, not just the one host.

Compliance impact

Public Telnet exposure will fail almost every common framework. PCI DSS explicitly requires strong cryptography for administrative access, CIS Benchmarks call out unrestricted ingress on management ports, and SOC 2 auditors will treat it as a clear control gap. If you are working toward any of these, this finding needs to be resolved before assessment.


How to fix it

First, find the offending rule. List firewall rules in the affected project and look for anything allowing TCP 23 from a public source.

gcloud compute firewall-rules list \
  --filter="direction=INGRESS AND allowed.ports:23" \
  --format="table(name, network, sourceRanges.list(), allowed[].map().firewall_rule().list(), targetTags.list())"

If you want to inspect a specific rule in detail:

gcloud compute firewall-rules describe RULE_NAME --format=json

Once you know which rule is at fault, you have three options depending on whether you still need any Telnet access at all.

Option 1: Delete the rule (preferred)

If nothing legitimately needs Telnet, and almost nothing should, delete the rule entirely.

Danger: Deleting a firewall rule takes effect immediately and applies to every instance it targeted. Confirm that no production service depends on this rule before you run the command. If you are unsure, restrict the source range first (Option 2) and observe traffic before removing it.

gcloud compute firewall-rules delete RULE_NAME

Option 2: Restrict the source range

If you genuinely need Telnet to reach a legacy device, never expose it to the entire internet. Limit the source to a specific trusted CIDR, such as a corporate egress IP or a bastion subnet.

gcloud compute firewall-rules update RULE_NAME \
  --source-ranges="203.0.113.10/32"

For a brand new, narrowly scoped rule that only applies to tagged instances:

gcloud compute firewall-rules create allow-telnet-trusted \
  --network=my-vpc \
  --direction=INGRESS \
  --action=ALLOW \
  --rules=tcp:23 \
  --source-ranges="203.0.113.10/32" \
  --target-tags=legacy-appliance

Option 3: Replace Telnet with SSH

The real fix is to stop using Telnet. For Compute Engine instances, use SSH, which encrypts the session. Even better, use IAP TCP forwarding so the instance needs no public IP and no inbound port at all.

# Connect through Identity-Aware Proxy, no public ingress required
gcloud compute ssh INSTANCE_NAME \
  --tunnel-through-iap \
  --zone=us-central1-a

To allow IAP, you only need to permit ingress from Google's IAP range on port 22, not from the public internet:

gcloud compute firewall-rules create allow-ssh-iap \
  --network=my-vpc \
  --direction=INGRESS \
  --action=ALLOW \
  --rules=tcp:22 \
  --source-ranges="35.235.240.0/20"

Tip: IAP TCP forwarding lets you reach instances without any public IP. Combined with IAM-based access control, it removes the need to manage SSH keys and source IP allowlists by hand. It is the single biggest upgrade you can make over a port-based access model.


Fixing it with Terraform

If your infrastructure is managed as code, fix the source there so the change does not get reverted on the next apply. A safe SSH-over-IAP rule looks like this:

resource "google_compute_firewall" "allow_ssh_iap" {
  name      = "allow-ssh-iap"
  network   = google_compute_network.my_vpc.name
  direction = "INGRESS"

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

  # Google IAP range only, never 0.0.0.0/0
  source_ranges = ["35.235.240.0/20"]
  target_tags   = ["ssh-iap"]
}

If a Telnet rule exists in your state, remove the resource block and run terraform apply. Do not leave it defined with a tighter source range unless you have a hard requirement, because the next engineer who copies the block may widen it again.


How to prevent it from happening again

Closing one rule is easy. Stopping the next one from being created is what keeps the finding from coming back.

Use Organization Policy constraints

GCP lets you block public ingress on sensitive ports at the org or folder level. Apply the policy that restricts protocol forwarding and use VPC firewall rules with hierarchical policies to deny ingress to port 23 by default. A deny-all baseline rule with a high priority means a misconfigured allow rule lower in the stack never takes effect.

Add a policy-as-code gate in CI

Catch the bad rule before it is ever applied. With Terraform, OPA or Conftest can reject any plan that opens port 23 to the world.

cat > telnet.rego <<'EOF'
package firewall

deny[msg] {
  rule := input.resource.google_compute_firewall[name]
  rule.allow[_].ports[_] == "23"
  rule.source_ranges[_] == "0.0.0.0/0"
  msg := sprintf("Firewall '%s' exposes Telnet (23) to the public internet", [name])
}
EOF

conftest test plan.json --policy telnet.rego

Tip: Wire this check into your pull request pipeline so a failing plan blocks the merge. A developer gets feedback in seconds, long before anything reaches a live project, and your security team stops playing whack-a-mole with firewall rules.

Run continuous detection

Policies and CI gates cover the code path, but rules also get created by hand in the console or by scripts that bypass review. Lensix scans your live GCP environment on a schedule and re-runs vpc_opentelnet against the actual firewall configuration, so a manually created rule surfaces even when it never touched your repository.


Best practices

  • Never use Telnet for anything. If a device only speaks Telnet, put it behind a bastion or VPN and never give it a public route.
  • Default to deny. Build firewall rules as an explicit allowlist on top of a deny-all baseline rather than opening ranges and trimming later.
  • Scope rules with target tags or service accounts. An untagged rule applies to the whole VPC, which turns a small mistake into a wide exposure.
  • Avoid wide port ranges. Rules like tcp:0-65535 silently include 23, 3389, and every other management port. Open only the ports you actually need.
  • Prefer IAP over public IPs. If an instance does not need to be reachable from the internet, do not give it a public IP and do not open inbound ports.
  • Audit firewall rules regularly. Stale rules accumulate. Schedule a review and delete anything that no longer maps to a current need.

Telnet on a public port is a small finding with an outsized blast radius. Closing it costs a single command, and the prevention work pays off across every other risky ingress rule you will ever write.