Skip to content
Pro Analyzer — Available with ShieldCI Pro

Eval Usage Analyzer

Analyzer IDCategorySeverityTime To Fix
eval-usage🛡️ SecurityCritical20 minutes

What This Checks

Detects eval() and related dynamic code execution patterns. Severity escalates to Critical when tainted user-input variables (tracked from $_GET, request(), $request->, etc.) flow into any dangerous call, including pre-assigned variables like $fn = request('x'); ob_start($fn).

Detected Patterns:

Code Execution Functions (4)

  • eval() - executes arbitrary PHP code
  • assert() with a string literal - treated as code in PHP < 8.3
  • create_function() - deprecated (PHP 7.2), removed (PHP 8.0); uses eval internally
  • preg_replace() with /e modifier - executes replacement as PHP code (removed PHP 7.0)

Dynamic Invocation with User Input (5)

Flagged when the callable or class name is tainted by user input:

  • call_user_func() / call_user_func_array() with a user-controlled function name
  • $func() - variable function calls
  • $obj->$method() - dynamic method dispatch
  • Class::$method() - dynamic static dispatch
  • new $class() - dynamic class instantiation

Unsafe Deserialization (1)

  • unserialize() with user input - Critical; PHP object injection / RCE
  • unserialize() without a safe allowed_classes restriction - Medium; flagged even without user input. Resolved by ['allowed_classes' => false] (no objects needed) or ['allowed_classes' => [MyClass::class, ...]] (explicit known-class list). true or an empty [] still warns.

User-Controlled Callbacks and Templates (4)

  • ob_start() with a user-supplied callable
  • register_shutdown_function() with a user-supplied callable
  • register_tick_function() with a user-supplied callable
  • Blade::compileString() with user input - @php directives allow arbitrary PHP execution

Why It Matters

Dynamic code execution is one of the most dangerous vulnerability classes, allowing attackers to run arbitrary PHP code on your server:

  • Remote Code Execution (RCE) - Complete server takeover by injecting malicious PHP code
  • Data Exfiltration - Reading sensitive files, environment variables, database credentials
  • Backdoor Installation - Persistent server access by writing malicious files
  • Privilege Escalation - Executing system commands as the web server user
  • Lateral Movement - Using the compromised server to attack internal services
  • PHP Object Injection - Triggering __wakeup, __destruct, or magic methods on existing classes via unserialize()

A single dynamic code execution call with user input gives attackers the same access as your PHP process, making it one of the most critical security vulnerabilities possible.

How to Fix

Quick Fix (5 minutes)

Replace dynamic code execution with proper control structures or callbacks:

Before (❌):

php
public function calculate(Request $request)
{
    $formula = $request->input('formula');
    eval($formula); // VULNERABLE
}

After (✅):

php
public function calculate(Request $request)
{
    $validated = $request->validate([
        'operand1' => 'required|numeric',
        'operand2' => 'required|numeric',
        'operator' => 'required|in:add,subtract,multiply,divide',
    ]);

    $result = match ($validated['operator']) {
        'add'      => $validated['operand1'] + $validated['operand2'],
        'subtract' => $validated['operand1'] - $validated['operand2'],
        'multiply' => $validated['operand1'] * $validated['operand2'],
        'divide'   => $validated['operand2'] != 0
            ? $validated['operand1'] / $validated['operand2']
            : throw new \InvalidArgumentException('Division by zero'),
    };

    return response()->json(['result' => $result]);
}

Proper Fix (20 minutes)

Replace create_function() with closures:

Before (❌):

php
// VULNERABLE: create_function() uses dynamic evaluation internally
$sorter = create_function('$a, $b', 'return $a["name"] <=> $b["name"];');
usort($items, $sorter);

After (✅):

php
// SAFE: Arrow function (PHP 7.4+)
usort($items, fn($a, $b) => $a['name'] <=> $b['name']);

Replace preg_replace() with /e modifier:

Before (❌):

php
// VULNERABLE: /e modifier executes replacement as PHP code
$result = preg_replace('/\{(\w+)\}/e', '$data["$1"]', $template);

After (✅):

php
// SAFE: preg_replace_callback() with closure
$result = preg_replace_callback('/\{(\w+)\}/', function ($matches) use ($data) {
    return $data[$matches[1]] ?? '';
}, $template);

Replace assert() with string argument:

Before (❌):

php
// VULNERABLE: assert() with string executes code
assert('$user->isValid()');

After (✅):

php
// SAFE: assert() with boolean expression
assert($user->isValid());

// In production, disable assertions entirely:
// zend.assertions = 0

Secure call_user_func() usage:

Before (❌):

php
public function execute(Request $request)
{
    $callback = $request->input('action');
    call_user_func($callback, $request->input('data')); // VULNERABLE
}

After (✅):

php
public function execute(Request $request)
{
    $validated = $request->validate([
        'action' => 'required|in:process,validate,transform',
        'data'   => 'required|string',
    ]);

    $callbacks = [
        'process'   => [$this, 'processData'],
        'validate'  => [$this, 'validateData'],
        'transform' => [$this, 'transformData'],
    ];

    call_user_func($callbacks[$validated['action']], $validated['data']);
}

Fix unserialize() usage:

Before (❌):

php
// VULNERABLE: user-controlled data
$object = unserialize($request->input('payload'));

// UNSAFE: no class restriction
$cached = unserialize($data);

After (✅):

php
// SAFE: reject user input entirely — use JSON instead
$data = json_decode($request->input('payload'), true);

// SAFE: restrict allowed classes when deserializing internal data
$cached = unserialize($data, ['allowed_classes' => false]);

// SAFE: allow only specific trusted classes
$cached = unserialize($data, ['allowed_classes' => [MyValueObject::class]]);

Fix dynamic class instantiation:

Before (❌):

php
$class = $request->input('driver');
$instance = new $class(); // VULNERABLE

After (✅):

php
$validated = $request->validate(['driver' => 'required|in:mysql,sqlite,pgsql']);

$drivers = [
    'mysql'  => MySqlDriver::class,
    'sqlite' => SqliteDriver::class,
    'pgsql'  => PgsqlDriver::class,
];

$instance = new $drivers[$validated['driver']]();

Fix callback registration with user input:

Before (❌):

php
// VULNERABLE: user controls the callback
ob_start($request->input('handler'));
register_shutdown_function($request->input('fn'));

After (✅):

php
// SAFE: use a fixed, static callback
ob_start(function () {
    // process output
});

// SAFE: register only known, internal functions
register_shutdown_function([$this, 'cleanup']);

Fix Blade::compileString() with user input:

Before (❌):

php
// VULNERABLE: user-controlled Blade template compiles arbitrary @php blocks
$html = Blade::compileString(request('template'));

After (✅):

php
// SAFE: pass user data as variables, never as the template itself
return view('templates.user-content', [
    'content' => $request->input('content'),
]);

References