PHP eval() Function Usage

Critical Risk Code Injection
phpevalcode-injectionremote-code-executiondynamic-execution

What it is

The PHP application uses the dangerous eval() function to execute code dynamically, which can lead to remote code execution if user input is processed. The eval() function treats strings as PHP code and executes them, making it extremely dangerous when handling untrusted input.

// Vulnerable: Using eval() with user input function processUserExpression($expression) { // Extremely dangerous: Direct eval() of user input $result = eval("return $expression;"); return $result; } // Example usage that allows code injection $userInput = $_GET['calc']; // e.g., "1+1; system('rm -rf /')" $result = processUserExpression($userInput); // Another dangerous pattern function loadConfig($configString) { // Dangerous: eval() for configuration eval($configString); } $config = file_get_contents('user_config.php'); loadConfig($config);
// Secure: Safe alternatives to eval() // Alternative 1: Predefined operations function processUserExpression($expression) { // Parse and validate mathematical expressions safely if (!preg_match('/^[0-9+\-*/()\s]+$/', $expression)) { throw new InvalidArgumentException('Invalid expression format'); } // Use a safe math expression evaluator or parser // This is a simple example - use a proper library in production $allowedOperators = ['+', '-', '*', '/']; $tokens = preg_split('/([+\-*/])/', $expression, -1, PREG_SPLIT_DELIM_CAPTURE); // Validate all tokens foreach ($tokens as $token) { $token = trim($token); if (!is_numeric($token) && !in_array($token, $allowedOperators)) { throw new InvalidArgumentException('Invalid token in expression'); } } // Safe evaluation using create_function alternative // In practice, use a proper expression parser library return "Expression validated - use proper math library"; } // Alternative 2: Configuration with safe parsing function loadConfig($configFile) { if (!file_exists($configFile)) { throw new Exception('Config file not found'); } // Use JSON or YAML instead of PHP eval $configData = file_get_contents($configFile); try { $config = json_decode($configData, true, 10, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw new Exception('Invalid JSON configuration'); } // Validate configuration structure $requiredKeys = ['database', 'api_key', 'debug']; foreach ($requiredKeys as $key) { if (!isset($config[$key])) { throw new Exception("Missing required config key: $key"); } } return $config; } // Alternative 3: Predefined function dispatch function executeUserAction($action, $params) { $allowedActions = [ 'calculate' => 'doCalculation', 'format' => 'doFormatting', 'validate' => 'doValidation' ]; if (!isset($allowedActions[$action])) { throw new InvalidArgumentException('Action not allowed'); } $functionName = $allowedActions[$action]; if (!function_exists($functionName)) { throw new Exception('Function not available'); } return call_user_func($functionName, $params); } try { $action = filter_input(INPUT_GET, 'action', FILTER_SANITIZE_STRING); $params = filter_input(INPUT_GET, 'params', FILTER_SANITIZE_STRING); $result = executeUserAction($action, $params); } catch (Exception $e) { error_log('Action execution error: ' . $e->getMessage()); // Handle error appropriately }

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

PHP applications call eval() to execute user-submitted code strings, mathematical expressions, or configuration data treating them as executable PHP code. Common patterns include calculator functionality: eval("return {$_GET['expression']};"); where users submit expressions like 1+1, but attackers inject system('cat /etc/passwd'); or phpinfo(); to execute arbitrary commands. Custom formula evaluators in reporting tools, pricing calculators, or spreadsheet-like applications use eval() to process user-defined formulas: eval('$result = ' . $userFormula . ';');, enabling remote code execution when users control $userFormula. Plugin or module systems that allow users to submit PHP code snippets for customization: eval($userPlugin); intended to execute user extensions but allowing complete system compromise. Content management systems processing shortcodes or template variables through eval(): eval('echo ' . $shortcode . ';'); to dynamically generate content. Admin interfaces for "code execution" or "PHP sandbox" features intended for debugging: eval($_POST['php_code']); which attackers access through privilege escalation or credential theft. The vulnerability is critical because eval() executes PHP code with the full privileges of the web application—file system access, database credentials, session manipulation, and server commands—with no sandboxing or isolation. Attackers gain immediate remote code execution allowing data exfiltration, malware installation, lateral movement to internal networks, or complete server compromise.

Root causes

Using eval() to Process User-Provided Data or Configuration

