Back to blog
AzureBest PracticesCloud SecurityDatabasesNetworking

NSG Allows Public Oracle: Closing Port 1521 to the Internet

Learn how to detect and fix Azure NSG rules that expose Oracle port 1521 to the public internet, with CLI fixes, Terraform, and Azure Policy guardrails.

TL;DR

This check flags any Azure Network Security Group rule that exposes Oracle Database (port 1521) to the public internet. Database ports should never face the open internet. Scope the source to known private ranges or remove the inbound rule entirely.

Oracle databases tend to hold the data that matters most: financial records, customer accounts, ERP backends, and the kind of business-critical state that keeps the lights on. When an Azure Network Security Group (NSG) opens TCP port 1521 to the world, you are inviting every botnet, vulnerability scanner, and opportunistic attacker on the internet to knock on your database listener directly.

The nsg_openoracle check looks for exactly this misconfiguration and tells you before someone else finds it.


What this check detects

The check inspects the inbound security rules across your Azure NSGs and raises a finding when a rule meets all of the following conditions:

  • Direction is Inbound
  • Access is Allow
  • Protocol is TCP (or *)
  • Destination port includes 1521, either directly, as part of a range, or via a wildcard
  • Source is a public address, meaning *, 0.0.0.0/0, Internet, or any CIDR that resolves to publicly routable space

Port 1521 is the default listener port for the Oracle TNS (Transparent Network Substrate) protocol. It is the front door clients use to establish a session with the database. If it is reachable from the internet, so is your data.

Note: A common mistake is assuming a rule using 0.0.0.0/0 is safe because the VM has no public IP. Effective exposure depends on the full picture: public IPs, load balancers, NAT rules, and peered networks. The NSG rule itself is the policy, and an overly broad source is a finding regardless of the current routing setup. Routing changes; the bad rule stays behind.


Why it matters

An exposed Oracle listener is not a theoretical risk. Internet-wide scanners catalog open port 1521 hosts continuously, and there is a well-established attack chain against them.

The TNS listener is a known target

Before a client authenticates to a database, it talks to the listener. Historically the Oracle listener has suffered from issues that an unauthenticated remote attacker can abuse:

  • Information disclosure. Commands like STATUS and SERVICES against an unprotected listener leak service names, version banners, and operating system details. That is reconnaissance handed over for free.
  • TNS Poison style attacks. Older listener configurations allowed remote service registration, enabling man-in-the-middle and session hijacking against connecting clients.
  • Credential brute forcing. Once a service name is known, attackers can hammer default and weak accounts (SYSTEM, SYS, SCOTT, DBSNMP) at internet speed.
  • Denial of service. A reachable listener can be flooded or crashed, taking your database offline.

The business impact

If an attacker gets a foothold in an Oracle instance, the blast radius is usually enormous. These databases sit at the center of business operations, and a breach often means regulated data (PCI, HIPAA, GDPR scoped records) walking out the door. Beyond the breach itself, you are looking at mandatory disclosure, regulatory fines, and the operational pain of rebuilding trust in your data.

Warning: Even with strong database authentication, an exposed listener gives attackers an unauthenticated surface to fingerprint and probe. Defense in depth means they should never reach the listener in the first place. Authentication is your last line, not your first.


How to fix it

The fix is to remove public access. There are two common paths: tighten the source on the offending rule, or delete the rule and route database access through a private, controlled channel.

Step 1: Find the offending rule

List the inbound rules on the NSG and identify which one opens 1521 to 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

Look for any rule where the port is 1521 (or a range that includes it, or *) and the source is *, Internet, or 0.0.0.0/0.

Step 2: Restrict the source to known ranges

If the database genuinely needs to be reachable from specific networks, for example a corporate VPN egress or a peered application subnet, scope the source to those CIDRs instead of the internet.

az network nsg rule update \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-oracle \
  --source-address-prefixes 10.20.0.0/24 198.51.100.10/32 \
  --destination-port-ranges 1521

Step 3: Or remove the public rule entirely

If there is no legitimate need for inbound 1521 from outside your virtual network, delete the rule. This is the cleanest outcome.

Danger: Deleting or tightening an NSG rule on a production database can immediately sever live connections from clients that were relying on that access. Confirm what is actually connecting (check connection logs and application configs) before you change anything. Make the change during a maintenance window if there is any doubt.

