Safeguard
Vulnerability Analysis•

Atlassian CVE-2026-21589: A Pre-Auth File Read in Eight Products, and Why crowd.properties Decides the Damage

CVE-2026-21589 is in CISA KEV, due 18 Oct. Fixed versions for all eight Atlassian Data Center products, how the bug works, and a log and webroot check.

Safeguard Security Team
Security Research
9 min read

CISA added CVE-2026-21589 to its Known Exploited Vulnerabilities catalog on 15 October 2026, with a remediation date of 18 October for federal agencies. The bug lets an unauthenticated attacker download files from the web application directory of eight Atlassian Data Center products: Jira Software, Jira Service Management, Confluence, Bitbucket, Bamboo, Crowd, Crucible and Fisheye. Atlassian fixed it on 5 October. watchTowr Labs published a root-cause analysis and a working exploit the next day, including a chain from a leaked Crowd password to Jira administrator.

This post is for teams running self-managed Atlassian products. It covers which versions to move to, how the bug works without the exploit paths, why the impact depends on which files sit in your web root, and how to check logs and installs for what an attacker could have read. The script below implements Atlassian's own log-search guidance.

The short answer

Patch to a fixed version: Jira Software 9.12.40, 10.3.26 or 11.3.12; Jira Service Management 5.12.40, 10.3.26 or 11.3.12; Confluence 9.2.26 or 10.2.19; Bitbucket 9.4.26, 10.2.8 or 10.5.1; Bamboo 10.2.24 or 12.1.12; Crowd 6.3.7, 7.0.3, 7.1.7 or 7.2.4; Crucible and Fisheye 4.9.15. Atlassian says every earlier version is affected. If you can't patch now, take internet-facing instances offline or apply Atlassian's WAF or Tomcat rewrite rule. Then search access logs from 5 October onwards, and rotate any secret stored in the web application directory, starting with the Crowd application password in crowd.properties. Atlassian Cloud is already patched.

What the bug is

Atlassian's advisory describes an arbitrary file access bug that lets an unauthenticated attacker read "specific files within the web application root directory". The attacker has to know the exact name and path, because the bug can't list directories. Atlassian rates it 9.3 (CVSS 4.0) and notes that "in some configurations" sensitive files there raise the risk.

watchTowr's analysis, published 6 October, explains why so many products are affected. They share a library, atlassian-plugins-webresource, which serves static resources for plugins. Its router turns :: into / when it maps URLs back to resource paths. Tomcat and the library's own checks look for ../. They don't treat ..:: as traversal, and the router later converts it into one. A request for a plugin resource can then step out of the plugin's directory and read any file inside the deployed web application, including the WEB-INF directory. watchTowr found it couldn't escape the Tomcat context, so files elsewhere on the server are out of reach.

That's why Atlassian's mitigation regex looks unusual. It blocks two dots next to /, \ or ::, in plain and percent-encoded forms, including encoded-twice variants.

Why the impact depends on your configuration

Most of a web application directory is code and static files. The danger is configuration that holds credentials. watchTowr went looking for that and found the Crowd single sign-on setup. When Jira (or another product) uses Crowd for SSO, Atlassian's integration guide tells you to configure atlassian-jira/WEB-INF/classes/crowd.properties with application.name and application.password, the credentials Jira uses to talk to Crowd. That file sits inside the web root, so this bug can read it.

With those credentials, watchTowr called Crowd's REST API directly, created a user and added it to jira-administrators. Crowd lets you restrict which IP addresses an application can connect from. watchTowr notes that an allowlist makes the chain much harder, because the attacker then needs a way to reach Crowd from a permitted host.

So the questions that decide your exposure are:

  • Is any instance reachable from the internet, or from networks you don't trust?
  • Does any product use Crowd SSO through crowd.properties in its web root?
  • Has anyone added other secrets inside the web application directory, such as custom properties files or keystores?
  • Does Crowd restrict application connections to known addresses?

Timeline

