· Updated

Zero Just Prevented a Windows Taskkill Hijack — And You Didn't Even Know It Was Possible

Gitlawb Zero#gitlawb-zero#windows#security#taskkill#hijack#binary-hijacking#path-injection#guide

There’s a class of security vulnerability that’s particularly insidious: binary hijacking on Windows. If a tool calls taskkill without an absolute path, an attacker can place a malicious taskkill.exe in a directory that’s searched before the system directory. The tool runs the attacker’s binary instead — with whatever privileges the tool has.

Gitlawb Zero just fixed this. Here’s why it matters, how the fix works, and what every Windows developer should know about PATH-based execution risks.

The Bug: PATH Search Order Is an Attack Surface

Zero’s process management code on Windows called taskkill without specifying the full path. On Windows, the PATH search order means the first matching binary in any PATH directory wins. The search order is:

  1. The directory containing the application executable
  2. The current working directory
  3. System directories (C:\Windows\System32, C:\Windows)
  4. Directories in the user and system PATH environment variables

If an attacker placed a malicious taskkill.exe in a directory that’s earlier in PATH than C:\Windows\System32 — say, a project’s node_modules/.bin, a user’s ~/bin, or even the current working directory — Zero would execute the attacker’s code instead of the real Windows utility.

The Fix: Absolute Path Resolution

fix(windows): resolve absolute path for taskkill to prevent hijacking (#617)

The fix resolves taskkill to its absolute path (C:\Windows\System32\taskkill.exe) before executing it. This eliminates the PATH search entirely — the correct binary is always used, regardless of what’s in PATH or the current directory.

// Before: vulnerable to PATH hijacking
cmd := exec.Command("taskkill", "/PID", pidStr, "/F")

// After: absolute path, no PATH search
taskkillPath, _ := exec.LookPath("taskkill")  // resolves to C:\Windows\System32\taskkill.exe
cmd := exec.Command(taskkillPath, "/PID", pidStr, "/F")

The fix uses exec.LookPath which searches PATH but returns the absolute path to the first match. Since C:\Windows\System32 is always in the system PATH and comes before user directories, the legitimate binary wins — but crucially, the returned absolute path bypasses any subsequent PATH manipulation.

Why This Matters: Binary Hijacking Is Real

Binary hijacking is a well-known Windows vulnerability, but it’s surprisingly common in open-source tools. Most developers test on macOS or Linux, where the PATH search is less exploitable (the current directory isn’t in PATH by default, and system binaries are in protected locations). On Windows, it’s a real attack vector:

Scenario Risk
Cloned repo with malicious taskkill.exe in repo root Developer runs Zero in repo → attacker code executes
Compromised node_modules/.bin from supply chain attack Any tool run in project directory is affected
User writes custom scripts to ~/bin (in PATH) Typosquatting or malicious scripts intercept system calls
Shared development machines One user’s malicious binary affects others

Zero’s fix is the right approach:

  • Always use absolute paths for system utilities
  • Don’t rely on PATH for security-critical operations
  • Validate binary locations before execution

Beyond taskkill: The General Pattern

This isn’t just about taskkill. Any Windows tool that shells out to system utilities without absolute paths has the same vulnerability. Common targets:

  • powershell.exe, cmd.exe — command execution
  • net.exe, sc.exe — service management
  • reg.exe — registry manipulation
  • wmic.exe — WMI queries
  • findstr.exe, where.exe — search utilities
  • Git, npm, python, node — if not using absolute paths

The secure pattern: Resolve the absolute path once at startup (or use a known system path), cache it, and always execute the cached absolute path.

// Secure pattern for any system binary
var (
    taskkillPath string
    powershellPath string
)

func init() {
    var err error
    taskkillPath, err = exec.LookPath("taskkill")
    if err != nil { log.Fatal("taskkill not found") }
    powershellPath, err = exec.LookPath("powershell.exe")
    if err != nil { log.Fatal("powershell not found") }
}

// Use cached absolute paths everywhere
func killProcess(pid int) error {
    return exec.Command(taskkillPath, "/PID", strconv.Itoa(pid), "/F").Run()
}

What This Means for Zero Users

Zero on Windows is now more secure against a class of attacks that most users never think about — but that attackers definitely do. It’s the kind of security hardening that separates a tool you trust from a tool you hope works.

For developers building CLI tools on Windows: audit your exec.Command calls. Every unqualified system binary name is a potential hijack vector. The fix is trivial (resolve absolute path once), the risk is real, and the users who get protected will never know — which is exactly how good security should work.



Tired of deciding which AI subscription to keep? aiFiesta bundles GPT, Claude, Gemini, Grok, DeepSeek, Perplexity and more for $12/mo — less than half of a single premium chat sub.

FREE RESOURCE

Get the AI Agent Cheat Sheet

All 19 coding agents in one comparison table — pricing, features, benchmarks. Updated weekly. Delivered to your inbox.

k
kira_bug_hunter
Security & Bug Hunter
Former pen tester. Finds the bugs nobody wants to exist. Skeptical of everything, especially status indicators.

Related articles