You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d866152
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: src/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-vim-applications-injection.md
+61-10Lines changed: 61 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,11 +17,13 @@ echo "hi" > /tmp/victim.txt
17
17
printf':qa!\n'| VIMINIT='silent! !touch /tmp/vim-executed' vim /tmp/victim.txt
18
18
ls -la /tmp/vim-executed
19
19
20
-
# Pure Vimscript (no external process, e.g. write a file)
21
-
printf':qa!\n'|VIMINIT='call writefile(["x"],"/tmp/vim-vimscript")' vim /tmp/victim.txt
20
+
# Pure Vimscript: write the marker and exit without reading stdin
21
+
VIMINIT='call writefile(["x"],"/tmp/vim-vimscript")|qall!' vim /tmp/victim.txt
22
22
```
23
23
24
-
The `:qa!` fed on stdin just closes the editor after the payload has already run; in a real scenario the victim simply opens Vim normally.
24
+
The `:qa!` fed on stdin in the first example only closes the editor after the payload has run; in a real scenario the victim can open Vim normally.
25
+
26
+
`VIMINIT` is parsed as **one Ex command line**. Separate a chain with `|` (or a literal newline). It has precedence over the user's vimrc and `EXINIT`, so a payload does not need a malicious configuration file and runs before the normal user configuration.<sup>[[1]](#references)[[2]](#references)</sup>
This primitive depends on a **normal startup**. `vim -u NONE` / `nvim -u NONE` skip the environment/user initialization (and plugins), while `-u <file>` uses that file instead. Vim `-es`/`-Es` and Neovim `-es`, `-Es`, or `-l` also skip these initialization steps. Do not mistake `--headless` for a safe mode: a normal Neovim headless startup still processes `VIMINIT`.<sup>[[1]](#references)[[2]](#references)</sup>
40
+
41
+
Consequently, validate the complete launch chain: the variable must survive the wrapper, `sudo` policy, job runner, and editor selection, and the final command must not force `-u NONE`/`NORC` or batch mode. A reliable payload can terminate itself with `|qall!`, which also makes testing wrappers that do not provide a TTY easier.<sup>[[1]](#references)[[2]](#references)</sup>
42
+
43
+
## Neovim current-directory Lua module hijacking
44
+
45
+
A separate Neovim injection primitive affects builds whose Lua `package.path`/`package.cpath` still contain current-directory templates such as `./?.lua` or `./?.so`. Starting Neovim alone is insufficient: a config or plugin must call `require("name")`, and no earlier loader may resolve that name. A common trigger is an **optional dependency check** such as `pcall(require, "optional_dep")`; placing `optional_dep.lua` in an attacker-controlled working directory then executes it without enabling the separate `'exrc'` local-configuration feature. Core `vim.*` modules and modules already found on `'runtimepath'` are not generally shadowable, so enumerate actual missing/optional `require()` calls rather than guessing names.<sup>[[3]](#references)</sup>
46
+
47
+
The following reproduces the loader primitive with a harmless marker:<sup>[[3]](#references)</sup>
Upstream tracks removal of the current-directory fallback during normal editor startup while retaining Lua-script (`nvim -l`) behavior. Until the installed build no longer exposes it, place this at the **start** of `init.lua` (it intentionally removes relative current-directory Lua/C module templates, so do not apply it to workflows that require them):<sup>[[3]](#references)</sup>
-**Neovim** honours `VIMINIT` too (it is checked before the user `init.vim`/`init.lua`).
38
-
- Batch/Ex mode (`vim -es` / `vim -Es`) does **not** source `VIMINIT`/`EXINIT`; the variables run in a normal (interactive) startup, which is the common victim scenario.
39
-
- Related file-based vectors are the per-directory `exrc`/`.nvimrc` "modeline"/local-rc features and `-u <vimrc>`; the environment-variable path above needs no writable file at all.
83
+
-**Neovim** honours both `VIMINIT` and the `EXINIT` fallback, but its normal user configuration is `init.vim` or `init.lua`.<sup>[[2]](#references)</sup>
84
+
- The environment-variable path needs no writable file. Local rc and current-directory module hijacking are separate, file-backed primitives.<sup>[[1]](#references)[[3]](#references)</sup>
85
+
- Project-local configuration is a different surface from modelines. With Vim's `'exrc'` enabled, a local vimrc/exrc owned by another user runs with `'secure'` restrictions; however, extracting an archive normally makes the planted file owned by the victim and defeats that ownership-based protection. Neovim also searches for `.nvim.lua`, `.nvimrc`, or `.exrc` when `'exrc'` is enabled—do not conflate that opt-in mechanism with the `require()` current-directory fallback above.<sup>[[1]](#references)[[2]](#references)[[3]](#references)</sup>
86
+
- Editor-selection variables only choose what program is launched; they do not guarantee that `VIMINIT` reaches the final process. Inspect the exact environment and arguments at the Vim/Neovim exec boundary.<sup>[[1]](#references)[[2]](#references)</sup>
40
87
41
88
## Hardening
42
89
43
-
- Sanitize the environment (drop `VIMINIT`/`EXINIT`) before launching editors from privileged or automated contexts, and prefer `sudo -i`/`env -i` wrappers that reset the environment.
44
-
- Set `EDITOR`/`VISUAL` to trusted absolute paths and avoid running editors as root with an inherited user environment.
45
-
- Treat control over a target's environment as equivalent to code execution for any Vim/Neovim it spawns.
90
+
- Explicitly drop the variables before privileged or automated editor launches: `env -u VIMINIT -u EXINIT /usr/bin/vim -u NONE -- "$file"`. `-u NONE` is important when the caller must ignore every user startup source.<sup>[[1]](#references)[[2]](#references)</sup>
91
+
- Set `EDITOR`/`VISUAL` to trusted absolute paths, avoid running interactive editors as root with an inherited user environment, and ensure wrappers cannot restore `VIMINIT`/`EXINIT` after sanitization.<sup>[[1]](#references)[[2]](#references)</sup>
92
+
- For Neovim, update to a build that removes current-directory Lua/C search templates during editor mode, or strip them before loading plugins. Audit plugin code for optional `pcall(require, ...)` calls when opening untrusted repositories.<sup>[[3]](#references)</sup>
93
+
- Treat control over a target's editor environment, working directory, or startup configuration as a potential code-execution primitive in the editor's security context.<sup>[[1]](#references)[[2]](#references)[[3]](#references)</sup>
0 commit comments