DateEvent
5 October 2026Atlassian publishes the advisory and fixed versions, and patches Cloud
6 October 2026watchTowr publishes its analysis, exploit chain and a detection tool
15 October 2026CISA adds the CVE to KEV, with forensic triage required under BOD 26-04
18 October 2026KEV remediation due date for US federal civilian agencies

CISA's entry marks known ransomware use as "unknown". Its Vulnrichment record classes exploitation as active and technical impact as total.

If you can't patch today

Atlassian gives three temporary options, all aimed at the same pattern:

  1. A WAF or reverse proxy rule that blocks any URL matching its regex, for all affected products. Test that it catches the percent-encoded and double-encoded forms.
  2. Tomcat's RewriteValve for Confluence, Jira, Jira Service Management, Bamboo and Crowd, with a rewrite.config that checks both the normalised path and the raw URI.
  3. A urlrewrite.xml rule for Bitbucket, including mirrors and mirror farm nodes.

Atlassian's first recommendation, before any of these, is to remove instances from the internet until they're patched or mitigated. That includes instances that sit behind user authentication, since the bug doesn't need a login.

Check logs and installs

Atlassian's detection advice is to URL-decode each access-log request up to twice, then look for .. next to /, \ or ::, or to run its regex against raw lines. This script does both. A second mode walks a web application directory and lists the files an attacker could have asked for: credentials files, keystores and configuration containing password-like keys. It reads files and logs only, and uses the Python standard library.

#!/usr/bin/env python3
"""Hunt for CVE-2026-21589 attempts and check what an attacker could have read.

  logs MODE:    python3 atlassian_21589_hunt.py logs access_log [access_log.1 ...]
  webroot MODE: python3 atlassian_21589_hunt.py webroot /opt/atlassian/jira/atlassian-jira

logs: applies Atlassian's advisory guidance to access logs. Each request line is
checked raw with Atlassian's regex, then URL-decoded up to twice and checked for
".." next to "/", "\\" or "::". Prints hits with the HTTP status, because a 200
means a file was probably served.

webroot: lists files inside the web application directory that hold secrets,
which is everything this bug can reach. Read-only. Standard library only.
"""
import os, re, sys
from collections import Counter
from urllib.parse import unquote

# Regex copied from Atlassian's CVE-2026-21589 advisory (WAF option 1)
ATLASSIAN = re.compile(
    r"(?is).*(?:/|\\|::|%(?:25)*(?:2f|5c)|(?::|%(?:25)*3a){2})(?:\.|%(?:25)*2e){2}"
    r"(?:/|\\|::|%(?:25)*(?:2f|5c)|(?::|%(?:25)*3a){2}|;|%(?:25)*3b|$).*")
DECODED = re.compile(r"(?:/|\\|::)\.\.(?:/|\\|::|;|$)")
REQUEST = re.compile(r'"(?:GET|POST|HEAD|PUT|OPTIONS) (\S+)[^"]*" (\d{3})')
SECRET_NAMES = re.compile(r"(?i)(crowd\.properties|\.jks$|\.keystore$|\.p12$|\.pem$|\.key$|dbconfig\.xml$)")
SECRET_TEXT = re.compile(r"(?i)^\s*[\w.\-]*(password|passwd|secret|token)[\w.\-]*\s*[=:]\s*\S+", re.M)


def hunt_logs(paths):
    hits, statuses, sources = 0, Counter(), Counter()
    for path in paths:
        with open(path, errors="replace") as f:
            for n, line in enumerate(f, 1):
                m = REQUEST.search(line)
                if not m:
                    continue
                url, status = m.group(1), m.group(2)
                path_only = url.split("?", 1)[0]
                once = unquote(path_only)
                twice = unquote(once)
                if not (ATLASSIAN.match(path_only) or DECODED.search(once) or DECODED.search(twice)):
                    continue
                hits += 1
                statuses[status] += 1
                sources[line.split(" ", 1)[0]] += 1
                flag = "SERVED " if status == "200" else ""
                print(f"{os.path.basename(path)}:{n} {flag}[{status}] {twice[:110]}")
    print(f"\n{hits} matching request(s); status codes {dict(statuses)}; {len(sources)} source address(es)")
    return hits


