【问题标题】:What's the point of having __wakeup in source code of PHP 7.4 Exceptions?在 PHP 7.4 异常的源代码中包含 __wakeup 有什么意义?
【发布时间】:2019-11-20 12:17:13
【问题描述】:

存在一种称为phardeserialisation 的(相当新的)利用技术,它由PHP 的file system functions 触发,例如file_existsunlink,...然后非序列化代码在实例化类中执行需要至少包含__wakeup__destruct 两个魔术方法之一。

我的想法是在 PHP 的源代码中搜索内置 PHP 类,这些类可能具有本机实现的魔术方法(__wakeup__destruct)作为此类类的一部分,并且可以在反序列化过程中以某种方式被利用 异常处理(可能会触发一些释放后使用的错误)。事实上,我在几个 Exception 类中找到了__wakeup(获取所有内置类并检查它们是否具有__wakeup__destruct 方法的简单方法是使用PHP 函数get_declared_classes())。但目前看来,它似乎无法利用。

所以问题不在于利用事物。

问题:我不明白为什么,例如__wakeup异常中实现?它只是扩展此类 Exception 类并在以后编写自己的__wakeup/__destruct 方法的占位符方法吗?

源代码: https://github.com/php/php-src/blob/master/Zend/zend_exceptions.c#L316

在第 306-333 行(php-src/Zend/zend_exceptions.c,PHP 7.4):

/* }}} */

/* {{{ proto Exception::__wakeup()
   Exception unserialize checks */
#define CHECK_EXC_TYPE(id, type) \
    pvalue = zend_read_property_ex(i_get_exception_base(object), (object), ZSTR_KNOWN(id), 1, &value); \
    if (Z_TYPE_P(pvalue) != IS_NULL && Z_TYPE_P(pvalue) != type) { \
        zend_unset_property(i_get_exception_base(object), object, ZSTR_VAL(ZSTR_KNOWN(id)), ZSTR_LEN(ZSTR_KNOWN(id))); \
    }

ZEND_METHOD(exception, __wakeup)
{
    zval value, *pvalue;
    zval *object = ZEND_THIS;
    CHECK_EXC_TYPE(ZEND_STR_MESSAGE,  IS_STRING);
    CHECK_EXC_TYPE(ZEND_STR_STRING,   IS_STRING);
    CHECK_EXC_TYPE(ZEND_STR_CODE,     IS_LONG);
    CHECK_EXC_TYPE(ZEND_STR_FILE,     IS_STRING);
    CHECK_EXC_TYPE(ZEND_STR_LINE,     IS_LONG);
    CHECK_EXC_TYPE(ZEND_STR_TRACE,    IS_ARRAY);
    pvalue = zend_read_property(i_get_exception_base(object), object, "previous", sizeof("previous")-1, 1, &value);
    if (pvalue && Z_TYPE_P(pvalue) != IS_NULL && (Z_TYPE_P(pvalue) != IS_OBJECT ||
            !instanceof_function(Z_OBJCE_P(pvalue), zend_ce_throwable) ||
            pvalue == object)) {
        zend_unset_property(i_get_exception_base(object), object, "previous", sizeof("previous")-1);
    }
}
/* }}} */

之后在第 788-801 行: https://github.com/php/php-src/blob/master/Zend/zend_exceptions.c#L788

static const zend_function_entry default_exception_functions[] = {
    ZEND_ME(exception, __clone, NULL, ZEND_ACC_PRIVATE|ZEND_ACC_FINAL)
    ZEND_ME(exception, __construct, arginfo_exception___construct, ZEND_ACC_PUBLIC)
    ZEND_ME(exception, __wakeup, NULL, ZEND_ACC_PUBLIC)
    ZEND_ME(exception, getMessage, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
    ZEND_ME(exception, getCode, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
    ZEND_ME(exception, getFile, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
    ZEND_ME(exception, getLine, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
    ZEND_ME(exception, getTrace, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
    ZEND_ME(exception, getPrevious, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
    ZEND_ME(exception, getTraceAsString, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
    ZEND_ME(exception, __toString, NULL, 0)
    ZEND_FE_END
};

【问题讨论】:

    标签: php c exception deserialization magic-methods


    【解决方案1】:

    我认为关键在于,由于 Exception 类应该是可反序列化的,它需要在 C 级别添加 __wakeup() 方法(因为 Exception 类 属性也在 C 中定义)。

    【讨论】:

      【解决方案2】:

      据我所知,这段代码正在验证异常属性的类型,如果类型不正确,则将它们设置为null。例如:

      CHECK_EXC_TYPE(ZEND_STR_MESSAGE,  IS_STRING);
      

      大致相当于PHP代码:

      if (!is_string($this->message))
          $this->message = null;
      

      同样,最后的代码以pvalue = zend_read_property(…) 开头,检查$this->previous 是一个实现Throwable 的对象。在 PHP 中,它大致相当于:

      if (!is_object($this->previous) || !($this->previous instanceof Throwable)
          $this->previous = null;
      

      至于为什么这段代码是 PHP 解释器的一部分?整个 Exception 类在 C 代码中定义。没有任何内置类或方法是用 PHP 编写的——它们不可能,因为该语言无法将方法的 PHP 实现安装到用 C 声明的类中。

      【讨论】:

      • 嗯,你描述的是CHECK_EXC_TYPE的代码。但我很好奇为什么 Exception 类的__wakeup 方法需要在 C 级别而不是以后在 PHP 级别定义,我们可以在其中例如定义像__destruct 这样的魔术方法。所以我知道,我们正在检查第 306-333 行中的类型。但是在这里定义__wakeup 有什么意义呢?在第 788-801 行:ZEND_ME(exception, __wakeup, NULL, ZEND_ACC_PUBLIC) 只是说__wakeupException 类的一部分并且是公共的。而已。只是不清楚它在这里做什么。
      • 拥有__wakeup 的意义在于它只是在本地启用反序列化(不定义自己的 Exception 类)吗?但由于它没有__sleep 方法,它实际上不应该被序列化(但它仍然有效)。所以这对我来说毫无意义。
      • "但是我很好奇为什么 Exception 类的 __wakeup 方法需要在 C 级别而不是以后在 PHP 级别定义" -- 因为没有与 @987654338 对应的 PHP 代码@ 班级。该类完全在 PHP 中定义,因此必须在其中定义任何方法。
      • 我认为你是对的。我认为关键是因为 Exception 类应该是可反序列化的,它需要在 C 级别添加 __wakeup() 方法(因为 Exception 类属性也在 C 中定义) .
      • 也许您可以将您的评论移至上面的答案,稍作修改,我将其标记为已回答。再次感谢您。
      猜你喜欢
      • 2011-04-13
      • 2013-11-08
      • 2012-06-03
      • 1970-01-01
      • 1970-01-01
      • 2011-05-14
      • 1970-01-01
      • 2012-05-14
      • 1970-01-01
      相关资源
      最近更新 更多