CVE-2026-48693: FastNetMon Truncates Whatever /tmp/fastnetmon.dat Points At | Lorikeet Security Skip to main content
Back to Blog

CVE-2026-48693: FastNetMon Truncates Whatever /tmp/fastnetmon.dat Points At

Lorikeet Security Team May 23, 2026 8 min read
CVSS 7.0 -- High

Symlink Attack via Predictable /tmp Statistics File — Arbitrary File Overwrite as Root

CVE
CVE-2026-48693
CVSS
7.0 (High)
CWE
CWE-59 (Improper Link Resolution Before File Access), CWE-377 (Insecure Temporary File)
Affected
FastNetMon Community Edition <= 1.2.9
Component
src/fastnetmon.cpp line 159 (path); src/fastnetmon_logic.cpp lines 2184-2196 (write); src/fastnetmon.cpp line 1821 (umask(0))
Attack Vector
Local
Discovered by
Lorikeet Security

FastNetMon periodically dumps its current per-subnet statistics to a file so that operators and external scripts can read the current state without going through the gRPC API. The default file path is hardcoded as /tmp/fastnetmon.dat. The function print_screen_contents_into_file() opens this path with C++'s std::ofstream in std::ios::trunc mode (truncating any existing content) and writes the latest statistics.

Three independent flaws combine to make this a classical symlink attack:

  1. The path is predictable (always /tmp/fastnetmon.dat) and in a world-writable directory.
  2. The open uses std::ofstream without setting O_NOFOLLOW, so symlinks at that path are followed.
  3. The daemon calls umask(0) during daemonization, so any file the daemon creates gets the world-writable permission bits in its mode argument applied verbatim.

A bonus fourth issue compounds the impact: the chmod() call that's supposed to harden the stats file after creation always operates on the hardcoded cli_stats_file_path variable, ignoring the file_path parameter that the function actually received. So calling the function with a different path applies the wrong-file's permissions.

Disclosure status: Lorikeet Security notified FastNetMon LTD on April 25, 2026. CVE-2026-48693 was assigned by MITRE. No vendor response or fix as of May 23, 2026.


The vulnerable code

// src/fastnetmon.cpp around line 159 -- the predictable path
std::string cli_stats_file_path = "/tmp/fastnetmon.dat";