PHP applications call eval() to execute user-submitted code strings, mathematical expressions, or configuration data treating them as executable PHP code. Common patterns include calculator functionality: eval("return {$_GET['expression']};"); where users submit expressions like 1+1, but attackers inject system('cat /etc/passwd'); or phpinfo(); to execute arbitrary commands. Custom formula evaluators in reporting tools, pricing calculators, or spreadsheet-like applications use eval() to process user-defined formulas: eval('$result = ' . $userFormula . ';');, enabling remote code execution when users control $userFormula. Plugin or module systems that allow users to submit PHP code snippets for customization: eval($userPlugin); intended to execute user extensions but allowing complete system compromise. Content management systems processing shortcodes or template variables through eval(): eval('echo ' . $shortcode . ';'); to dynamically generate content. Admin interfaces for "code execution" or "PHP sandbox" features intended for debugging: eval($_POST['php_code']); which attackers access through privilege escalation or credential theft. The vulnerability is critical because eval() executes PHP code with the full privileges of the web application—file system access, database credentials, session manipulation, and server commands—with no sandboxing or isolation. Attackers gain immediate remote code execution allowing data exfiltration, malware installation, lateral movement to internal networks, or complete server compromise.

Dynamic Code Generation with Insufficient Input Validation

Developers use eval() to dynamically construct and execute PHP code based on runtime conditions, user selections, or database values, implementing input validation that fails to prevent code injection. Applications build PHP variable assignments dynamically: eval('\$config_' . $key . ' = ' . $value . ';'); where $key and $value come from user input or databases containing attacker-controlled content. Dynamic function calls using eval(): eval($functionName . '(' . $parameters . ')'); attempting to invoke different functions based on user choices but failing to restrict which functions can be called (allowing exec(), shell_exec(), system()). Code generation for performance optimization where developers use eval() to create specialized functions at runtime: eval('function generated_' . $id . '() { /* generated code */ }'); incorporating user data into function bodies. Variable variable patterns using eval() instead of proper variable variables syntax: eval('$variable = $$' . $dynamicName . ';'); Blacklist-based validation attempts to block dangerous patterns: if (!preg_match('/exec|system|shell/', $input)) { eval($input); } which attackers bypass using string concatenation (sy.'stem'), backticks (`whoami`), character encoding, or alternative dangerous functions (passthru(), proc_open()). The fundamental problem is that comprehensive input validation for eval() is impossible—PHP's syntax is too complex and flexible to whitelist safely, and attackers continuously discover new bypass techniques making blacklists ineffective. Even seemingly safe inputs become dangerous in eval() context: the string '); system('whoami'); // appears harmless outside eval() but executes commands when evaluated within certain code structures.

Processing Template Strings or Expressions with eval()

Applications implement template rendering or string interpolation systems using eval() to process templates containing embedded PHP code or variable substitutions. Email template systems store templates in databases with PHP variable placeholders: $template = 'Hello <?php echo $userName; ?>, your balance is <?php echo $balance; ?>'; eval('?>' . $template); allowing users who control template content to inject arbitrary PHP code. View rendering without proper template engines: eval('?>' . file_get_contents($templateFile)); to process template files containing mixed HTML and PHP, but when users can upload or modify template files (through CMS interfaces, file upload vulnerabilities, or compromised accounts), they inject malicious PHP code that executes during rendering. Widget or block rendering in CMS systems: eval($widgetCode); where widget definitions stored in databases can be modified by administrators, but privilege escalation or SQL injection allows attackers to modify widget code for code execution. Report generators processing user-defined output formats: eval('echo "' . $userFormat . '";'); where $userFormat contains formatting strings like Customer: $name - Total: $total but attackers inject "; system('whoami'); //. Dynamic localization systems using eval() to process language strings containing embedded PHP: eval('return "' . $translationString . '";'); which becomes vulnerable when translation management interfaces allow users to modify language strings. The pattern emerges from developers seeking convenience of in-line PHP execution for templates without implementing proper template engines (Twig, Blade, Smarty) that provide similar functionality with automatic escaping and sandboxing. Legacy codebases frequently contain these patterns from eras when eval()-based templating was common practice before security awareness improved.

Deserializing Untrusted Data that Contains PHP Code