def scan_webroot(root):
    found = 0
    for d, _, files in os.walk(root):
        for name in files:
            p = os.path.join(d, name)
            rel = os.path.relpath(p, root)
            why = None
            if SECRET_NAMES.search(name):
                why = "sensitive file type"
            elif name.endswith((".properties", ".xml", ".yml", ".yaml", ".conf")) and os.path.getsize(p) < 2_000_000:
                with open(p, errors="replace") as f:
                    m = SECRET_TEXT.search(f.read())
                if m:
                    why = f"contains '{m.group(0).split('=')[0].split(':')[0].strip()}'"
            if why:
                found += 1
                print(f"  {rel}: {why}")
    print(f"\n{found} file(s) under {root} an attacker could have requested by name")
    return found


def main():
    if len(sys.argv) < 3 or sys.argv[1] not in ("logs", "webroot"):
        sys.exit(__doc__)
    n = hunt_logs(sys.argv[2:]) if sys.argv[1] == "logs" else scan_webroot(sys.argv[2])
    sys.exit(1 if n else 0)


if __name__ == "__main__":
    main()

We ran it on 16 October against a sample access log and a sample Jira web root. The log has normal traffic, a plain attempt, a percent-encoded one, a double-encoded one and a classic ../ probe. The web root has a crowd.properties and a custom mail settings file with a password:

$ python3 atlassian_21589_hunt.py logs access_log
access_log:3 SERVED [200] /s/1/_/download/resources/example.plugin:assets/images/..::..::..::WEB-INF::web.xml
access_log:4 [404] /s/1/_/download/resources/example.plugin:assets/images/..::..::WEB-INF::classes::crowd.properties
access_log:5 SERVED [200] /s/1/_/download/resources/example.plugin:assets/images/..::..::WEB-INF::classes::crowd.properties
access_log:6 [400] /s/1/_/download/resources/../../../../etc/passwd

4 matching request(s); status codes {'200': 2, '404': 1, '400': 1}; 2 source address(es)
exit 1

$ python3 atlassian_21589_hunt.py webroot webroot/atlassian-jira
  WEB-INF/classes/mail-defaults.properties: contains 'smtp.password'
  WEB-INF/classes/crowd.properties: sensitive file type

2 file(s) under webroot/atlassian-jira an attacker could have requested by name
exit 1

The benign lines, including a search query containing a..b and a file named loading...gif, aren't flagged. A 200 on a matching request means a file was probably served. If the path ends in crowd.properties or another file the webroot mode lists, treat that secret as stolen.

What to do

  1. Patch every affected product, not only Jira and Confluence. Bamboo, Bitbucket, Crowd, Crucible and Fisheye are on the list too.
  2. Search access logs from 5 October onwards, on every node and on any proxy or load balancer in front of them.
  3. Rotate exposed secrets whether or not you find a hit, if they live in the web root. For Crowd SSO, change the application password in Crowd and in crowd.properties.
  4. Check Crowd for new users and group changes, especially additions to administrator groups, made through the application's REST access.
  5. Restrict Crowd's application connections to the addresses of the products that use it.

The fair view

The bug has limits. It can't list directories or leave the web application, and a default install may have little worth stealing in its web root. Atlassian shipped fixes across every supported line on one day, published three mitigations and gave clear detection guidance. Atlassian Cloud was already patched when the advisory went out. Neither the advisory nor the CVE record names who reported the bug. The root-cause and exploit-chain research described here is watchTowr Labs'.

But the conditions that make it serious are common in older self-managed estates: internet-facing Jira and Confluence, Crowd SSO configured years ago, and secrets put next to code because that's where the integration guide pointed. A file-read bug is only as bad as the files it can reach. That's worth knowing before the next one. Our earlier post on CVE-2019-11580 in Crowd covers another time the identity hub behind Atlassian products was the real target.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.