* Null字符问题
由于 PHP 的文件系统操作是基于 C 语言的函数的,所以它可能会以您意想不到的方式处理 Null 字符。 Null字符在 C 语言中用于标识字符串结束,一个完整的字符串是从其开头到遇见 Null 字符为止。
# 会被 Null 字符问题攻击的代码:
$file = $_GET['file']; // "http://www.cnblogs.com/etc/passwd\0"
if (file_exists('/home/wwwrun/'.$file.'.php')) {
// file_exists will return true as the file /home/wwwrun/http://www.cnblogs.com/etc/passwd exists
include '/home/wwwrun/'.$file.'.php';
// the file /etc/passwd will be included
}
# 验证输入的正确做法
$file = $_GET['file'];
// 对字符串进行白名单检查
switch ($file) {
case 'main':
case 'foo':
case 'bar':
include '/home/wwwrun/include/'.$file.'.php';
break;
default:
include '/home/wwwrun/include/main.php';
}
四、数据库安全
* 设计数据库
第一步一般都是创建数据库,除非是使用第三方的数据库服务。当创建一个数据库的时候,会指定一个所有者来执行和新建语句。通常,只有所有者(或超级用户)才有权对数据库中的对象进行任意操作。如果想让其他用户使用,就必须赋予他们权限。
应用程序永远不要使用数据库所有者或超级用户帐号来连接数据库,因为这些帐号可以执行任意的操作,比如说修改数据库结构(例如删除一个表)或者清空整个数据库的内容。
应该为程序的每个方面创建不同的数据库帐号,并赋予对数据库对象的极有限的权限。仅分配给能完成其功能所需的权限,避免同一个用户可以完成另一个用户的事情。这样即使攻击者利用程序漏洞取得了数据库的访问权限,也最多只能做到和该程序一样的影响范围。
鼓励用户不要把所有的事务逻辑都用 web 应用程序(即用户的脚本)来实现。最好用视图(view)、触发器(trigger)或者规则(rule)在数据库层面完成。当系统升级的时候,需要为数据库开辟新的接口,这时就必须重做所有的数据库客户端。除此之外,触发器还可以透明和自动地处理字段,并在调试程序和跟踪事实时提供有用的信息。
Example #1 一段实现数据分页显示的代码……也可以被用作创建一个超级用户(PostgreSQL系统)。
<?php
$offset = $argv[0]; // 注意,没有输入验证!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
<?php
// $uid == ' or uid like'%admin%'; --
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";
// $pwd == "hehehe', admin='yes', trusted=100 "
$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE
...;";
?>
下面这个可怕的例子将会演示如何在某些数据库上执行系统命令。
Example #4 攻击数据库所在主机的操作系统(MSSQL Server)
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
如果攻击提交 a%' exec master..xp_cmdshell 'net user test testpass /ADD' --作为变量 $prod的值,那么 $query将会变成
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
?>
<?php
settype($offset, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// 请注意格式字符串中的 %d,如果用 %s 就毫无意义了
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$offset);
?>
<?php
if ($username) { // Not initialized or checked before usage
$good_login = 1;
}
if ($good_login == 1) { // If above test fails, not initialized or checked before usage
readfile ("/highly/sensitive/data/index.html");
}
?>
六、使用Register Globals
可能 PHP 中最具争议的变化就是从 PHP » 4.2.0版开始配置文件中 register_globals的默认值从 on 改为 off 了。对此选项的依赖是如此普遍以至于很多人根本不知道它的存在而以为 PHP 本来就是这么工作的。本节会解释用这个指令如何写出不安全的代码,但要知道这个指令本身没有不安全的地方,误用才会。
当 register_globals 打开以后,各种变量都被注入代码,例如来自 HTML 表单的请求变量。再加上 PHP 在使用变量之前是无需进行初始化的,这就使得更容易写出不安全的代码。这是个很艰难的抉择,但 PHP 社区还是决定默认关闭此选项。当打开时,人们使用变量时确实不知道变量是哪里来的,只能想当然。但是 register_globals 的关闭改变了这种代码内部变量和客户端发送的变量混杂在一起的糟糕情况。下面举一个错误使用 register_globals 的例子:
Example #1 错误使用 register_globals = on 的例子
<?php
// 当用户合法的时候,赋值 $authorized = true
if (authenticated_user()) {
$authorized = true;
}
// 由于并没有事先把 $authorized 初始化为 false,
// 当 register_globals 打开时,可能通过GET auth.php?authorized=1 来定义该变量值
// 所以任何人都可以绕过身份验证
if ($authorized) {
include "/highly/sensitive/data.php";
}
?>
当 register_globals = on 的时候,上面的代码就会有危险了。如果是 off,$authorized 就不能通过如 URL 请求等方式来改变,这样就好多了,尽管初始化变量是一个良好的编程习惯。比如说,如果在上面的代码执行之前加入 $authorized = false 的话,无论 register_globals 是 on 还是 off 都可以,因为用户状态被初始化为未经认证。
另一个例子是关于会话的。当 register_globals = on 的时候,$username 也可以用在下面的代码中,但要意识到 $username 也可能会从其它途径进来,比如说通过 URL 的 GET。
Example #2 使用会话时同时兼容 register_globals on 和 off 的例子
<?php
// 我们不知道 $username 的来源,但很清楚 $_SESSION 是
// 来源于会话数据
if (isset($_SESSION['username'])) {
echo "Hello <b>{$_SESSION['username']}</b>";
} else {
echo "Hello <b>Guest</b><br />";
echo "Would you like to login?";
}
?>
采取相应的预防措施以便在伪造变量输入的时候给予警告是完全有可能的。如果事先确切知道变量是哪里来的,就可以检查所提交的数据是否是从不正当的表单提交而来。不过这不能保证变量未被伪造,这需要攻击者去猜测应该怎样去伪造。如果不在乎请求数据来源的话,可以使用 $_REQUEST 数组,它包括了 GET、POST 和 COOKIE 的所有数据。详情可参见本手册的来自 PHP 之外的变量。
; Magic quotes
;
; Magic quotes for incoming GET/POST/Cookie data.
magic_quotes_gpc = Off
; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc.
magic_quotes_runtime = Off
; Use Sybase-style magic quotes (escape ' with '' instead of \').
magic_quotes_sybase = Off