Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to handle '===' opcode? #39

Open
dzentota opened this issue Feb 20, 2020 · 4 comments
Open

Is it possible to handle '===' opcode? #39

dzentota opened this issue Feb 20, 2020 · 4 comments
Labels
question Further information is requested

Comments

@dzentota
Copy link

How to intercept is identical (===) check ? e.g.

$o1 == $o2;

will trigger __compare() handler, while

$o1 === $o2;

doesn't trigger neither __compare() nor __doOperation() with \ZEngine\System\OpCode::IS_IDENTICAL;

@lisachenko lisachenko added the question Further information is requested label Feb 20, 2020
@lisachenko
Copy link
Owner

As I know, is identical implemented directly as an opcode handler and it doesn't call object handlers at all.

But you can install an opcode handler for OpCode::IS_IDENTICAL code via OpCode->setHandler() to handle comparison manually. But this handler could be changed soon to general hook scheme.

@dzentota
Copy link
Author

Thank you for quick reply. As I understood:

\ZEngine\System\OpCode::setHandler(\ZEngine\System\OpCode::IS_IDENTICAL,
    function (\ZEngine\System\ExecutionData $executionState): int {
       echo 'is_identical';
    });
$o1 === $o2;

it should echo is_identical, but I see nothing;

Actually what I want to archive - I want to do taint analysis based on your lib. So I need a wrapper class (ValueObject) which can behave like the value it wraps in most cases and in the same time give the ability to call some methods on it.
POC:

<?php

use ZEngine\ClassExtension\ObjectCastInterface;
use ZEngine\ClassExtension\ObjectCompareValuesInterface;
use ZEngine\ClassExtension\ObjectCreateInterface;
use ZEngine\ClassExtension\ObjectCreateTrait;
use ZEngine\ClassExtension\ObjectDoOperationInterface;

class Tainted implements
    ObjectCastInterface,
    ObjectCreateInterface,
    ObjectCompareValuesInterface,
    ObjectDoOperationInterface
{
    use ObjectCreateTrait;

    private $value;
    private $isSafe = false;

    public function __construct($value)
    {
        $this->value = $value;
    }

    public function value()
    {
        return $this->value;
    }

    public function markSafe()
    {
        $this->isSafe = true;
    }

    public function markUnsafe()
    {
        $this->isSafe = false;
    }

   public function isSafe(): bool
   {
        return $this->isSafe;
   }

    /**
     * Performs comparison of given object with another value
     *
     * @param \ZEngine\ClassExtension\Hook\CompareValuesHook $hook Instance of current hook
     *
     * @return int Result of comparison: 1 is greater, -1 is less, 0 is equal
     */
    public static function __compare(\ZEngine\ClassExtension\Hook\CompareValuesHook $hook): int
    {
        $first = $hook->getFirst();
        $second = $hook->getSecond();
        if (is_object($first) && get_class($first) === __CLASS__) {
            $first = $first->value();
        }
        if (is_object($second) && get_class($second) === __CLASS__) {
            $second = $second->value();
        }
        return $first <=> $second;
    }

    /**
     * Performs an operation on given object
     *
     * @param \ZEngine\ClassExtension\Hook\DoOperationHook $hook Instance of current hook
     *
     * @return mixed Result of operation value
     */
    public static function __doOperation(\ZEngine\ClassExtension\Hook\DoOperationHook $hook)
    {
        $first = $hook->getFirst();
        $second = $hook->getSecond();
        if (is_object($first) && get_class($first) === __CLASS__) {
            $first = $first->value();
        }
        if (is_object($second) && get_class($second) === __CLASS__) {
            $second = $second->value();
        }
        $opcode = $hook->getOpcode();
        switch (true) {
            case $opcode === \ZEngine\System\OpCode::ADD;
                return new Tainted($first + $second);
            case $opcode === \ZEngine\System\OpCode::SUB:
                return new Tainted($first - $second);
            case $opcode === \ZEngine\System\OpCode::CONCAT:
                return new Tainted($first . $second);
            case $opcode === \ZEngine\System\OpCode::IS_IDENTICAL:
                return $first === $second;
           // Here should be handling for all other opcodes...
        }
    }

    /**
     * Performs casting of given object to another value
     *
     * @param \ZEngine\ClassExtension\Hook\CastObjectHook $hook Instance of current hook
     *
     * @return mixed Casted value
     */
    public static function __cast(\ZEngine\ClassExtension\Hook\CastObjectHook $hook)
    {
        return $hook->getObject()->value();
    }
}

I expect next behavior:

$foo = new Tainted('foo');
$bar = new Tainted('bar');
$one = new Tainted(1);
$two = new Tainted(2);
echo $foo . $bar; //foobar
$one === 1; //true
$one == 1; //true
$two > $one;//true
// also instance of Tainted should pass type hints
function add(int $a, int $b) {
    return $a + $b;
}
echo add($one, $two); //3
//And another key feature: ability to know function name in which tainted variable was passed
$xss = '<script>alert(1)</script>';
if (rand(0,1) {
    //xss mitigation. **Tainted** should be some how notified in case it was to htmlspecialchars()
    $xss = htmlspecialchars($xss); 
}
var_dump($xss->isSafe());

Having such class I think it should be possible to wrap any variable at the beginning of the script and check if it safe at the end.

@lisachenko
Copy link
Owner

I'm not sure if your idea is possible right now, because messing with typehints and value boxing/unboxing in runtime is unpredictable. Of course, it is possible to wrap everything during AST processing, but object will violate your int typehint anyway.

Regarding your question about opcode handler: you should install it before the first inclusion of file that contains operations itself. PHP checks if specific opcode has user handler and generates specific version of opcodes to invoke custom handler. If you try to install handler after that - nothing will work...

@dzentota
Copy link
Author

Thank you. Yes, it triggers handler if I put it in a separate file. Could provide a little example of how two write such handlers. It's not clear how to access $arguments and which integer should be returned. e.g. if I want to return true if my objects are identical:

$o1 === $o2;
\ZEngine\System\OpCode::setHandler(\ZEngine\System\OpCode::IS_IDENTICAL,
    function (\ZEngine\System\ExecutionData $executionState): int {
        //$executionState->getArguments() returns empty array. $o1 and $o2 expected
        return Core::ZEND_USER_OPCODE_DISPATCH;// What should be returned here?
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants