19.5 PHP

本节主要介绍在PHP平台上获取用户提交的输入的方法、与用户会话交互的方式、其中存在潜在危险的API以及与平台安全相关的配置选项。

19.5.1 确定用户提交的数据

PHP使用一系列数组变量保存用户提交的数据,如表19-7所示。

表19-7 PHP平台中用于获取用户提交的数据的变量

变 量 描 述
$_GET
$HTTP_GET_VARS
这个数组包含在查询字符串中提交的参数。这些参数根据其名称访
问。例如,在下面的URL中:
https://wahh-app.com/search.php?query=foo
查询参数的值使用以下代码访问:
$_GET[‘query’]
$_POST $HTTP_POST_VARS 这个数组包含在请求主体中提交的参数
$_COOKIE $HTTP_COOKIE_VARS 这个数组包含在请求主体中提交的cookie
$_REQUEST 这个数组包含$_GET、$_POST与$_COOKIE数组中的所有数据
$_FILES $HTTP_POST_FILES 这个数组包含在请求中上传的文件
$_SERVER[‘REQUEST_METHOD’] 包含在HTTP请求中使用的方法
$_SERVER[‘QUERY_STRING’] 包含在请求中提交的完整查询字符串
$_SERVER[‘REQUEST_URI’] 包含在请求中提交的完整URL
$_SERVER[‘HTTP_ACCEPT’] 包含HTTP Accept消息头的内容
$_SERVER[‘HTTP_ACCEPT_CHARSET’] 包含HTTP Accept-charset消息头的内容
$_SERVER[‘HTTP_ACCEPT_ENCODING’] 包含HTTP Accept-encoding消息头的内容
$_SERVER[‘HTTP_ACCEPT_LANGUAGE’] 包含HTTP Accept-language消息头的内容
$_SERVER[‘HTTP_CONNECTION’] 包含HTTP Connection消息头的内容
$_SERVER[‘HTTP_HOST’] 包含HTTP Host消息头的内容
$_SERVER[‘HTTP_REFERER’] 包含HTTP Referer消息头的内容
$_SERVER[‘HTTP_USER_AGENT’] 包含HTTP User-agent消息头的内容
$_SERVER[‘PHP_SELF’] 包含当前运行脚本的名称。虽然攻击者无法控制脚本名称,但可以
在这个名称后附加路径信息。例如,如果一个脚本包含以下代码:
<form action=“<?= $_SERVER[‘PHP_SELF’] ?>”>
那么攻击者就可以设计诸如下面的跨站点脚本攻击:
/search.php/"><script>

当尝试确定PHP应用程序如何访问用户提交的输入时,应该记住以下反常情况。

img002 $GLOBALS是一个包含在脚本全局范围内定义的所有变量的引用的数组。使用它可以根据名称访问其他变量。

img002 如果配置指令register_globals被激活,PHP会为所有请求参数(即$_REQUEST数组中的全部数据)建立全局变量。这表示,应用程序可通过与相关参数相同的名称引用一个变量,从而访问用户输入。如果应用程序使用这种方法访问用户提交的数据,那么只有仔细地逐行审查代码,才能确定以这种方式使用的变量。

img002 除前面提到的标准HTTP消息头外,PHP还在$_SERVER数组中增加了一个数据,用于处理在请求中收到的任何定制HTTP消息头。例如,提交消息头:

img558a

生成:

img558b

img002 名称包含下标(方括号内)的输入参数被自动转换为数组。例如,请求下面的URL:

img559a

将使$_GET[‘query’]变量的值转换成一个包含两个成员的数组。如果一个数组被提交给一个希望收到标量值的函数,可能会在应用程序中出现无法预料的行为。

19.5.2 会话交互

PHP使用$_SESSION数组保存和检索用户会话中的信息。例如:

img559b

$HTTP_SESSION_VARS数组的用法与上面的数组相同。

如果register_globals被激活(见19.5.4节),那么全局变量将通过以下方式保存在当前会话中:

img559c

19.5.3 潜在危险的API

这一节介绍一些常见的PHP API。以危险的方式使用这些API可能会造成安全漏洞。

1.文件访问

PHP中包含大量用于访问文件的函数,其中许多接受可用于访问远程文件的URL和其他结构。

下面的函数用于读取或写入一个指定文件的内容。如果向这些API提交用户可控制的数据,攻击者就可以利用这些API访问服务器文件系统上的任意文件。

img002 fopen

img002 readfile

img002 file

img002 fpassthru

img002 gzopen

img002 gzfile

img002 gzpassthru

img002 readgzfile

img002 copy

img002 rename

img002 rmdir

img002 mkdir

img002 unlink

img002 file_get_contents

img002 file_put_contents

img002 parse_ini_file

下面的函数用于包含并执行一个指定的PHP脚本。如果攻击者能够使应用程序执行受控的文件,他就可以在服务器上执行任意命令。

img002 include

img002 include_once

img002 require

img002 require_once

img002 virtual

请注意,即使无法包含远程文件,但如果攻击者可向服务器上传任意文件,他仍然能够执行任意命令。

PHP配置选项allow_url_fopen可用于防止一些文件函数访问远程文件。但是,在默认情况下,这个选项设为1(表示允许远程文件);因此,表19-8中列出的协议可用于检索远程文件。

表19-8 可用于检索远程文件的网络协议

协 议 示 例
HTTP,HTTPS http://wahh-attacker.com/bad.php
FTP ftp://user:password@wahh-attacker.com/bad.php
SSH ssh2.shell://user:pass@wahh-attacker.com:22/xterm
ssh2.exec://user:pass@wahh-attacker.com:22/cmd

即使allow_url_fopen设为0,攻击者仍然可以使用表19-9列出的方法访问远程文件(取决于所安装的扩展)。

表19-9 allow_url_fopen设为0时仍然可用于访问远程文件的方法

方 法 示 例
SMB \\wahh-attacker.com\bad.php
PHP输入/输出流 php://filter/resource=http://wahh-attacker.com/bad.php
压缩流 compress.zlib://http://wahh-attacker.com/bad.php
音频流 ogg://http://wahh-attacker.com/bad.php

img001  注解  PHP5.2以后的版本引入了一个新的选项allow_url_include,默认情况下,该选项被禁用。这个默认的配置防止前面提到的方法在调用文件包含函数时用于指定一个远程文件。

2.数据库访问

下面的函数用于向数据库发送一个查询并检查查询结果:

img002 mysql_query

img002 mssql_query

img002 pg_query

SQL语句以一个简单的字符串提交。如果用户可控制的数据属于字符串参数的一部分,那么应用程序就可能容易受到SQL注入攻击。例如:

img561a

它会执行不良查询:

img561b

下面的函数可用于创建预处理语句,允许应用程序建立一个包含参数占位符的SQL查询,并以可靠而且类型安全的方式设定这些占位符的值:

img002 mysqli->prepare

img002 stmt->prepare

img002 stmt->bind_param

img002 stmt->execute

img002 odbc_prepare

如果按照正常的方式使用,这种机制就不易受到SQL注入攻击。例如:

img561c

它生成的查询等同于:

img561d

3.动态代码执行

下面的函数可用于动态执行PHP代码:

img002 eval

img002 call_user_func

img002 call_user_func_array

img002 call_user_method

img002 call_user_method_array

img002 create_function

分号分隔符用于将几个语句连接在一起。如果向这些函数提交用户可控制的数据,那么应用程序可能易于受到脚本注入攻击。

搜索与替代正则表达式的preg_replace函数,如果以/e选项调用,可用于运行一段特殊的PHP代码。如果用户可控制的数据出现在动态执行的PHP代码中,应用程序可能易于受到攻击。

PHP的另一个有趣的特点在于,它可以通过一个包含函数名称的变量动态调用该函数。例如,下面的代码将调用在查询字符串func参数中指定的函数:

img562a

这时,用户可以通过修改func参数的值,使应用程序调用任意一个函数(没有参数)。例如,调用phpinfo函数将使应用程序输出大量与PHP环境有关的信息,包括配置选项、操作系统信息与扩展。

4.OS命令执行

下面这些函数可用于执行操作系统命令:

img002 exec

img002 passthru

img002 popen

img002 proc_open

img002 shell_exec

img002 system