Applications combine unserialize() with eval() or use serialized data that gets evaluated as code, creating chained vulnerabilities where object injection leads to code execution. PHP's unserialize() on untrusted data can instantiate arbitrary classes whose __wakeup(), __destruct(), or other magic methods contain eval() calls: class Template { private $code; public function __wakeup() { eval($this->code); } } combined with unserialize($_COOKIE['data']) allows attackers to craft serialized objects that execute arbitrary code on deserialization. Configuration systems serializing PHP arrays containing code: $config = unserialize($data); eval($config['init_code']); where attackers control serialized configuration leading to code execution. Session handling with custom session serializers that eval() session data: session_decode($attackedSessionData); followed by eval($_SESSION['callback']); creates code execution when attackers manipulate session data. Cache systems storing serialized closures or anonymous functions: $func = unserialize($cacheData); $func(); where serialized closures are reconstructed and executed, but attackers inject malicious serialized closures. Database-stored PHP code: applications storing PHP code snippets in databases for plugins, hooks, or extensions: eval($row['plugin_code']); becomes vulnerable when SQL injection or privilege escalation allows modifying stored code. Object-Relational Mappers (ORMs) with attribute serialization that store serialized objects in database columns, then deserialize and execute them: $user->settings = unserialize($row['settings']); where settings objects contain eval() in magic methods. The Phar deserialization vulnerability (CVE-2018-19296 and others) compounds this issue where phar:// stream wrappers trigger unserialization of attacker-controlled data through file operations, creating code execution paths through eval() in gadget chains.

Using eval() for Configuration File Processing

Legacy applications and frameworks process configuration files written in PHP syntax using include, require, or eval(), treating configuration as executable code rather than declarative data. Configuration files written as PHP arrays: return ['database' => 'localhost', 'key' => 'secret']; loaded via include/require, but extended with eval() for dynamic configuration: eval(file_get_contents($configFile)); allowing users who control configuration file contents (through file upload, path traversal, or compromised deployment pipelines) to execute arbitrary PHP code. .env file processors that parse PHP syntax: foreach($lines as $line) { eval($line); } attempting to process KEY=value files but accepting PHP code. INI file parsers with eval() for value processing: eval('$config[$key] = ' . $value . ';'); where INI values get executed as code. Configuration override systems allowing users to provide custom configuration: eval($userConfigOverride); intended to merge user preferences but executing arbitrary code. Installation wizards or setup scripts accepting configuration parameters through POST/GET and using eval() to apply them: eval('define("DB_HOST", "' . $_POST['db_host'] . '");'); allowing code injection through installation parameters. Framework configuration caches that serialize configuration with embedded closures, then unserialize and eval(): $config = eval(file_get_contents('config.cache.php')); Feature flags or environment-based configuration using eval() to determine active features: eval('if (' . $condition . ') { $enabled = true; }'); where $condition comes from external sources. The root cause is treating configuration as code rather than data—modern best practices use JSON, YAML, XML, or .env files parsed as data structures without code execution capabilities, but legacy systems and some frameworks still eval() configuration creating severe security risks when configuration sources become attacker-controllable.

Fixes

1

Avoid Using eval() Entirely - Consider Alternative Approaches

Eliminate all eval() usage from PHP codebases by refactoring to use safer alternatives specific to each use case—there are no safe uses of eval() with untrusted input, making complete removal the only secure solution. Audit codebase for all eval() occurrences: grep -r 'eval(' --include='*.php' to identify usage locations. For each eval() usage, determine the underlying requirement: dynamic code execution (use function dispatch), mathematical expression evaluation (use expression parser libraries), template rendering (use template engines), configuration processing (use JSON/YAML parsers), or variable assignments (use array access). Create issue tickets for each eval() instance documenting purpose, risk level, and remediation approach. Prioritize removal based on data flow analysis—eval() processing user input requires immediate remediation, while eval() on hardcoded strings or admin-only features has lower priority but should still be removed. Implement static analysis tools that fail CI/CD builds when eval() is detected: use PHPStan, Psalm, or custom pre-commit hooks: grep -q 'eval(' changed_files && exit 1. For legacy codebases where complete removal is infeasible short-term, implement defense-in-depth: restrict eval() usage to specific isolated modules, add extensive logging around eval() calls: error_log('EVAL_CALL: ' . $input) for monitoring, implement strict access controls ensuring only trusted admins can reach eval() code paths, and schedule technical debt removal. Document eval() alternatives in coding standards prohibiting new eval() introduction. Review third-party dependencies for eval() usage: search vendor/ directories and consider alternative libraries without eval() dependencies.

2

