PHP接受外部数据的规则:
规则1:绝不要信任外部数据或输入
有潜在问题的代码:
1 |
|
可以看出$username是来自表单提交$_POST二来,用户可以在这个输入域中输入任何字符串,包括用来清除文件或运行以前上传的文件的恶意命令。您可能会问,“难道不能使用只接受字母 A-Z 的客户端(Javascrīpt)表单检验脚本来避免这种危险吗?”。
但是不要相信任何外部数据。
解决这个问题很简单,就是对$_POST[‘username’]进行清理。
假设我们只希望$_POST[‘username’]获得[a-z]范围的字符串
1 |
|
规则2:禁用那些使安全性难以实施的PHP配置
比如:
- 确保禁用register_globals
- 确保禁用系统命令
end so on…
规则3:如果不能理解它,就不能保护它
有些代码写法可能比较简洁,但是语义模糊,或者不够清晰,就很容易出现漏洞
语义不清晰的代码1
2
$input = isset($_POST['username']) ? $_POST['username'] : "";
语义更好的代码:1
2
3
4
5
$input = "";
if (isset($_POST['username'])) {
$input = $_POST['username'];
}
规则4:纵深防御
同时在处理表单的 PHP 代码中采用必要的措施。同样,即使使用 PHP regex 来确保 GET 变量完全是数字的,仍然可以采取措施确保 SQL 查询使用转义的用户输入。
这么一连串的措施就是纵深防御的思想
SQL防注入
常见的SQL注入漏洞就是$_GET[‘id’]获取用户id
1 |
|
如果$_GET[‘id’]得到的是一个字符串:’ or 1 = 1’,将会得到这样的sql1
SELECT * FROM users WHERE id = '' or 1 = 1
这样全部user都被查出来了
解决办法
- 使用mysql_real_escape_string来转义
1 |
|
第三个mysql_real_escape_string之所以能够防注入是因为mysql_escape_string本身并没办法 判断当前的编码,必须同时指定服务端的编码和客户端的编码,加上就能防编码问题的注入了。
虽然是可以一定程度上防止SQL注入,但是有还是被黑客绕过的风向。
- Prepare Statement机制
解决方案就是使用拥有Prepared Statement机制的PDO和MYSQLi来代替mysql_query(注:mysql_query自 PHP 5.5.0 起已废弃,并在将来会被移除):
PDO:1
2
3
4
5
6
7
8
9
10
11
$name = $POST['username'];
$pdo = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
$stmt->bindParam(1, $name, PDO::PARAM_STR); // 进行类型绑定
$stmt->execute();
foreach ($stmt as $row) {
// do something with $row
}
Mysqli:1
2
3
4
5
6
7
8
9
$name = $POST['username'];
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name); // 进行类型绑定
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
// do something with $row
}
$_GET变量攻击
如果想要获取用户ID:$_GET[‘id’],我们只希望id是一个[0-9]的数字。
- 使用is_numeric()
1 |
|
虽然有效限定了数字,但是一下几个值仍然有效:1
2
3
4100 (有效)
100.1 (不应该有小数位)
+0123.45e6 (科学计数法 —— 不好)
0xff33669f (十六进制 —— 危险!危险!)
更高效的方式就是使用正则匹配:
1 |
|
仅仅这样还不够,如果$_GET[‘id’]给你一个1000000000000000,超大的数字字符串,能够满足你的正则匹配,但是仍然有可能使你的代码可能产生溢出。
所以如果你的id最大是99999,那么你可以”纵深防御”,再加一个判断
1 |
|
strlen判断id是否超过5位数
跨站点脚本攻击
在跨站点脚本(XSS)攻击中,往往有一个恶意用户在表单中(或通过其他用户输入方式)输入信息,这些输入将恶 意的客户端标记插入过程或数据库中。例如,假设站点上有一个简单的来客登记簿程序,让访问者能够留下姓名、电子邮件地址和简短的消息。恶意用户可以利用这 个机会插入简短消息之外的东西,比如对于其他用户不合适的图片或将用户重定向到另一个站点的 Javascrīpt,或者窃取 cookie 信息。
幸运的是,PHP 提供了 strip_tags() 函数,这个函数可以清除任何包围在 HTML 标记中的内容
1 |
|
远程表单提交
首先可能考虑检查 $_SERVER[’HTTP_REFERER’],从而判断请求是否来自自己的服务器,这种方法可以挡住大多数恶意用户,但是挡不住最高明的黑客。这些人足够聪明,能够篡改头部中的引用者信息,使表单的远程副本看起来像是从您的服务器提交的。
处理远程表单提交更好的方式是,根据一个惟一的字符串或时间戳生成一个令牌,并将这个令牌放在会话变量和表单中。提交表单之后,检查两个令牌是否匹配。如果不匹配,就知道有人试图从表单的远程副本发送数据。