img002 反单引号(`)

所有这些命令都可以使用 | 字符链接在一起。如果未经过滤就向这些函数提交用户可控制的数据,那么攻击者就可以在应用程序中执行任意命令。

5.URL重定向

下面的API用于在PHP中发布一个HTTP重定向:

img002 http_redirect

img002 header

img002 HttpMessage::setResponseCode

img002 HttpMessage::setHeaders

通常,使用http_redirect函数可以实现一个重定向,该函数接受一个包含相对或绝对URL的字符串。如果这个字符串的值由用户控制,那么应用程序可能易于受到钓鱼攻击。

通过调用包含适当Location消息头的header函数也可以实现重定向,它让PHP得出结论,认为需要一个HTTP重定向。例如:

img562b

还应仔细审查setResponseCode与setHeaders API的用法。如果某个重定向包含一个含有 HTTP Location 消息头的3xx响应,应用程序就可能使用这些API执行重定向。

6.套接字

下面的API用于在PHP中建立和使用网络套接字:

img002 socket_create

img002 socket_connect

img002 socket_write

img002 socket_send

img002 socket_recv

img002 fsockopen

img002 pfsockopen

使用socket_create创建一个套接字后,再通过调用socket_connect与远程主机建立连接;这个API接受目标主机的IP与端口信息为参数。如果用户能够以某种方式控制这些主机信息,攻击者就可以利用应用程序与任意主机建立网络连接,无论这些主机位于公共因特网上、私有DMZ中还是应用程序运行的内部网络。

fsockopen与pfsockopen函数可用于打开连接指定主机与端口的套接字,并返回一个可用在fwrite和fgets等标准文件函数中的文件指针。如果向这些函数提交用户数据,应用程序就可能易于受到攻击,如前文所述。

19.5.4 配置PHP环境

PHP配置选项在php.ini文件中指定,该文件使用与Windows INI文件相同的结构。有各种选项都会影响一个应用程序的安全。最新版的PHP删除了许多以前引起问题的选项。

1.使用全局变量注册

如果register_globals指令被激活,PHP会为所有请求参数建立全局变量。如果PHP不要求变量在使用前被初始化,这个选项就会导致安全漏洞,使攻击者能够将一个变量初始化为任意一个值。

例如,下面的代码检查一名用户的证书,如果证书有效,就将$authenticated变量值设为1:

img563

因为最初PHP没有将$authenticated变量明确地初始化为0,攻击者就可以通过提交请求参数authenticated=1避开登录。这使PHP在进行证书检查之前就将全局变量$authenticated设为1。

img001  注解  从PHP 4.2.0开始,register_globals指令默认被禁用。然而,由于许多老式应用程序依赖于register_globals执行的正常操作,因此,通常php.ini会明确激活该指令。PHP 6完全删除了register_globals指令。

2.安全模式

如果safe_mode指令被激活,那么PHP会对使用某些危险的函数施加限制。一些函数被完全禁用,其他一些函数的使用也受到限制,如下所示。

img002 shell_exec函数被禁用,因为这个函数可用于执行操作系统命令。

img002 mail函数的additional_parameters参数被禁用,因此,如果以不安全的方式使用这个参数,可能导致SMTP注入漏洞(请参阅第10章了解相关内容)。

img002 exec函数仅能够执行safe_mode_exec_dir指定目标下的可执行程序,命令字符串中的元字符被自动转义。

img001  注解  并非所有的危险函数都受到安全模式的限制,一些限制受到其他配置选项的影响。而且,有各种方法可以避开一些安全模式限制。安全模式并不能完全解决PHP应用程序中的安全问题。PHP 6已删除安全模式。

3.magic quotes

如果激活magic_quotes_gpc指令,那么请求参数中包含的任何单引号、双引号、反斜线和空字符都会用一个反斜线自动转义。如果magic_quotes_sybase指令被禁用,那么PHP就会用一个单引号转义所有单引号。这个选项旨在保护包含不安全的数据库调用的危险代码,以防它被恶意的用户输入利用。在应用程序的代码中查找SQL注入漏洞时,应该检查magic quotes是否被激活,因为它会影响应用程序处理输入的方式。

使用magic quotes并不能防止所有SQL注入攻击。如第9章所述,注入一个数字字段的攻击并不需要使用单引号。而且,如果其中包含的引号没有被转义的数据随后又从数据库中读回,那么仍可以利用这些数据实施二阶攻击。

在不需要任何转义的情况下处理数据时,激活magic quotes选项可能会使PHP对用户输入进行不必要的修改,导致代码中多出斜线,还要使用stripslashes函数删除。

在必要时,一些应用程序通过addslashes函数提交参数,自行对相关输入进行转义。如果PHP配置激活了magic quotes,那么这种方法就会导致双重转义字符,这时就会将配对的斜线解释为字面量反斜线,潜在恶意字符不会转义。

由于magic quotes选项的局限性与不规则性,建议禁用该选项,使用预处理语句安全访问数据库。

img001  注解  PHP 6已删除magic quotes选项。

4.其他

表19-10列出了其他一些可能影响PHP应用程序安全的配置选项。

表19-10 其他PHP配置选项

选 项 描 述
allow_url_fopen 如果禁用,该指令阻止一些文件函数访问远程文件(如前文所述)
allow_url_include 如果禁用,该指令阻止PHP文件包含函数用于包含一个远程文件
display_errors 如果禁用,该指令阻止PHP向用户浏览器发送错误消息。log_errors与error_log选项
可在服务器上记录错误消息,以方便诊断错误
file_uploads 如果激活,该指令将导致PHP允许通过HTTP上传文件
upload_tmp_dir 这个指令可用于指定保存上传的文件的临时目录。该指令确保不会将敏感文件保存在任何
用户都可访问的位置