Use Safe Alternatives like json_decode() for Data Processing

Replace eval()-based data processing with structured data parsers that treat input as data rather than code: json_decode(), yaml_parse(), parse_ini_file(), or SimpleXML. For configuration processing, migrate from eval(file_get_contents('config.php')) to JSON configuration: $config = json_decode(file_get_contents('config.json'), true, 10, JSON_THROW_ON_ERROR); with validation: if (!isset($config['database'], $config['api_key'])) { throw new ConfigException('Invalid config'); }. Use YAML for complex configuration with Symfony YAML component: use Symfony\Component\Yaml\Yaml; $config = Yaml::parseFile('config.yaml'); providing nested configuration without code execution risks. For .env files, use vlucas/phpdotenv library: $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->load(); which safely parses KEY=value format. Replace serialized PHP arrays with JSON: change serialize($data) to json_encode($data, JSON_THROW_ON_ERROR) and unserialize($data) to json_decode($data, true, 10, JSON_THROW_ON_ERROR) preventing object injection and eval() gadget chains. For data exchange between systems, prefer JSON APIs over serialized PHP objects. Validate parsed data structure using JSON Schema validation libraries (justinrainbow/json-schema) or create validation functions: function validateConfig(array $config): void { if (!is_string($config['database']) || !filter_var($config['api_key'], FILTER_VALIDATE_URL)) { throw new InvalidArgumentException(); } }. Implement strong typing for configuration objects using PHP 7.4+ typed properties: class Config { public string $database; public string $apiKey; public function __construct(array $data) { $this->database = $data['database']; $this->apiKey = $data['api_key']; } } ensuring configuration conforms to expected structure without eval() execution.

3

Implement Proper Template Engines Instead of eval() for Dynamic Content

Replace eval()-based template rendering with secure template engines that provide sandboxed execution and automatic escaping: Twig, Blade, Smarty, or Latte. Twig templates provide Django-style syntax with automatic XSS protection: $loader = new \Twig\Loader\FilesystemLoader('/path/to/templates'); $twig = new \Twig\Environment($loader, ['autoescape' => 'html']); echo $twig->render('template.html.twig', ['user' => $userName]); Templates use {{ user }} for escaped output and {% if %} for logic without PHP code execution. Laravel Blade templates use @{{ variable }} syntax: return view('email.template', ['user' => $user]); with automatic escaping and @directive support for control structures. Smarty templates: $smarty = new Smarty(); $smarty->assign('user', $userName); $smarty->display('template.tpl'); use {$user} placeholders with configurable security policies. For simple variable substitution without full template engine, use sprintf() or string replacement: $template = 'Hello %s, your balance is %s'; $output = sprintf($template, htmlspecialchars($userName), $balance); Template engines provide: automatic HTML escaping preventing XSS, sandboxed execution preventing code injection, template inheritance for code reuse, filters and functions for data manipulation (date formatting, number formatting), and clear separation between template logic and application code. Migrate legacy eval() templates incrementally: identify template files, convert PHP code to template syntax, test rendering output matches original, deploy and monitor. For email templates, use packages like symfony/mime with Twig: $email = (new TemplatedEmail())->htmlTemplate('email.html.twig')->context(['user' => $user]); Document template migration in technical debt backlog with deadlines for eval() removal.

4

Use Predefined Function Arrays or Switch Statements for Dynamic Execution

Replace eval() used for dynamic function dispatch with explicit allowlists of permitted functions using arrays or switch statements that prevent arbitrary code execution. For dynamic function calling based on user input, create function dispatch table: $allowedFunctions = ['calculateTotal' => 'calculateTotal', 'formatCurrency' => 'formatCurrency', 'validateEmail' => 'validateEmail']; $action = $_GET['action']; if (!isset($allowedFunctions[$action])) { throw new InvalidArgumentException('Invalid action'); } if (!function_exists($allowedFunctions[$action])) { throw new RuntimeException('Function not available'); } $result = call_user_func($allowedFunctions[$action], $parameters); This approach whitelists specific functions preventing execution of dangerous functions (system(), exec(), file_put_contents()). For class method dispatch: $allowedMethods = ['save', 'update', 'delete']; if (!in_array($method, $allowedMethods, true)) { throw new InvalidArgumentException(); } if (!method_exists($object, $method)) { throw new BadMethodCallException(); } call_user_func([$object, $method], $args); Use switch statements for simple dispatch: switch ($action) { case 'create': return $service->create($data); case 'update': return $service->update($data); default: throw new InvalidArgumentException('Unknown action'); }. For plugin/extension systems, use explicit plugin registration: $plugins = []; public function registerPlugin(string $name, callable $handler): void { $this->plugins[$name] = $handler; } public function executePlugin(string $name, array $args) { if (!isset($this->plugins[$name])) { throw new PluginNotFoundException(); } return ($this->plugins[$name])(...$args); } ensuring only registered plugins execute. Implement Command pattern for complex action dispatch encapsulating each action in a class: interface Command { public function execute(array $params): mixed; } $commands = ['create' => new CreateCommand(), 'update' => new UpdateCommand()]; $commands[$action]->execute($params); This provides type safety, testability, and prevents code injection through explicit command registration.