az network nsg rule delete \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-oracle

Step 4: Provide a private access path

Internet exposure is almost never the right answer for a database. Replace it with one of these patterns:

  • VNet peering or private subnet. Keep the database in a private subnet and allow 1521 only from the application subnet's CIDR.
  • VPN or ExpressRoute. Administrators and on-prem systems connect over an encrypted tunnel, never the open internet.
  • Azure Bastion or a jump host. For administrative access, use a hardened bastion rather than exposing the listener.

Define the secure rule in Terraform

If you manage NSGs as code, lock the source down to a private CIDR and keep it in version control:

resource "azurerm_network_security_rule" "oracle_private" {
  name                        = "allow-oracle-from-app-subnet"
  priority                    = 200
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "1521"
  source_address_prefix       = "10.20.0.0/24"
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.main.name
  network_security_group_name = azurerm_network_security_group.db.name
}

Tip: Use Application Security Groups (ASGs) instead of hardcoded CIDRs where you can. You attach an ASG to the application NICs and reference it as the source in the rule, so the policy follows the workloads even as IPs change. It is far less brittle than maintaining CIDR lists by hand.


How to prevent it from happening again

Fixing one rule is good. Making the misconfiguration impossible to ship is better. Push the guardrails left so a bad rule never reaches production.

Azure Policy as a deny gate

Azure Policy can flat-out deny the creation of NSG rules that expose sensitive ports to the internet. Assign a policy that targets Microsoft.Network/networkSecurityGroups/securityRules and denies any rule where the source is * or Internet and the destination port covers 1521.

{
  "if": {
    "allOf": [
      { "field": "type", "equals": "Microsoft.Network/networkSecurityGroups/securityRules" },
      { "field": "Microsoft.Network/networkSecurityGroups/securityRules/access", "equals": "Allow" },
      { "field": "Microsoft.Network/networkSecurityGroups/securityRules/direction", "equals": "Inbound" },
      {
        "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange",
        "equals": "1521"
      },
      {
        "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix",
        "in": [ "*", "0.0.0.0/0", "Internet" ]
      }
    ]
  },
  "then": { "effect": "deny" }
}

Note: Port ranges can hide exposure. A rule with destinationPortRange of 1000-2000 or * still includes 1521. A production-grade policy should account for ranges and the destinationPortRanges array, not just the exact single-port match shown above.

Catch it in CI/CD

Scan your IaC before it merges. Tools like tfsec, Checkov, and Terrascan all flag open database ports in Terraform and ARM templates. Wire one into your pipeline so a pull request that opens 1521 to the world fails the build.

# Example: fail the pipeline on a Checkov finding
checkov -d ./infra --framework terraform \
  --check CKV_AZURE_77 --soft-fail-on LOW

Continuous monitoring with Lensix

Policy gates and CI checks cover what you provision through code. Manual portal changes, drift, and rules created outside your pipeline still slip through. Lensix runs the nsg_openoracle check continuously across your subscriptions and alerts you the moment a public Oracle rule appears, so a 2 a.m. firefighting change does not quietly become a permanent exposure.


Best practices

  • Treat databases as private by default. No database listener should ever accept connections from 0.0.0.0/0. If you think you need it, you almost certainly need a VPN or private endpoint instead.
  • Use least-privilege NSG rules. Scope sources to specific CIDRs or ASGs, and scope ports to exactly what the workload needs. Avoid wildcard ports and wildcard sources together.
  • Layer your defenses. NSGs, subnet segmentation, the Oracle listener's own access control (such as TCP.VALIDNODE_CHECKING), and strong authentication should all be active. Each one is a backstop for the others.
  • Patch the listener. Apply Oracle's Critical Patch Updates promptly. Many listener-level attacks target known, already-patched issues.
  • Disable default and unused accounts. Lock down SCOTT, DBSNMP, and any sample schemas, and enforce strong passwords on privileged accounts.
  • Audit regularly. Re-run the check on a schedule. Network rules drift over time as teams add temporary access and forget to clean it up.

The safest internet-facing database port is the one that does not exist. If a rule opens 1521 to the world, the default assumption should be that it is a mistake until proven otherwise.

Close the door on port 1521, give your applications a private path to the database, and put a policy gate in front of your pipeline so the rule can never come back. Your future incident-response self will thank you.