DEV Community

Cover image for A CVSS 9.3 File-Read Bug Made Us Finally Map Everything We'd Left Facing the Internet
Rohit Bhadani
Rohit Bhadani Subscriber

Posted on

A CVSS 9.3 File-Read Bug Made Us Finally Map Everything We'd Left Facing the Internet

This week, Atlassian disclosed a critical vulnerability — CVE-2026-21589, CVSS 9.3 — affecting eight of its self-hosted Data Center products, including Jira and Confluence. No login required. An attacker who knows the right filename and path can pull specific files straight out of the web application root. Atlassian's own advice for anyone who can't patch immediately was blunt: take the instance off the public internet.

That last sentence is the one that got me. Not "patch urgently." Take it off the internet. Which only works as advice if you actually know which of your internal tools are reachable from the internet in the first place. We didn't, with the confidence I would have claimed twenty-four hours earlier.

The assumption that felt obviously true

We run a self-hosted Confluence instance for internal documentation. It's "internal" — behind a login page, linked only from our internal wiki homepage, never advertised anywhere public. My first assumption, reading the advisory, was that this didn't really apply to us: unauthenticated file read is scary for something genuinely public-facing, less scary for something nobody outside the company would ever stumble onto.

That's not a security boundary. That's hoping nobody looks.

Checking the wrong thing first

I went to check our firewall rules, assuming the real question was "is port 443 open to this instance from the public internet." It was — intentionally, so people could reach the wiki from home without VPN. Firewall rule confirmed what I expected: yes, reachable. I almost stopped there and filed it as "known, accepted risk, revisit later."

What I hadn't checked was everything else running on the same host. The Confluence instance shared a box with two other internal services from an earlier, less careful infrastructure phase — a staging Bamboo instance nobody had touched in eight months, and an internal file-browsing tool someone had spun up for a one-off migration eighteen months ago and never torn down. Both were also Data Center products on the affected list. Neither was in our asset inventory, because neither had ever gone through the process that would have put it there — they predated that process.

The actual gap

The real problem wasn't the Atlassian CVE specifically. It was that our mental model of "what's exposed to the internet" was built from memory and intention, not from anything that actually enumerates what's listening on a port anywhere in our infrastructure. The CVE was just the flashlight that happened to be pointed at that gap this week. Next month it'll be a different vendor, a different product, the same gap.

The fix

1. An actual, automated inventory, not a mental model.

#!/usr/bin/env bash
# nightly-exposure-scan.sh — run against every known host and subnet
for host in $(cat known_hosts.txt); do
  nmap -Pn -p- --open "$host" -oG - | \
    grep '/open/' | \
    awk -v h="$host" '{print h, $0}'
done > exposure_report_$(date +%F).txt

diff exposure_report_$(date -d yesterday +%F).txt exposure_report_$(date +%F).txt \
  || echo "New open ports detected — review before close of day."
Enter fullscreen mode Exit fullscreen mode

Run nightly, diffed against the previous run. New open ports get a Slack alert, not a quarterly audit finding.

2. We killed the two forgotten instances outright — the stale Bamboo box and the leftover migration tool had no reason to exist anymore and their removal took fifteen minutes once we actually found them.

3. For the Confluence instance we do need reachable, we moved it behind an allowlisted VPN-only path instead of the open internet, and we rebuilt it on infrastructure where "default deny inbound" is the starting state, not something we have to remember to configure. I'm the founder of Krova Cloud, and this is one of the defaults I'm most stubborn about: every VM starts with inbound traffic denied by default, and you explicitly open only what you mean to expose — not the other way around. It doesn't stop a vendor from shipping a CVE. It does mean that the eighteen-month-old forgotten box from the next migration starts locked down instead of starts open, because somebody has to deliberately choose to expose it rather than deliberately choose to lock it down after the fact.

Lessons

  • "It's internal, nobody would find it" is not a security control. If it has an IP address and an open port, something will find it eventually, intentionally or by accident.
  • Your asset inventory is only as good as the process that populates it. Anything that predates that process is invisible to it — which is exactly the stuff most likely to be running an old, unpatched version of something.
  • A vendor CVE affecting products you run is a prompt to check the specific advisory, but also a good reason to run a broader exposure sweep anyway — you'll usually find something the CVE wasn't even about.
  • Default-deny inbound networking turns "did someone remember to lock this down" into "did someone remember to deliberately open it," which fails a lot less often in practice.

If you haven't run an actual port scan against your own infrastructure recently — not what you think is exposed, what nmap says is exposed — this is a reasonable week to do it.


I'm Rohit, founder of Krova Cloud — cloud VMs with default-deny inbound networking, so forgotten and leftover infrastructure starts locked down instead of starts open. If you want more deep debugging stories like this one, I write regularly over at debugly.dev too.

Top comments (0)