5

Validate and Sanitize All Input Before Any Dynamic Processing

For unavoidable dynamic processing scenarios, implement multi-layered input validation that strictly limits input to safe patterns, though this provides incomplete protection and elimination remains the best solution. For mathematical expression evaluation, use strict whitelist validation: if (!preg_match('/^[0-9+\-*\/().\s]+$/', $expression)) { throw new InvalidArgumentException('Expression contains invalid characters'); } combined with expression parser libraries like mossadal/math-parser or symfony/expression-language that safely parse and evaluate expressions without eval(): use Symfony\Component\ExpressionLanguage\ExpressionLanguage; $language = new ExpressionLanguage(); $result = $language->evaluate($expression, ['x' => 5]); These libraries provide AST-based evaluation preventing code injection. For variable name validation: if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $variableName)) { throw new InvalidArgumentException(); } ensuring variable names match PHP identifier rules without special characters. Implement type validation for all inputs: filter_var($input, FILTER_VALIDATE_INT), filter_var($email, FILTER_VALIDATE_EMAIL), or custom validators. Use allowlist validation for enumerated values: $validStatuses = ['active', 'inactive', 'pending']; if (!in_array($status, $validStatuses, true)) { throw new InvalidArgumentException(); } Never rely solely on blacklist validation (blocking specific keywords) as bypasses always exist. Sanitize output even after validation: htmlspecialchars(), filter_var($input, FILTER_SANITIZE_STRING). For truly dynamic scenarios requiring code generation, generate code files during build/deployment rather than runtime, or use code generation libraries like nette/php-generator that programmatically construct AST then output code, preventing injection through API constraints.

6

Consider Using Sandbox Environments if Dynamic Execution is Necessary

If dynamic PHP code execution is an absolute business requirement after exploring all alternatives, implement strict sandboxing, containerization, and isolation to limit blast radius, though this significantly increases complexity and still carries substantial risk. Use runkit7 extension (experimental, not recommended for production) that allows disabling dangerous functions: runkit7_function_remove('exec'); runkit7_function_remove('system'); before eval(), though comprehensive function blocking is impractical. Implement PHP sandbox libraries like corveda/php-sandbox that parse PHP code into AST, filter dangerous operations, and execute in restricted context: use Corveda\Sandbox\Sandbox; $sandbox = new Sandbox(); $sandbox->setPhpCode($userCode); $output = $sandbox->execute(); These libraries provide incomplete protection and have bypass histories. Deploy separate Docker containers for code execution requests: docker run --rm --read-only --network=none --memory=128m --cpus=0.5 -v /app/user-code.php:/code.php php:cli php /code.php with resource limits, no network access, read-only filesystem, and automatic cleanup. Use AWS Lambda, Google Cloud Functions, or Azure Functions to isolate code execution in managed sandboxes with execution timeouts and isolated environments. Implement WebAssembly-based sandboxing compiling PHP to WASM for isolated execution. Apply principle of least privilege: run sandbox processes under dedicated non-privileged user accounts, use seccomp or AppArmor profiles restricting system calls, implement mandatory access controls (SELinux) preventing file access. Monitor sandbox execution: log all code execution attempts, implement anomaly detection for resource usage (CPU spikes, excessive memory), set strict timeouts (1-5 seconds), and alert on suspicious patterns. Document that sandboxing provides defense-in-depth but not complete security—determined attackers may discover sandbox escapes, making elimination of dynamic code execution the only truly secure long-term solution.

Detect This Vulnerability in Your Code

Sourcery automatically identifies php eval() function usage and many other security issues in your codebase.