Threat Overview
Atlassian disclosed CVE-2026-21589, a critical path-traversal/arbitrary-file-access vulnerability affecting self-managed editions of eight products. An unauthenticated remote attacker who knows or can infer an exact file path can read a file accessible to the application service account. The flaw does not provide directory listing, so exploitation requires a target filename or path; configuration files, application secrets, private keys, tokens, and other predictable files remain meaningful targets.
Atlassian rates the issue CVSS 9.3 and states that all earlier versions of the listed products are affected. The company patched its cloud services and reported no evidence of exploitation in its cloud investigation as of the advisory date. That statement does not establish that self-managed instances have not been probed or exploited. Atlassian also provided temporary WAF/rewrite guidance that blocks .. adjacent to /, \, or ::, including URL-encoded forms. This hunt therefore concentrates on raw and decoded traversal patterns, successful responses, source fan-out, and subsequent authenticated access from the same source rather than speculative endpoint execution.
References
- Atlassian, “CVE-2026-21589 – Arbitrary File Access Vulnerability impacts Multiple Products,” advisory released October 5, 2026: https://confluence.atlassian.com/security/cve-2026-21589-arbitrary-file-access-vulnerability-impacts-multiple-products-1870495748.html
- Atlassian Security Advisories, current advisory index accessed October 6, 2026: https://www.atlassian.com/trust/security/advisories
- The Hacker News, “Critical Atlassian Flaw Lets Unauthenticated Attackers Read Known Files Across 8 Products,” published October 6, 2026: https://thehackernews.com/2026/10/critical-atlassian-flaw-lets.html
Impacted Systems
- Bitbucket Data Center: all earlier releases; fixed in 9.4.26, 10.2.8, and 10.5.1 maintenance lines. Bitbucket Cloud is patched/not affected by customer action.
- Confluence Data Center: all earlier releases; fixed in 9.2.26 and 10.2.19.
- Jira Software Data Center: all earlier releases; fixed in 9.12.40, 10.3.26, and 11.3.12.
- Jira Service Management Data Center: all earlier releases; fixed in 5.12.40, 10.3.26, and 11.3.12.
- Bamboo Data Center: all earlier releases; the advisory’s fixed-version list states 10.2.24 and 12.1.12. Atlassian page fields have displayed an inconsistent 10.2.4/10.2.24 value; use the current vendor download/advisory and install the newest supported maintenance release.
- Crowd Data Center: all earlier releases; the advisory’s fixed-version list states 6.3.7, 7.0.3, 7.1.7, and 7.2.4. Atlassian page fields/tickets have displayed a 7.1.6/7.1.7 discrepancy; use the newest supported release rather than treating the lower value as authoritative.
- Crucible and Fisheye: all earlier releases; fixed in 4.9.15.
- Platform/deployment: self-managed/on-premises Data Center applications on supported Windows or Linux hosts, commonly Java/Tomcat based and exposed through a reverse proxy or load balancer.
- Prerequisites: no authentication; the attacker must know or infer an exact readable path. No directory-listing capability is documented.
- Exposure: highest for Internet-facing product URLs. Internal-only instances remain reachable after phishing, VPN compromise, or another foothold.
- Cloud: Atlassian-hosted cloud products were patched by Atlassian; no customer patch action is required for those services.
Why this matters
The affected product set is broad and includes collaboration, source-code, CI/CD, service-management, and identity-adjacent systems that often store reusable credentials or integration secrets. A read-only exploit may not create processes or files, so perimeter and application request telemetry are the primary opportunity to identify attempts. Internet-facing Data Center deployments warrant rapid version and exposure validation even though exploitation has not been confirmed.
Exploitation Status
No confirmed active exploitation in the cited sources. Atlassian reported no evidence of exploitation in the cloud-service investigation and did not publish attacker infrastructure, hashes, or a product-specific exploit URI. Public scanners may quickly adopt generic encoded traversal patterns. A request matching the mitigation syntax is an attempt indicator, not proof of file disclosure; response status and byte counts are important context.
What this hunt looks for
Affected product versions, literal and encoded traversal syntax, one- and two-pass decoding, apparently successful responses, one-source fan-out across paths or hosts, follow-on authenticated access, WAF blocks, and Syslog access-log fallback activity.
Required logs
Authoritative Atlassian product inventory; WAF, reverse-proxy, load-balancer, or application access logs in CommonSecurityLog or AzureDiagnostics that preserve raw request targets and response status; and Syslog for web/application access-log fallback coverage.
Hunt 1 — First-pass Atlassian product and version inventory
let lookback = 14d;
DeviceTvmSoftwareInventory
| where Timestamp >= ago(lookback)
| where SoftwareVendor has "Atlassian" or SoftwareName has_any ("Bitbucket", "Confluence", "Jira", "Bamboo", "Crowd", "Crucible", "Fisheye")
| summarize LastSeen=max(Timestamp), Devices=make_set(DeviceName, 50), DeviceCount=dcount(DeviceId) by SoftwareVendor, SoftwareName, SoftwareVersion
| order by SoftwareName asc, SoftwareVersion asc
Hunt 2 — Raw encoded and literal traversal patterns in perimeter logs
let lookback = 30d;
union isfuzzy=true CommonSecurityLog, AzureDiagnostics
| where TimeGenerated >= ago(lookback)
| extend Raw=tostring(pack_all()), Src=coalesce(column_ifexists("SourceIP", ""), column_ifexists("clientIp_s", ""), column_ifexists("CallerIPAddress", "")), Dst=coalesce(column_ifexists("DestinationHostName", ""), column_ifexists("host_s", ""), column_ifexists("requestUri_s", ""))
| where Raw has_any ("../", @"..\", "..::", "%2e%2e%2f", "%2e%2e%5c", "%2e%2e%3a%3a", "%252e%252e%252f", "%252e%252e%255c")
| project TimeGenerated, Src, Dst, Raw=substring(Raw, 0, 1800)
| order by TimeGenerated desc
Hunt 3 — One- and two-pass decoded traversal detection
let lookback = 30d;
union isfuzzy=true CommonSecurityLog, AzureDiagnostics
| where TimeGenerated >= ago(lookback)
| extend Raw=tostring(pack_all()), Src=coalesce(column_ifexists("SourceIP", ""), column_ifexists("clientIp_s", ""), column_ifexists("CallerIPAddress", ""))
| extend Decoded1=url_decode_component(Raw)
| extend Decoded2=url_decode_component(Decoded1)
| where Decoded1 matches regex @"(?i)\.\.(\/|\\|::)" or Decoded2 matches regex @"(?i)\.\.(\/|\\|::)"
| project TimeGenerated, Src, Raw=substring(Raw, 0, 900), Decoded=substring(Decoded2, 0, 1400)
| order by TimeGenerated desc
Hunt 4 — Traversal attempts with apparently successful HTTP responses
let lookback = 30d;
union isfuzzy=true CommonSecurityLog, AzureDiagnostics
| where TimeGenerated >= ago(lookback)
| extend Raw=tostring(pack_all()), Src=coalesce(column_ifexists("SourceIP", ""), column_ifexists("clientIp_s", ""), column_ifexists("CallerIPAddress", "")), Status=tostring(coalesce(column_ifexists("HttpStatusCode", ""), column_ifexists("scStatus_s", ""), column_ifexists("httpStatus_d", ""))), Bytes=tolong(coalesce(column_ifexists("SentBytes", long(0)), column_ifexists("responseBytes_d", long(0)), column_ifexists("bytesSent_d", long(0))))
| extend Decoded2=url_decode_component(url_decode_component(Raw))
| where Decoded2 matches regex @"(?i)\.\.(\/|\\|::)"
| where Status startswith "2" or Status startswith "3"
| project TimeGenerated, Src, Status, Bytes, Request=substring(Decoded2, 0, 1600)
| order by TimeGenerated desc
Hunt 5 — One source probing multiple paths or Atlassian hosts
let lookback = 30d;
union isfuzzy=true CommonSecurityLog, AzureDiagnostics
| where TimeGenerated >= ago(lookback)
| extend Raw=tostring(pack_all()), Src=coalesce(column_ifexists("SourceIP", ""), column_ifexists("clientIp_s", ""), column_ifexists("CallerIPAddress", "")), Host=coalesce(column_ifexists("DestinationHostName", ""), column_ifexists("host_s", ""), column_ifexists("Resource", ""))
| extend Decoded2=url_decode_component(url_decode_component(Raw))
| where Decoded2 matches regex @"(?i)\.\.(\/|\\|::)"
| summarize Requests=count(), Hosts=dcount(Host), HostSet=make_set(Host, 20), Samples=make_set(substring(Decoded2, 0, 500), 5), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Src, bin(TimeGenerated, 10m)
| where Requests >= 3 or Hosts >= 2
| order by Requests desc
Hunt 6 — Traversal attempt followed by authenticated or administrative access
let lookback = 30d;
let Http = union isfuzzy=true CommonSecurityLog, AzureDiagnostics
| where TimeGenerated >= ago(lookback)
| extend Raw=tostring(pack_all()), Src=coalesce(column_ifexists("SourceIP", ""), column_ifexists("clientIp_s", ""), column_ifexists("CallerIPAddress", "")), User=coalesce(column_ifexists("SourceUserName", ""), column_ifexists("user_s", ""), column_ifexists("User", ""))
| extend Decoded2=url_decode_component(url_decode_component(Raw));
let Attempts = Http
| where Decoded2 matches regex @"(?i)\.\.(\/|\\|::)"
| summarize AttemptTime=min(TimeGenerated) by Src;
Http
| where isnotempty(User) or Decoded2 has_any ("/admin", "/plugins/servlet", "/rest/api", "/secure/admin", "/projects", "/repos")
| join kind=inner Attempts on Src
| where TimeGenerated between (AttemptTime .. AttemptTime + 2h)
| project TimeGenerated, Src, User, AttemptTime, Request=substring(Decoded2, 0, 1600)
| order by TimeGenerated desc
Hunt 7 — WAF blocks matching Atlassian temporary mitigation syntax
let lookback = 30d;
union isfuzzy=true CommonSecurityLog, AzureDiagnostics
| where TimeGenerated >= ago(lookback)
| extend Raw=tostring(pack_all()), Src=coalesce(column_ifexists("SourceIP", ""), column_ifexists("clientIp_s", ""), column_ifexists("CallerIPAddress", "")), Action=coalesce(column_ifexists("DeviceAction", ""), column_ifexists("action_s", ""), column_ifexists("Action", ""))
| extend Decoded2=url_decode_component(url_decode_component(Raw))
| where Decoded2 matches regex @"(?i)\.\.(\/|\\|::)"
| where Action has_any ("block", "deny", "drop", "prevent") or Raw has_any ("Blocked", "Matched", "WAF")
| summarize Blocks=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Samples=make_set(substring(Decoded2, 0, 600), 5) by Src, Action
| order by Blocks desc
Hunt 8 — Syslog/application-access-log fallback
let lookback = 30d;
Syslog
| where TimeGenerated >= ago(lookback)
| where ProcessName has_any ("nginx", "httpd", "apache", "tomcat", "haproxy", "jira", "confluence", "bitbucket", "bamboo", "crowd") or Facility in ("local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7")
| extend Decoded2=url_decode_component(url_decode_component(SyslogMessage))
| where SyslogMessage has_any ("../", @"..\", "%2e%2e", "%252e%252e") or Decoded2 matches regex @"(?i)\.\.(\/|\\|::)"
| project TimeGenerated, Computer, HostName, ProcessName, SeverityLevel, SyslogMessage, Decoded=substring(Decoded2, 0, 1600)
| order by TimeGenerated desc
Detection Notes
- Hunts 2–4 are the highest-value request-layer detections. Preserve the raw request target before the proxy or application normalizes it; otherwise
%2e,%2f,%5c, and double encoding may disappear from logs. - Generic traversal strings attract Internet scanning and false positives. Confidence rises when the destination is a confirmed Atlassian instance, multiple exact paths are attempted, or a 2xx response returns a nontrivial byte count.
- A 200 response does not prove file disclosure; some products return branded error pages with status 200. Compare body size, content type, and normal response patterns in the native connector.
- This flaw is file read, not documented remote code execution. Endpoint process/file-creation hunts were intentionally not added because they would imply unsupported post-exploitation behavior.
pack_all()provides schema-tolerant discovery but can be expensive and may encounter connector truncation. Replace it with validated URI, query, response-code, response-byte, action, source-IP, and hostname fields after confirming each workspace schema.- If TLS terminates on the application and access logs are not ingested, the exploit can be invisible to Sentinel. Product inventory without request telemetry supports exposure validation but not retrospective exploitation determination.