Search code examples
phpevalunsetvariable-variables

Using eval() in the code, and how would you avoid it?


In my code, I want to try fetching $_POST or $_GET variables, and unsetting them as soon as they were requested once.

This function returns the method used, which is simple enough. Unfortunately I cannot simply return the $_POST or $_GET variables directly or by reference, because I wouldn't be able to unset them (unsetting by reference doesn't work). So, I return variable names:

class Input {
  protected function interface_get($method): string {
    switch ($method) {
      case 'post':
        return '_POST';
        break;
      case 'get':
        return '_GET';
        break;
      case 'ajax':
        // Not supported yet.
        return null;
        break;
      default:
        return null; // returns '_GET' or '_POST'
    }
  }
  public function fetch_eval(string $method, ?string $request = null) { // ...fetch('post', 'username')
    if ($request !== null) {
      $request = '[\'' . $request . '\']'; // "['username']"
    }
    $request = $this->interface_get($method) . $request; #"$_POST['username']"
    eval('$return = $' . $request . ';'); #$return = $_POST['username'];
    eval('unset($' . $request . ');'); #unset($_POST['username']);
    return $return;
  }
  public function fetch_varvar(string $method, ?string $request = null) {
    $interface = $this->interface_get($method); #$interface = '_POST';
    if ($request === null) {
      $return = (${$interface});
      unset(${$interface});
    } else {
      $return = ${$interface}; #"Notice:  Undefined variable: _POST in [...]"
      $return = ${$interface}[$request]; #"Warning:  Illegal string offset 'email' in [...]"
      unset($interface[$request]);
    }
    return $result;
}
}
// set $_POST = ['email'=>'spam@me'];
$in = new Input();
echo $in->fetch_eval('post', 'email'); #'spam@me'
// $_POST = [];

// set $_POST = ['email'=>'spam@me']; again
echo $in->fetch_varvar('post', 'email'); #'Notice:  Undefined variable: _POST [...]'

The fun part is processing that output. Here is my fetch function, the easiest, but dirtiest, I think, way:

I tried using variable variables, it worked in a test script:

// $_POST = [0 => 5, 'bob' => 5];
$e = '_POST';
$var = 'bob';
echo ${$e}[$var]; #5
unset(${$e}[$var]);
echo ${$e}[$var ]; #NOTICE Undefined index: bob on line number 22
// Works as expected.

But it did not work in my script (Undefined variable: _POST [...].). I thought maybe the reason was that it was in a class, but anyway I couldn't solve it. What's more, if you put these functions outside the class and remove the $this->-es, they work! But not inside the class.

If someone could tell me why my latter code doesn't work, I would appreciate it. But in any case, would you say using eval() is reasonable? I know some people avoid it no matter what. Obviously in this case it opens up a pretty huge vulnerability, so rather than sanitizing and worrying I would prefer to avoid it altogether.

I would like to keep interface_get() as a separate function, but if needed, I can duplicate it inside fetch() too, I suppose.

Thank you very much in advance.


Solution

  • You could use $GLOBALS[$interface] to get your data.

    Assuming $_POST and $_GET is always defined, however, you could check if the keys are defined before to get it and unset it, to avoid warnings.

    public function fetch(string $method, ?string $request = null) {
        $interface = $this->interface_get($method); #$interface = '_POST';
        if ($request === null) {
            $result = $GLOBALS[$interface];
            unset($GLOBALS[$interface]);
            return $result;
        }
        if (isset($GLOBALS[$interface][$request])) {
            $result = $GLOBALS[$interface][$request];
            unset($GLOBALS[$interface][$request]);
            return $result;
        }
        return null;
    }