This check flags Azure Network Security Group rules that expose Kibana (port 5601) to the public internet. An open Kibana endpoint leaks logs, dashboards, and sometimes cluster credentials to anyone who scans for it. Lock the rule down to known IPs or put Kibana behind a private network and an authenticated proxy.
Kibana is the window into your Elasticsearch data. It is also one of the most commonly exposed services on the public internet, right alongside open databases and orphaned S3 buckets. The nsg_openkibana check looks at your Azure Network Security Groups and raises a finding when a rule permits inbound traffic to TCP port 5601 from a public source such as 0.0.0.0/0 or the Internet service tag.
If that rule exists, your Kibana dashboard is reachable from anywhere. Whether that is actually a breach waiting to happen depends on how Kibana is configured, but the exposure itself is the problem, and it is almost never intentional.
What this check detects
The check inspects every inbound security rule across your NSGs and matches on three conditions at once:
- Direction is
Inbound - Access is
Allow - Destination port covers
5601(either directly, as part of a range, or via*) - Source is a public address prefix such as
0.0.0.0/0,*, or theInternetservice tag
When all of those line up, the rule is letting the public reach Kibana, and the finding fires.
Note: Port 5601 is the default Kibana HTTP port. Some teams run Kibana behind a reverse proxy on 443 instead, in which case this specific check may not catch it, but the same exposure risk applies to whatever port you forward. Treat 5601 as the canonical signal, not the only one to watch.
Why it matters
Kibana sits in front of Elasticsearch, and Elasticsearch usually holds your logs, application telemetry, audit trails, and frequently personal or customer data. A publicly reachable Kibana instance is a problem for a few concrete reasons.
Unauthenticated Kibana exposes everything behind it
The open source Kibana distribution historically shipped without authentication enabled by default. If security features are not turned on, anyone who hits the URL gets full read access to every index, every dashboard, and the Dev Tools console, which lets them run arbitrary queries against Elasticsearch. That includes deleting indices.
It is trivially discoverable
Scanners like Shodan and Censys index every internet-facing Kibana instance continuously. You do not need to be targeted to be found. A new public 5601 endpoint will typically show up in scan results within hours, and automated bots probe it shortly after.
Real exploitation history
Kibana has had remote code execution vulnerabilities, most famously CVE-2019-7609, a prototype pollution bug in the Timelion feature that allowed authenticated and in some configurations unauthenticated attackers to run code on the host. Exposed Kibana panels have been a recurring entry point in the Meow attacks and various data extortion campaigns where attackers wipe or ransom open Elasticsearch clusters.
Danger: If your Elasticsearch cluster has no authentication and Kibana is public, assume the data is already compromised. Attackers routinely dump, delete, and ransom open clusters. Rotate any credentials or tokens stored in those indices and review access logs before you do anything else.
How to fix it
The fix is to stop the public from reaching port 5601. You have a few options depending on how Kibana is accessed. Start by finding the offending rule.
1. Identify the rule
List NSG rules and filter for anything touching 5601:
az network nsg rule list \
--resource-group my-rg \
--nsg-name my-nsg \
--query "[?destinationPortRange=='5601' || contains(destinationPortRanges, '5601')].{Name:name, Source:sourceAddressPrefix, Access:access, Port:destinationPortRange}" \
--output table
If you are not sure which NSG holds the rule, loop over all of them:
for nsg in $(az network nsg list --query "[].name" -o tsv); do
echo "NSG: $nsg"
az network nsg rule list --resource-group my-rg --nsg-name "$nsg" \
--query "[?access=='Allow' && direction=='Inbound'].{Name:name, Port:destinationPortRange, Source:sourceAddressPrefix}" \
-o table
done
2. Restrict the source to known IPs
If the team genuinely needs Kibana over the internet, scope the source to your office or VPN ranges instead of the whole world:
az network nsg rule update \
--resource-group my-rg \
--nsg-name my-nsg \
--name allow-kibana \
--source-address-prefixes 203.0.113.0/24 198.51.100.10/32
Warning: Hardcoded office IP allowlists drift over time as ISPs reassign addresses and remote workers come and go. They are a reasonable stopgap, but a private endpoint plus VPN or a bastion is far more durable. Revisit allowlists quarterly.
3. Or remove public access entirely
Danger: Deleting or denying this rule will cut off anyone currently reaching Kibana through it, including legitimate users and any automation that hits the endpoint. Confirm the access path before you run this, especially in production.
If Kibana should never be public, delete the rule:
az network nsg rule delete \
--resource-group my-rg \
--nsg-name my-nsg \
--name allow-kibana
Or keep the rule for documentation but flip it to deny while you migrate users to a private path:
az network nsg rule update \
--resource-group my-rg \
--nsg-name my-nsg \
--name allow-kibana \
--access Deny
4. Fix it in Terraform
If your infrastructure is managed as code, the NSG rule lives in your config and editing the console will only cause drift. Update the source there instead:
resource "azurerm_network_security_rule" "kibana" {
name = "allow-kibana"
priority = 200
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "5601"
# Before: source_address_prefix = "*" <-- public, do not do this
source_address_prefixes = ["203.0.113.0/24", "198.51.100.10/32"]
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.main.name
network_security_group_name = azurerm_network_security_group.main.name
}
5. Better: put Kibana behind a private path
The strongest fix removes the public route altogether. Common patterns:
- Keep Kibana on a private subnet and reach it through an Azure Bastion host or a point-to-site VPN.
- Front it with Application Gateway or Azure Front Door plus an authenticating proxy so users hit 443 with SSO, never 5601 directly.
- If you are on the managed offering, use Elastic Cloud on Azure with private link, which keeps traffic off the public internet entirely.
Tip: Whatever you do at the network layer, also turn on Elasticsearch security and require authentication in Kibana. Network controls and authentication are layers, not substitutes. An allowlisted but unauthenticated Kibana still trusts every device behind that IP.
How to prevent it from happening again
Fixing one rule is easy. Stopping the next one from shipping is the real work. Add guardrails so a public 5601 rule cannot reach production unnoticed.
Azure Policy to deny public 5601 inbound
This custom policy denies any NSG rule that allows inbound traffic to 5601 from the internet:
{
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Network/networkSecurityGroups/securityRules" },
{ "field": "Microsoft.Network/networkSecurityGroups/securityRules/direction", "equals": "Inbound" },
{ "field": "Microsoft.Network/networkSecurityGroups/securityRules/access", "equals": "Allow" },
{ "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange", "equals": "5601" },
{
"anyOf": [
{ "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "*" },
{ "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "0.0.0.0/0" },
{ "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "Internet" }
]
}
]
},
"then": { "effect": "deny" }
}
Assign it at the management group or subscription scope so it applies everywhere by default.
Catch it in CI/CD with policy-as-code
Scan Terraform plans before they apply. A Checkov or tfsec step in your pipeline will fail the build on an open management port:
# Fail the pipeline if any IaC scan rule trips
checkov -d . --framework terraform --compact
Or write a focused Conftest policy in Rego that rejects 5601 from the internet:
package main
deny[msg] {
rule := input.resource.azurerm_network_security_rule[name]
rule.access == "Allow"
rule.direction == "Inbound"
rule.destination_port_range == "5601"
rule.source_address_prefix == "*"
msg := sprintf("NSG rule '%s' exposes Kibana (5601) to the internet", [name])
}
Tip: Run Lensix on a schedule so the same rule is enforced against what is actually deployed, not just what is in the repo. Console edits, emergency changes, and resources created outside your IaC all bypass pipeline checks. Continuous scanning closes that gap.
Best practices
- Default deny inbound. NSGs should only open the ports a workload actually serves. Management and observability tools like Kibana rarely belong on the public internet.
- Never use
*or0.0.0.0/0as a source for anything beyond a deliberately public web tier on 80 and 443. - Enable Elasticsearch security. Turn on native authentication, set strong passwords for built-in users, and enable TLS between Kibana and Elasticsearch.
- Prefer private connectivity. Bastion, VPN, or private link beat IP allowlists for any internal tool.
- Patch Kibana promptly. Several of its worst CVEs were remote code execution. An exposed and unpatched instance is the highest-risk combination there is.
- Audit NSG rules continuously. Rules accumulate. What was a temporary debug rule six months ago is now a standing exposure nobody remembers creating.
Public Kibana is one of those misconfigurations that looks harmless in a sprint planning ticket and turns into an incident report later. The fix is cheap, the exposure is expensive, and the prevention is a one-time policy. Close the rule, push the guardrail, and move on.

