This check flags Azure Network Security Group rules that allow inbound Telnet (port 23) from the public internet. Telnet sends everything, including credentials, in plaintext, so exposing it invites credential theft and remote takeover. Fix it by scoping or removing the offending NSG rule and switching to SSH instead.
Telnet has no business being open to the internet in 2024, yet it still turns up in NSG rules more often than you would expect. Usually it is a copy-pasted rule from an old runbook, a quick test someone forgot to clean up, or a legacy appliance that only speaks Telnet. Whatever the reason, an NSG that allows port 23 from 0.0.0.0/0 or Internet is a serious exposure, and Lensix raises nsg_opentelnet when it finds one.
What this check detects
The nsg_opentelnet check inspects every Network Security Group in your subscriptions and looks for inbound security rules that meet all of the following conditions:
- Direction is
Inbound - Access is
Allow - Protocol is
TCPor* - The destination port range includes 23 (directly, as a range like
20-30, or via*) - The source is a public address, such as
Internet,*,0.0.0.0/0, or any non-private CIDR
If a rule matches, the NSG is reported as allowing public Telnet. The check does not care whether a VM is actually attached to the NSG. An open rule that is waiting for a future workload is still a liability.
Note: Telnet is an unencrypted remote terminal protocol from 1969. Unlike SSH, it provides no encryption, no host authentication, and no integrity checking. Anyone positioned between the client and server can read or alter the session in real time.
Why it matters
Opening Telnet to the internet combines two of the worst properties a network rule can have: a wide-open source range and a protocol with zero confidentiality. Here is what that actually means for an attacker.
Plaintext credentials on the wire
Every keystroke in a Telnet session travels unencrypted. When a user logs in, the username and password are transmitted in cleartext. Anyone who can observe the traffic, whether through a compromised network hop, a malicious ISP, or a man-in-the-middle position, captures those credentials verbatim. Once an attacker has working credentials, they often pivot to SSH, RDP, or management APIs where the real damage happens.
Automated scanning and brute force
Port 23 is one of the most heavily scanned ports on the internet. Botnets like Mirai built themselves almost entirely on Telnet, sweeping the IPv4 space for exposed devices and hammering them with default credential lists. An exposed Azure VM running Telnet will start receiving login attempts within minutes of the rule going live.
Warning: Telnet is frequently exposed by legacy network appliances, IoT gateways, and management consoles that ship with weak or default credentials. These are exactly the targets automated worms hunt for, and a single compromised host can become a beachhead into your virtual network.
Compliance failures
Cleartext administrative protocols violate the controls in PCI DSS, CIS Azure Foundations Benchmark, and most internal security baselines. An open Telnet rule is a near-guaranteed audit finding, and it signals to assessors that NSG hygiene is weak across the board.
How to fix it
The remediation depends on whether you need Telnet at all. In almost every case you do not, and the right move is to remove the rule and use SSH instead.
Step 1: Find the offending rule
List inbound rules for the NSG and look for anything touching port 23 with a broad source.
az network nsg rule list \
--resource-group my-rg \
--nsg-name my-nsg \
--query "[?direction=='Inbound' && access=='Allow'].{Name:name, Port:destinationPortRange, Source:sourceAddressPrefix, Priority:priority}" \
--output table
Step 2: Remove the rule (preferred)
If nothing legitimately needs Telnet, delete the rule entirely.
Danger: Deleting an NSG rule changes live network behavior. Confirm no active session or critical service depends on port 23 before you run this. If a legacy appliance relies on Telnet, jump to the scoping option below instead of deleting outright.
az network nsg rule delete \
--resource-group my-rg \
--nsg-name my-nsg \
--name allow-telnet
Step 3: If you genuinely need Telnet, scope it tightly
Some legacy hardware only speaks Telnet. If you cannot migrate it immediately, restrict the source to a known management IP or your corporate range, and never leave it open to Internet.
az network nsg rule update \
--resource-group my-rg \
--nsg-name my-nsg \
--name allow-telnet \
--source-address-prefixes 203.0.113.10/32 \
--destination-port-ranges 23
Tip: Even better than an allow-listed source, put legacy Telnet devices behind Azure Bastion or a jump host inside the VNet, then block port 23 at the NSG entirely. The management traffic never traverses the public internet, and you get session logging for free.
Step 4: Replace Telnet with SSH
For Linux VMs, switch remote access to SSH (port 22) and tighten the source there too. SSH gives you encryption, host key verification, and key-based authentication.
az network nsg rule create \
--resource-group my-rg \
--nsg-name my-nsg \
--name allow-ssh-mgmt \
--priority 200 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 203.0.113.10/32 \
--destination-port-ranges 22
Fixing it in infrastructure as code
If your NSGs come from Terraform or Bicep, fix the source there so the change survives the next apply. A console edit alone will be reverted the moment someone runs a deploy.
Terraform
resource "azurerm_network_security_rule" "mgmt_ssh" {
name = "allow-ssh-mgmt"
priority = 200
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "203.0.113.10/32"
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.main.name
network_security_group_name = azurerm_network_security_group.main.name
}
Note there is no Telnet rule at all. That is the point. Make sure no leftover destination_port_range = "23" block lingers in the module.
Bicep
{
"name": "allow-ssh-mgmt",
"properties": {
"priority": 200,
"direction": "Inbound",
"access": "Allow",
"protocol": "Tcp",
"sourcePortRange": "*",
"destinationPortRange": "22",
"sourceAddressPrefix": "203.0.113.10/32",
"destinationAddressPrefix": "*"
}
}
How to prevent it from coming back
A one-off fix is fine, but the same rule will reappear unless you build guardrails. Use a layered approach.
Azure Policy to deny it at the source
Azure ships a built-in policy that flags NSG rules permitting Telnet from any source. Assign it in Deny mode to block the rule before it ever lands.
az policy assignment create \
--name 'deny-public-telnet' \
--display-name 'Deny NSG rules allowing public Telnet' \
--policy '/providers/Microsoft.Authorization/policyDefinitions/8c66e294-e9e2-4d99-99f5-7e7 ' \
--params '{"effect": {"value": "Deny"}}'
Note: Look up the exact built-in definition with az policy definition list --query "[?contains(displayName, 'Telnet')]". Azure occasionally updates definition IDs, so resolve the current one rather than hardcoding.
Scan IaC in CI/CD
Catch the rule at pull request time with a policy-as-code scanner like Checkov or tfsec. Add it as a required check so a Telnet rule cannot merge.
checkov -d ./infra --framework terraform
Tip: Pair the build-time gate with continuous monitoring in Lensix. CI scans catch the IaC you control, but Lensix catches drift, manual console edits, and rules introduced by resources you do not manage in code.
Best practices
- Never use Telnet for remote administration. Use SSH for Linux and RDP over a private path for Windows.
- Default deny on inbound. Start NSGs with no public inbound access and open only what a workload demonstrably needs.
- Avoid public management ports entirely. Use Azure Bastion or a hardened jump host so 22, 23, and 3389 never face the internet.
- Scope sources to specific CIDRs. When a rule must exist, allow-list known IPs rather than
Internetor*. - Audit NSG rules on a schedule. Rules accumulate. Review them quarterly and prune anything without a clear owner and purpose.
- Treat console edits as drift. Manage NSGs in code and alert on any change made outside the pipeline.
Telnet is one of the easiest findings to close because the answer is almost always "remove it." Take the five minutes to delete the rule, swap any legacy dependency to a private path, and put a policy in place so it never sneaks back.

