Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-14361 — Consul Template's writeToFile helper opened an operator-supplied destination directly and followed linked path components, allowing rendered output to escape the intended directory and overwrite a preexisting file. | Kitploit
Tools/GitHubGitHub/0xmrma/cve-2026-14361
Vulnerability AnalysisCode AnalysisExploitationLearning & Education
GitHub0xmrma/cve-2026-14361

CVE-2026-14361

Consul Template's writeToFile helper opened an operator-supplied destination directly and followed linked path components, allowing rendered output to escape the intended directory and overwrite a preexisting file.

View Repository
52 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-14361

Consul Template's writeToFile helper opened an operator-supplied destination directly and followed linked path components, allowing rendered output to escape the intended directory and overwrite a preexisting file.

Intro

I found this issue while reviewing HashiCorp Consul Template, with a direct filesystem security question in mind:

If writeToFile receives a path that appears to be inside the intended directory, does it verify where the filesystem will actually write the data?

In this case, the answer was no.

The writeToFile template helper opened the final user-supplied path directly through os.Create() or os.OpenFile().

Those operations followed symbolic links, directory junctions, and equivalent filesystem redirections already present in the destination path.

That meant the path string could remain under the operator's intended root while the actual write landed somewhere else.

In my controlled proof of concept, a linked parent directory redirected rendered output outside the intended tree and caused a preexisting target file to be clobbered.

That issue became CVE-2026-14361.

HashiCorp bulletin: HCSEC-2026-20
IBM bulletin: CVE-2026-14361 security bulletin
CVE: CVE-2026-14361
Fixed in: 0.42.1

photo0


Attack Chain

operator-supplied destination appears inside intended root -> attacker pre-positions linked parent or final path component -> writeToFile opens the path directly -> filesystem resolves the write outside the intended directory -> rendered secret is redirected -> preexisting target may be overwritten


What writeToFile Does

Consul Template renders data from sources such as Consul and Vault.

The writeToFile helper lets a template write selected content to a separate local file while applying a requested owner, group, and permission mode.

HashiCorp's documentation specifically demonstrates the helper with PKI material:

private key -> writeToFile /my/path/to/cert.key
certificate authority -> writeToFile /my/path/to/cert.pem
certificate -> append to /my/path/to/cert.pem

That makes this more than an ordinary output helper.

The content crossing this boundary may include:

  • private keys
  • certificates
  • secrets fetched from Vault
  • configuration values
  • service credentials

The important question was not whether writeToFile could create a requested filename.

The real question was:

Does the process write to the operator's intended filesystem location, or merely to whatever object the path resolves to at open time?

In vulnerable versions, it trusted the latter.


Why This Surface Was Worth Looking At

Write helpers are high-value security surfaces because they cross from application data into filesystem mutation.

The interesting failures are often not classic ../ traversal.

They are resolution failures:

  • the string looks safe
  • the directory tree looks operator-controlled
  • a linked component changes the real destination
  • the open call follows that redirection automatically

That is especially important when the process runs with more filesystem privileges than the attacker.

A low-privileged local attacker may not be able to overwrite a sensitive file directly.

But if they can influence a path component under an intended write directory, a more privileged Consul Template process may perform the write for them.

That was the boundary I focused on.


The Boundary I Focused On

I did not approach this as a generic path traversal review.

The supplied path did not need .. segments.

It could remain lexically inside the intended root the entire time.

The stronger question was:

Are linked destination components rejected before sensitive content is written?

That question matters for both:

  • a linked final filename
  • a linked directory component that redirects the whole remaining path

The second case is especially useful because logs and configuration still show an innocent-looking path under the expected directory.

The filesystem resolves it somewhere else.


Root Cause

The root cause was direct path-based file creation without link-aware validation.

At the tested revision, writeToFile() selected one of two open paths.

Append mode used:

f, err = os.OpenFile(
    path,
    os.O_APPEND|os.O_WRONLY|os.O_CREATE,
    perm,
)

Normal write mode used:

dirPath := filepath.Dir(path)

if _, err := os.Stat(dirPath); err != nil {
    err := os.MkdirAll(dirPath, os.ModePerm)
    if err != nil {
        return "", err
    }
}

f, err = os.Create(path)

Neither path rejected linked destination components before the file was opened.

That matters because:

  • os.Create(path) follows existing filesystem redirections and truncates the resolved file
  • os.OpenFile(path, ...) follows linked path components during append mode
  • a linked directory changes where the final filename is resolved
  • a linked final component can redirect the open to a different existing file

The same path-based assumption continued after the write.

Ownership and permissions were applied using the path again:

err = os.Chown(path, uid, gid)
err = os.Chmod(path, perm)

That meant metadata operations were also tied to a mutable path name instead of the already-open file descriptor.

Why this is exploitable

Because the attacker can prepare the redirection before writeToFile runs.

No probabilistic race is required for the basic attack.

The sequence is straightforward:

  • operator configures a destination under an expected root
  • attacker gains enough local access to create or replace a linked component beneath that write location
  • the path string still appears to stay under the intended root
  • writeToFile opens it directly
  • the operating system follows the link or junction
  • rendered output lands at the resolved destination
  • if that destination already exists, normal create mode truncates and overwrites it

That is the whole vulnerability.


What Makes This a Security Issue, Not Just Normal Symlink Behavior

It is true that operating systems normally follow symlinks during path-based file opens.

That does not make this safe application behavior.

The security question is not:

"Did Go behave as documented?"

The real question is:

Did a helper writing sensitive rendered data verify that the resolved destination matched the operator's intended location?

In vulnerable versions, it did not.

That distinction matters because Consul Template may run:

  • as a long-lived service
  • with access to Vault-derived secrets
  • under an elevated account
  • with write access that the local attacker does not have directly

Following attacker-positioned filesystem redirection in that context creates a real privilege and trust-boundary problem.


Proof of Concept

I built a standalone reproducer around the exact writeToFile behavior at the tested commit.

The controlled setup used:

  • an intended output root
  • an external directory outside that root
  • a linked parent component beneath the intended root
  • a preexisting target file in the redirected directory
  • controlled secret content passed to writeToFile

The reproduction flow was:

Download Tool