// src/fastnetmon_logic.cpp around lines 2184-2196 -- the symlink-following write
void print_screen_contents_into_file(const std::string& file_path,
                                      const std::string& data) {
    std::ofstream screen_data_file;
    screen_data_file.open(file_path.c_str(), std::ios::trunc);
    // ^^^ Symlinks at file_path are followed. No O_NOFOLLOW.

    screen_data_file << data;
    screen_data_file.close();

    chmod(cli_stats_file_path.c_str(),    // <-- BUG: ignores file_path parameter
          S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
    // ^^^ Includes S_IROTH (world-readable) despite a code comment claiming '660'.
}

// src/fastnetmon.cpp around line 1821 -- the dangerous umask
umask(0);
// ^^^ Makes any file the daemon subsequently creates world-writable
//     (modulo whatever permission bits the calling code specifies, but
//     std::ofstream defaults to 0666 which umask(0) leaves intact).

Three errors in one function. The classical pattern is well-documented — this is exactly the bug shape that produced major CVEs in at, cron, logrotate, and dozens of other Unix daemons over the past three decades.


The attack, step by step

  1. Attacker creates a symlink. A local user (any user, even unprivileged) runs ln -s /etc/cron.d/payload /tmp/fastnetmon.dat. /etc/cron.d/payload doesn't need to exist yet; the symlink is just a forwarder.
  2. Wait for FastNetMon to refresh its stats. The daemon runs print_screen_contents_into_file() periodically (every few seconds, depending on configuration). On the next refresh, std::ofstream::open("/tmp/fastnetmon.dat", std::ios::trunc) follows the symlink, creates /etc/cron.d/payload as a new file (because std::ios::trunc creates the file if it doesn't exist), and truncates it.
  3. The daemon's stats data gets written to the target file. Whatever FastNetMon was about to write — per-subnet traffic counters, ban list status, internal state — gets dumped into the target path.
  4. The file is now world-writable because of umask(0). The chmod adds S_IROTH on top.
  5. The attacker writes whatever they want. The cron file (or any other file the attacker pointed the symlink at) is now writable by them. They populate it with a malicious cron entry. cron picks it up on the next minute and runs the attacker's payload as root.

This is a clean local-privilege-escalation primitive. FastNetMon typically runs as root (because it needs CAP_NET_RAW for packet capture and the default install does not split capabilities), so the daemon's writes have root permissions. Any file under /etc, /var, /usr — anywhere the root user can write — is a potential target.

High-value targets


How a fix should look

// 1. Move the stats file to a daemon-private directory.
constexpr const char* STATS_DIR = "/var/lib/fastnetmon";
// systemd unit creates this with mode 0750, owner fastnetmon:fastnetmon.

// 2. Open with O_NOFOLLOW | O_CLOEXEC and explicit mode.
int fd = open((std::string(STATS_DIR) + "/stats.dat").c_str(),
              O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC,
              0640);
if (fd < 0) {
    // If errno == ELOOP, a symlink was at the path: bail and log a security warning.
    log_error("stats file open failed (possibly symlink): %s", strerror(errno));
    return;
}

// 3. Set a sane umask in daemonize(), not 0.
umask(0027);  // owner full, group read, world nothing.

// 4. Write through the fd, close it, no chmod games.
write(fd, data.data(), data.size());
close(fd);

Four changes: dedicated private directory, explicit O_NOFOLLOW, sane umask, no post-creation chmod (the open's mode argument is the source of truth). Each change closes one of the four failure modes; all four together produce a write that cannot be redirected by a symlink, cannot create world-writable files, and cannot apply the wrong permissions.

The chmod bug (always using cli_stats_file_path instead of the file_path parameter) is a separate fix — even after the other improvements, the function should use its parameter, not a hardcoded module-level variable.


Compensating controls


The pattern: /tmp is hostile shared state

Symlink attacks on predictable /tmp paths are one of the oldest local-privilege-escalation patterns in Unix. They have been documented since at least 1989 (the "race tmpfile" family of CERT advisories). Every few years a new daemon ships with the same shape: predictable path, truncating open, missing O_NOFOLLOW, umask too permissive. Each of those is a separately-correctable problem, but the combination produces a clean LPE primitive.

The structural fix for new code is to never write to /tmp from a privileged process. Use a private directory created with the daemon's expected ownership and mode, ideally one that's bind-mounted into the daemon's namespace if you're running under modern systemd. Reserve /tmp for user-shell scratch space and unprivileged programs.

For existing code that already writes to /tmp, the minimum fix is the O_NOFOLLOW flag. It's one constant added to the open call, and it shuts down the symlink-redirection vector entirely. std::ofstream doesn't directly support O_NOFOLLOW, but you can call open() with the flag and then wrap the resulting fd with fdopen() + __gnu_cxx::stdio_filebuf, or just use write() directly.


Disclosure timeline

DateEvent
2026-04-25Vulnerability identified during Lorikeet Security source code audit of FastNetMon Community Edition 1.2.9
2026-04-25CVE ID requested from MITRE
2026-04-25Vendor (Pavel Odintsov / FastNetMon LTD) notified at the contact published in SECURITY.md
2026-05-22CVE-2026-48693 assigned by MITRE
TBDVendor response
TBDFix release
2026-05-23Lorikeet Security publishes responsible disclosure report

Full Responsible Disclosure Report (PDF)

Complete writeup of all 16 FastNetMon Community Edition vulnerabilities Lorikeet Security identified, including vulnerable-code excerpts, impact analysis, and remediation guidance for each CVE.

Download PDF

Auditing the network infrastructure your business depends on

Lorikeet Security finds the parser bugs, protocol confusion, and unauthenticated-control-plane issues that move your DDoS detector, your BGP speaker, and your edge devices from "running" to "exploitable." Source code review, fuzz harness development, and adversarial protocol testing.

-- views
Link copied!
Lorikeet Security

Lorikeet Security Team

Penetration Testing & Cybersecurity Consulting

Lorikeet Security helps modern engineering teams ship safer software. Our work spans web applications, APIs, cloud infrastructure, and AI-generated codebases — and everything we publish here comes from patterns we see in real client engagements.

Lory waving

Hi, I'm Lory! Need help finding the right service? Click to chat!