防止全局变量注入
问题
你在使用一个老版本的PHP,希望访问表单输入变量,但不允许恶意用户在你的程序中设置任意的全局变量。
解决方案
最容易的解决方案是使用PHP5.4.0或以后版本。从这个版本开始,去除了register_globals配置指令,正是这个配置指令导致了全局变量注入问题。
如果使用较早版本的PHP,可以禁用register_globals配置指令,只允许从$_GET、$_POST和$_CO0KIE数组访问变量,从而能准确地知道变量来自哪里。
为此,确保php.ini文件中有register_globals=0ff。如果不允许写php.ini文件,而且php.ini文件已经将register_globals打开,就需要与你的系统管理员好好谈一谈,或者找一个不依赖于十年前错误设置的新的托管提供商。如果使用PHP和Apache,而且Apache配置为使用各个目录的,htaccess文件,可以在.htaccess文件中增加php_flagregister_globals off来关闭register_globals。
register_globals设置为on时,外部变量(包括来自表单和cookie的变量)将直接导入到全局命名空间。这很方便,不过如果你不够仔细,没有检查变量以及它们在哪里定义,这就会为安全漏洞大开方便之门。为什么?因为可能你原本打算在内部使用一个变量,并不允许从外部访问,但是register_globals设置为on时,就可以从外部重写这个变量的值,而你对此全然不知。
简单的示例:假设有一个页面,用户要在这个页面中输入一个用户名和密码。如果用户名和密码得到验证,则返回用户的ID号,并使用这个ID号查找和输出他的个人信息。
不安全的register_globals代码
// 使用 $dbh->quote() 方法对用户输入的用户名和密码进行引用处理,
// 意图是防止SQL注入。然而,这种方法并不安全,因为它被用在了字符串拼接中。
// 在SQL查询中直接拼接用户输入,即使使用了 quote(),仍然容易受到SQL注入攻击。
// 正确的做法是使用预处理语句(prepared statements)来绑定用户输入。
$username = $dbh->quote($_GET['username']);
$password = $dbh->quote($_GET['password']);
// 使用 $dbh->query() 方法执行一个SQL查询,查询用户的ID。
// 由于直接拼接了用户输入,这个查询存在SQL注入的风险。
$sth = $dbh->query("SELECT id FROM users WHERE username = $username AND password = $password");
// 检查查询结果是否返回了一行数据(即检查用户名和密码是否匹配)。
// 这里假设 numRows() 方法返回查询结果的行数。
// 如果用户名和密码正确,应该返回1行;否则返回0行。
if(1 == $sth->numRows()){
// 如果用户名和密码正确,从结果集中获取一行数据,并将其转换为对象。
// fetchRow(DB_FETCHMODE_OBJECT) 方法用于获取一行数据,并返回一个对象。
// 注意:DB_FETCHMODE_OBJECT 是一个常量,用于指定获取数据的模式为对象。
// 然而,这个常量可能不是所有数据库抽象层都支持的,具体取决于 $dbh 的实现。
$row = $sth->fetchRow(DB_FETCHMODE_OBJECT);
// 从对象中获取用户的ID。
$id = $row->id;
}else{
// 如果用户名或密码错误,打印错误信息。
print "Bad username and password";
}
// 检查变量 $id 是否不为空。
// 如果 $id 不为空,意味着用户名和密码验证成功,可以进一步查询用户资料。
if(!empty($id)){
// 执行另一个查询来获取用户的详细信息。
// 同样,这里也存在SQL注入的风险,因为 $id 直接被拼接到了SQL字符串中。
// 但是,由于 $id 是在前面的查询中从数据库中获取的,并且假设数据库中的ID是安全的,
// 因此这里的SQL注入风险相对较低(但仍然不是最佳实践)。
// 更好的做法是使用预处理语句来绑定 $id。
$sth = $dbh->query("SELECT * FROM profile WHERE id = $id");
// 注意:这里缺少了处理 $sth 查询结果的代码。
// 通常,你会使用类似 $sth->fetch() 或 $sth->fetchAll() 的方法来获取查询结果。
}
正常情况下,只能由你的程序设置$id,而且要通过一个合法的数据库查找得到。不过,如果有人修改了查询字符串,并为$id传入一个值,你就会遇到问题。如果启用了register_globals,这个脚本还可以执行第二个数据库查询,即使用户名和密码查找失败也能返回结果。
如果未启用register_globals,$id仍是未设置的,因为只设置了$_REQUEST['id']和$_GET['id']。
避免register_globals问题
$username = $dbh->quote($_GET['username']);
$password = $dbh->quote($_GET['password']);
$sth = $dbh->query("SELECT id FROM users WHERE username = $username AND password = $password");
if(1 == $sth->numRows()){
$row = $sth->fetchRow(DB_FETCHMODE_OBJECT);
$id = $row->id;
if(!empty($id)){
$sth = $dbh->query("SELECT * FROM profile WHERE id = $id");
}
}else{
print "Bad username and password";
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战