# PYTHON-LANG-SEC-013: Shell Command with Wildcard Character

> **Severity:** MEDIUM | **CWE:** CWE-78 | **OWASP:** A03:2021

- **Language:** Python
- **Category:** Python Core
- **URL:** https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-013
- **Detection:** `pathfinder scan --ruleset python/PYTHON-LANG-SEC-013 --project .`

## Description

Shell commands containing wildcard characters such as * (glob), ? (single character), and
[ ] (character class) are expanded by the shell before the command executes. When wildcards
are used in commands executed via os.system() or similar shell-invoking functions, an attacker
who can create files in the target directory can exploit wildcard expansion to inject additional
command-line flags or arguments.

The classic wildcard injection technique creates files with names like "-rf ." or
"--checkpoint-action=exec=malicious.sh" in a directory that is processed with wildcards.
When tar, rsync, chown, chmod, or similar commands run with a wildcard argument, the shell
expands the wildcard to include the attacker-created filenames as additional command-line
arguments, effectively injecting arbitrary flags into the command.

Use Python's glob module or pathlib for file pattern matching, and pass argument lists to
subprocess.run() to avoid wildcard expansion entirely.


## Vulnerable Code

```python
import os

user_input = input("Enter command: ")
os.system(user_input)
```

## Secure Code

```python
import glob
import subprocess
import pathlib

# SECURE: Use Python's glob module for file pattern matching
def compress_logs(log_dir: str) -> None:
    log_files = glob.glob(f"{log_dir}/*.log")
    for log_file in log_files:
        path = pathlib.Path(log_file)
        if path.is_file() and path.suffix == ".log":
            subprocess.run(
                ["/bin/gzip", str(path)],
                check=True,
                timeout=30,
            )

# SECURE: Use pathlib.glob() for directory traversal
def list_python_files(directory: str) -> list:
    return [str(p) for p in pathlib.Path(directory).glob("*.py") if p.is_file()]

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, flows, QueryType
from codepathfinder.presets import PropagationPresets

class OSModule(QueryType):
    fqns = ["os"]


@python_rule(
    id="PYTHON-LANG-SEC-013",
    name="System Call with Wildcard",
    severity="MEDIUM",
    category="lang",
    cwe="CWE-78",
    tags="python,wildcard,command-injection,CWE-78",
    message="os.system() with wildcard (*) detected. Wildcards may lead to unintended file inclusion.",
    owasp="A03:2021",
)
def detect_system_wildcard():
    """Detects os.system() calls (audit for wildcard usage)."""
    return OSModule.method("system")
```

## How to Fix

- Replace shell commands with wildcards using Python's glob module or pathlib.Path.glob() for file pattern matching, combined with subprocess.run() using a list of arguments.
- When using tar, rsync, or similar tools that must process multiple files, explicitly enumerate the files in Python and pass them as individual arguments rather than using shell wildcards.
- Validate that files in directories processed with wildcards cannot be created by untrusted users; ensure proper directory permissions.
- Use subprocess.run() with shell=False (the default) and a list of arguments to prevent shell wildcard expansion entirely.
- For chown/chmod operations on multiple files, use Python's os.chown() and os.chmod() functions directly to avoid shell wildcard risks.

## Security Implications

- **Argument Injection via Malicious Filenames:** An attacker who can create files in a directory processed with wildcards can create
filenames that look like command-line flags (e.g., --checkpoint=1 for tar). The shell
expands the wildcard to include these filenames as arguments, injecting attacker-
controlled flags into the command execution.

- **File Inclusion Beyond Intended Scope:** Wildcards in copy, tar, or rsync commands may match more files than intended if
an attacker creates files in the target directory. This can cause sensitive files
to be included in archives, transmitted over the network, or processed by insecure
handlers.

- **Chown/Chmod Privilege Escalation:** Running chown or chmod with wildcards is particularly dangerous. An attacker who
creates a symlink named "file*" in the directory can cause chown to traverse
the symlink and change ownership of arbitrary system files, leading to privilege
escalation.

- **Data Destruction:** Wildcard expansion in rm or find commands can include unintended files and directories
in deletion operations. An attacker creating strategically named files can trigger
deletion of configuration files, logs, or application data.


## FAQ

**Q: Is wildcard injection only possible when attackers can create files?**

Yes, exploiting wildcard injection requires that an attacker can create files with
strategically crafted names in the directory being processed. Directories with proper
permissions that prevent attacker file creation are not exploitable via this technique.
However, shared directories, upload directories, and world-writable temp directories
are common targets.


**Q: Which tools are most commonly exploited via wildcard injection?**

tar (--checkpoint-action), rsync (-e option), chown (symlink traversal), chmod, find
(-exec), and cp are the most commonly exploited commands. These tools accept flag-like
arguments that can be injected via malicious filenames and have been used in real-world
privilege escalation attacks on Linux systems.


**Q: Does using subprocess.run() with shell=True still have this problem?**

Yes. subprocess.run() with shell=True passes the command to /bin/sh, which performs
wildcard expansion. The safe solution is subprocess.run() with shell=False (the default)
and an explicit list of file paths obtained via Python's glob module, not a shell wildcard.


**Q: What if I genuinely need to process all files matching a pattern?**

Use Python's glob.glob() or pathlib.Path.glob() to expand the pattern in Python code,
then pass each resulting path individually to subprocess.run() as a list argument. This
performs the same file matching without invoking the shell and without vulnerability to
filename-based argument injection.


**Q: Are Windows wildcards affected by this vulnerability?**

Windows cmd.exe also performs wildcard expansion, though the specific exploitation
techniques differ. On Windows, subprocess.run() with shell=False on Python 3 uses
CreateProcess directly without shell expansion, which avoids this risk. Use list
arguments and avoid shell=True on all platforms.


**Q: How do I audit my codebase for wildcard injection risks beyond this rule?**

Look for all uses of os.system(), subprocess.run(shell=True), subprocess.Popen(shell=True),
and os.popen() where the command string contains wildcards or processes directories that
users can write to. Also audit cron jobs, backup scripts, and deployment scripts for
the same pattern.


## References

- [CWE-78: OS Command Injection](https://cwe.mitre.org/data/definitions/78.html)
- [Wildcard Injection in Unix Shell Commands](https://www.exploit-db.com/papers/33930)
- [Python docs: glob module](https://docs.python.org/3/library/glob.html)
- [Python docs: pathlib module](https://docs.python.org/3/library/pathlib.html)
- [OWASP OS Command Injection Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html)

---

Source: https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-013
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
