一、 建立一个安全抽象层
我们并不建议你手工地把前面先容的技巧利用于每一个用户输进的实例中,而是强烈推荐你为此创立一个抽象层。一个简略的抽象是把你的校验计划参加到一个函数中,并且针对用户输进的每一项调用这个函数。当然,我们还可以创立一种更复杂的更高一级的抽象-把一个安全的查询封装到一个类中,从而利用于全部利用程序。在网上已经存在很多这种现成的免费的类;在本篇中,我们正要讨论其中的一些。
进行这种抽象至少存在三个长处(而且每一个都会改良安全级别):
1. 本地化代码。
2. 使查询的结构更快且更为可靠-由于这可以把部分工作交由抽象代码来实现。
3. 当基于安全特点进行构建并且适当应用时,这将会有效地防止我们前面所讨论的各种各样的注进式攻击。
二、 改良现有的利用程序
假如你想改良一个现有的利用程序,则应用一个简略的抽象层是最适当的。一个能够简略地'清算'你所收集的任何用户输进内容的函数可能看起来如下所示:
function safe( $string ) {
return ''' . mysql_real_escape_string( $string ) . '''
}
【留心】我们已经构建了相应于值请求的单引号以及mysql_real_escape_string()函数。接下来,就可以应用这个函数来结构一个$query变量,如下所示:
$variety = safe( $_POST['variety'] );
$query = ' SELECT * FROM wines WHERE variety=' . $variety;
现在,你的用户试图进行一个注进式攻击-通过输进下列内容作为变量$variety的值:
lagrein' or 1=1;
留心,假如不进行上面的'清算',则最后的查询将如下所示(这将导致无法预感的成果):
SELECT * FROM wines WHERE variety = 'lagrein' or 1=1;'
然而现在,既然用户的输进已经被清算,那么查询语句就成为下面这样一种无迫害的情势:
SELECT * FROM wines WHERE variety = 'lagrein\' or 1=1\;'
既然数据库中不存在与指定的值相应的variety域(这正是恶意用户所输进的内容-lagrein' or 1=1;),那么,这个查询将不能返回任何成果,并且注进将会失败。
三、 保护一个新的利用程序
假如你正在创立一个新的利用程序,那么,你可以从头开端创立一个安全抽象层。如今,PHP 5新改良的对于MySQL的支撑(这重要体现在新的mysqli扩大中)为这种安全特点供给了强有力的支撑(既有过程性的,也有面向对象特点的)。你可以从站点http://php.net/mysqli上获取有关mysqli的信息。留心,只有当你应用--with-mysqli=path/to/mysql_config选项编译PHP时,这种mysqli支撑才可用。下面是该代码的一个过程性版本,用于保护一个基于mysqli的查询:
<?php
//检索用户的输进
$animalName = $_POST['animalName'];
//连接到数据库
$connect = mysqli_connect( 'localhost', 'username', 'password', 'database' );
if ( !$connect ) exit( 'connection failed: ' . mysqli_connect_error() );
//创立一个查询语句源
$stmt = mysqli_prepare( $connect,'SELECT intelligence FROM animals WHERE name = ?' );
if ( $stmt ) {
//把调换绑定到语句上
mysqli_stmt_bind_param( $stmt, 's', $animalName );
//履行该语句
mysqli_stmt_execute( $stmt );
//检索成果...
mysqli_stmt_bind_result( $stmt, $intelligence );
// ...并显示它
if ( mysqli_stmt_fetch( $stmt ) ) {
print 'A $animalName has $intelligence intelligence.\n';
} else {
print 'Sorry, no records found.';
}
//清除语句源
mysqli_stmt_close( $stmt );
}
mysqli_close( $connect );
?>
该mysqli扩大供给了一组函数用于结构和履行查询。而且,它也非常准确地供给了前面应用我们自己的safe()函数所实现的功效。
在上面的片段中,首先收集用户提交的输进内容并建立数据库连接。然后,应用mysqli_prepare()函数创立一个查询语句源-在此命名为$stmt以反应应用它的函数的名称。这个函数应用了两个参数:连接资源和一个字符串(每当你应用扩大插进一个值时,'?'标记被插进到其中)。在本例中,你仅有一个这样的值-动物的名字。
留心,在一个SELECT语句中,放置'?'标记的唯一的有效地位是在值比拟部分。这正是为什么你不需要指定应用哪个变量的原因(除了在mysqli_stmt_bind_param()函数中之外)。在此,你还需要指定它的类型-在本例中,'s'代表字符串。其它可能的类型有:'I'代表整数,'d'代表双精度数(或浮点数),而'b'代表二进制字符串。
函数mysqli_stmt_execute(),mysqli_stmt_bind_result()和mysqli_stmt_fetch()负责履行查询并检索成果。假如存在检索成果,则显示它们;假如不存在成果,则显示一条无害的消息。最后,你需要封闭$stmt资源以及数据库连接-从内存中对它们加以开释。
假定一个正当的用户输进了字符串'lemming',那么这个例程将(假定是数据库中适当的数据)输出消息'A lemming has very low intelligence.'。假定存在一个尝试性注进-例如'lemming' or 1=1;',那么这个例程将打印(无害)消息'Sorry, no records found.'。
此外,mysqli扩大还供给了一个面向对象版本的雷同的例程。下面,我们想阐明这种版本的应用方法。
<?php
$animalName = $_POST['animalName'];
$mysqli = new mysqli( 'localhost', 'username', 'password', 'database');
if ( !$mysqli ) exit( 'connection failed: ' . mysqli_connect_error() );
$stmt = $mysqli->prepare( 'SELECT intelligence
FROM animals WHERE name = ?' );
if ( $stmt ) {
$stmt->bind_param( 's', $animalName );
$stmt->execute();
$stmt->bind_result( $intelligence );
if ( $stmt->fetch() ) {
print 'A $animalName has $intelligence intelligence.\n';
} else {
print 'Sorry, no records found.';
}
$stmt->close();
}
$mysqli->close();
?>
实际上,这部分代码是前面描写代码的复制-它应用了一种面向对象的语法和组织方法,而不是严格的过程式代码。
四、 更高级的抽象
假如你应用外部库PearDB,那么,你可以对利用程序的安全保护模块进行全面的抽象。
另一方面,应用这个库存在一个突出的毛病:你只能受限于某些人的思想,而且代码治理方面也添加了大批的工作。为此,在决定是否应用它们之前,你需要进行仔细地考虑。假如你决定这样做,那么,你至少确保它们能够真正帮助你'清算'你的用户输进的内容。
五、 测试你的注进式保护才能
正如我们在前面所讨论的,确保你的脚本安全的一个重要的部分是对它们进行测试。为此,最好的措施是你自己创立SQL代码注进测试。
在此,我们供给了一个这种测试的示例。在本例中,我们测试对一个SELECT语句的注进式攻击。
<?php
//被测试的保护函数
function safe( $string ) {
return ''' . mysql_real_escape_string( $string ) . '''
}
//连接到数据库
///////////////////////
//试图进行注进
///////////////////////
$exploit = 'lemming' AND 1=1;';
//进行清算
$safe = safe( $exploit );
$query = 'SELECT * FROM animals WHERE name = $safe';
$result = mysql_query( $query );
//测试是否保护是足够的
if ( $result && mysql_num_rows( $result ) == 1 ) {
exitt 'Protection succeeded:\n
exploit $exploit was neutralized.';
}
else {
exit( 'Protection failed:\n
exploit $exploit was able to retrieve all rows.' );
}
?>
假如你想创立这样的一个测试集,并实验基于不同的SQL命令的各种不同的注进,那么,你将会很快地探测出你的保护策略中的任何漏洞。一旦改正这些标题,那么,你就可以很有把握-你已经建立起真正的注进式攻击保护机制。
六、 小结
在本系列文章一开端,我们通过一个SQL注进讨论分析了对你的脚本的特定要挟-由不适当的用户输进所致。之后,我们描写了SQL注进的工作原理并准确地分析了PHP是怎样易于被注进的。然后,我们供给了一个实际中的注进示例。之后,我们推荐一系列措施来使试图的注进攻击变为无害的-这将分辨通过确保使所有提交的值以引号封闭,通过检查用户提交值的类型,以及通过过滤掉你的用户输进的埋伏危险的字符等方法来实现的。最后,我们推荐,你最好对你的校验例程进行抽象,并针对更改一个现有利用程序供给了脚本示例。然后,我们讨论了第三方抽象计划的优毛病。
以上就是在PHP中全面禁止SQL注进式攻击之三的内容,更多相关内容请关注PHP中文网(www.php.cn)!