内容简介:代码审计的时候经常会遇到种类繁杂的转义函数,最可怕的是他们长的都很像,还是拿出来总结一下吧。返回字符串,该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符是单引号(’)、双引号(”)、反斜线(\)与 NUL(NULL 字符)。一个使用 addslashes() 的例子是当你要往数据库中输入数据时。 例如,将名字 O’reilly 插入到数据库中,这就需要对其进行转义。 强烈建议使用 DBMS 指定的转义函数 (比如 MySQL 是 mysqli_real_escape_string(),P
代码审计的时候经常会遇到种类繁杂的转义函数,最可怕的是他们长的都很像,还是拿出来总结一下吧。
0X01 addslashes() –>(PHP 4, PHP 5, PHP 7)
用法:
string addslashes ( string $str )
返回值:
返回字符串,该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符是单引号(’)、双引号(”)、反斜线(\)与 NUL(NULL 字符)。
一个使用 addslashes() 的例子是当你要往数据库中输入数据时。 例如,将名字 O’reilly 插入到数据库中,这就需要对其进行转义。 强烈建议使用 DBMS 指定的转义函数 (比如 MySQL 是 mysqli_real_escape_string(),PostgreSQL 是 pg_escape_string()),但是如果你使用的 DBMS 没有一个转义函数,并且使用 \ 来转义特殊字符,你可以使用这个函数。 仅仅是为了获取插入数据库的数据,额外的 \ 并不会插入 。 当 PHP 指令 magic_quotes_sybase 被设置成 on 时,意味着插入 ‘ 时将使用 ‘ 进行转义。
PHP 5.4 之前 PHP 指令 magic_quotes_gpc 默认是 on, 实际上所有的 GET、POST 和 COOKIE 数据都用被 addslashes() 了。 不要对已经被 magic_quotes_gpc 转义过的字符串使用 addslashes(),因为这样会导致双层转义。 遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。
代码示例:
<?php $str = "Is your name O'reilly?"; // 输出: Is your name O\'reilly? echo addslashes($str); ?>
0X02 stripslashes() –>(PHP 4, PHP 5, PHP 7)
用法:
string stripslashes ( string $str )
反引用一个引用字符串,如果 magic_quotes_sybase 项开启,反斜线将被去除,但是两个反斜线将会被替换成一个。
返回值:
返回一个去除转义反斜线后的字符串(\’ 转换为 ‘ 等等)。双反斜线(\)被转换为单个反斜线(\)。
代码示例:
<?php function stripslashes_deep($value) { $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value); return $value; } // 范例 $array = array("f\\'oo", "b\\'ar", array("fo\\'o", "b\\'ar")); $array = stripslashes_deep($array); // 输出 print_r($array); ?>
结果:
Array ( [0] => f'oo [1] => b'ar [2] => Array ( [0] => fo'o [1] => b'ar ) )
0X03 addcslashes() –>(PHP 4, PHP 5, PHP 7)
用法:
string addcslashes ( string $str , string $charlist )
返回值:
返回字符串,该字符串在属于参数 charlist 列表中的字符前都加上了反斜线。
示例代码:
这段代码就是告诉我们要注意选取的字符的范围,大写字母和小写字母中间还有一些可见字符,另外起始字符的ascII 码要小于结束符的,否则达不到预期的效果,只能是转义这个几个列出来的
<?php echo addcslashes('foo[ ]', 'A..z'); // 输出:\f\o\o\[ \] // 所有大小写字母均被转义 // ... 但 [\]^_` 以及分隔符、换行符、回车符等也一并被转义了。 ?>
注意:当选择对字符 0,a,b,f,n,r,t 和 v 进行转义时需要小心,它们将被转换成 \0,\a,\b,\f,\n,\r,\t 和 \v。在 PHP 中,只有 \0(NULL),\r(回车符),\n(换行符)和
\t(制表符)是预定义的转义序列, 而在 C 语言中,上述的所有转换后的字符都是预定义的转义序列。
0X04 stripcslashes() –>(PHP 4, PHP 5, PHP 7)
用法:
string stripcslashes ( string $str )
返回值:
返回反转义后的字符串。可识别类似 C 语言的 \n,\r,… 八进制以及十六进制的描述。
示例代码:
stripcslashes('He\xallo') == 'He'."\n".'llo' stripcslashes('H\xaello') == 'H'.chr(0xAE).'llo'
0X05 mysql_escape_string() –>(PHP 4 >= 4.0.3, PHP 5)
用法:
string mysql_escape_string ( string $unescaped_string )
mysql_escape string() 并不转义 % 和 。 本函数和 mysql_real_escape_string() 完全一样,除了 mysql_real_escape_string() 接受的是一个连接句柄并根据当前字符集转移字符串之外。mysql_escape_string() 并不接受连接参数,也不管当前字符集设定。
示例代码:
<?php $item = "Zak's Laptop"; $escaped_item = mysql_escape_string($item); printf ("Escaped string: %s\n", $escaped_item); ?>
结果:
Escaped string: Zak\'s Laptop
0X06 mysql_real_escape_string() –>(PHP 4 >= 4.3.0, PHP 5)
用法:
string mysql_real_escape_string ( string $unescaped_string [, resource $link_identifier = NULL ] )
本函数将 unescaped_string 中的特殊字符转义,并计及连接的当前字符集,因此可以安全用于 mysql_query()。
mysql_real_escape_string() 调用mysql库的函数 mysql_real_escape_string, 在以下字符前添加反斜杠: \x00 \n \r \ ' " \x1a.
为了安全起见,在像MySQL传送查询前,必须调用这个函数(除了少数例外情况)。
注意:本扩展自 PHP 5.5.0 起已废弃,并在自 PHP 7.0.0 开始被移除。应使用 MySQLi 或 PDO_MySQL 扩展来替换之。
0X07 PHP 魔术引号 –> (< PHP 5.4)
1.什么是魔术引号
当打开时,所有的 ‘(单引号),”(双引号),\(反斜线)和 NULL 字符都会被自动加上一个反斜线进行转义。这和 addslashes() 作用完全相同。
一共有三个魔术引号指令:
(1)magic_quotes_gpc影响到 HTTP 请求数据(GET,POST 和 COOKIE)。不能在运行时改变。在 PHP 中默认值为 on。
代码示例:
<?php // 如果启用了魔术引号 echo $_POST['lastname']; // O\'reilly echo addslashes($_POST['lastname']); // O\\\'reilly // 适用各个 PHP 版本的用法 if (get_magic_quotes_gpc()) { $lastname = stripslashes($_POST['lastname']); } else { $lastname = $_POST['lastname']; } // 如果使用 MySQL $lastname = mysql_real_escape_string($lastname); echo $lastname; // O\'reilly $sql = "INSERT INTO lastnames (lastname) VALUES ('$lastname')"; ?>
(2)magic_quotes_runtime如果打开的话,大部份从外部来源取得数据并返回的函数,包括从数据库和文本文件,所返回的数据都会被反斜线转义。该选项可在运行的时改变,在 PHP 中的默认值为 off。
代码示例:
<?php // 创建临时文件指针 $fp = tmpfile(); // 写入一些数据 fwrite($fp, '\'PHP\' is a Recursive acronym'); // 没有 magic_quotes_runtime rewind($fp); set_magic_quotes_runtime(false); echo 'Without magic_quotes_runtime: ' . fread($fp, 64), PHP_EOL; // 有 magic_quotes_runtime rewind($fp); set_magic_quotes_runtime(true); echo 'With magic_quotes_runtime: ' . fread($fp, 64), PHP_EOL; // 清理 fclose($fp); ?>
magic_quotes_gpc与magic_quotes_runtime的区别
1.magic_quotes_runtime是对外部引入的数据库资料或者文件中的特殊字符进行转义,而magic_quotes_gpc是对post、get、cookie等数组传递过来的数据进行特殊字符转义。
2.他们都有相应的get函数,可以对php环境中是否设置了他们相应功能特性进行探测,如:get_magic_quotes_gpc,是对magic_quotes_gpc是否设置的探测,get_magic_quotes_runtime,是对magic_quotes_runtime是否设置的探测,而且都是如果设置了,get函数返回1,如果没有设置,get函数返回0。
3.不能在程序里面设置magic_quotes_gpc的值,原因是php中并没有set_magic_quotes_gpc这个函数,而magic_quotes_runtime有对应的能在代码中直接设置magic_quotes_runtime值的函数:set_magic_quotes_runtime,所以,magic_quotes_gpc的值,只能自己手动在php.ini文件里面设置了。
(3)magic_quotes_sybase
如果该选项在php.ini文件中是唯一开启的话,将只会转义%00为\0(即null字符)。此选项会完全覆盖magic_quotes_gpc。如果同时开启这两个选项的话,单引号将会被转义成两个单引号,%00会被转义为\0。而双引号、反斜线将不会进行转义
1.设置:magic_quotes_sybase = On & magic_quotes_gpc = Off
输入:
1'2"3\4%005
结果:
1'2"3\45
结论:
只将%00(即null字符)过滤了
2.设置:magic_quotes_sybase = On & magic_quotes_gpc = On
输入:
1'2”3\4%005
结果:
1''2"3\4\05
结论:
magic_quotes_sybase = On & magic_quotes_gpc = On时,magic_quotes_sybase将会使用单引号对单引号进行转义,%00(即null字符)也会被转义。
2.为什么存在魔术引号
没有理由再使用魔术引号,因为它不再是 PHP 支持的一部分。不过它帮助了新手在不知不觉中写出了更好(更安全)的代码。但是在处理代码的时候,最好是更改你的代码而不是依赖于魔术引号的开启。 为什么这个功能存在?是为了阻止 SQL 注入。在今天,开发者能够更好得意识到了安全问题,并最终使用数据库转移机制或者 prepared语句来取代魔术引号功能。
3.为什么不用魔术引号
(1)可移植性
编程时认为其打开或并闭都会影响到移植性。可以用 get_magic_quotes_gpc() 来检查是否打开,并据此编程。
(2)性能
由于并不是每一段被转义的数据都要插入数据库的,如果所有进入 PHP 的数据都被转义的话,那么会对程序的执行效率产生一定的影响。在运行时调用转义函数(如 addslashes())更有效率。 尽管 php.ini-dist 默认打开了这个选项,但是 php.ini-recommended 默认却关闭了它,主要是出于性能的考虑。
(3)不便
由于不是所有数据都需要转义,在不需要转义的地方看到转义的数据就很烦。比如说通过表单发送邮件,结果看到一大堆的 \’。针对这个问题,可以使用 stripslashes() 函数处理。
0X08 mysqli_real_escape_string/mysqli_escape_string –> (PHP >= 5 ,PHP 7)
此函数用来对字符串中的特殊字符进行转义, 以使得这个字符串是一个合法的 SQL 语句。传入的字符串会根据当前连接的字符集进行转义,得到一个编码后的合法的 SQL 语句。 mysqli_escape_string 是 mysqli_real_escape_string 的别名。
用法:
mysqli_real_escape_string(connection,escapestring);
参数解释:
connection 必需。规定要使用的 MySQL 连接。
escapestring 必需。要转义的字符串。编码的字符是 NUL(ASCII 0)、\n、\r、\、’、” 和 Control-Z。
返回值:
返回已转义的字符串。
注意:
1.调用 mysqli_real_escape_string() 函数之前, 必须先通过调用 mysqli_set_charset() 函数或者在 MySQL 服务器端设置字符集
2.mysqli_character_set_name() 返回当前数据库连接的默认字符编码
0X09 prepare 预编译
通过使用 预编译语句 (prepared statements)和 参数化查询 (parameterized queries)。这些sql语句从参数,分开的发送到数据库服务端,进行解析。这样黑客不可能插入恶意sql代码。
对应的就是下面这两种方法:
1.使用PDO对象(对于任何数据库驱动都好用)
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute(array('name' => $name)); foreach ($stmt as $row) { // do something with $row }
2. 使用MySqli
$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 }
正确地建立连接:
注意:当使用PDO去连接Mysql数据库时,真正的预处理默认并没有开启。为了开启他,你应该关闭模拟的预处理语句,以下是一个例子:
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass'); $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
在上面的例子里,错误模式严格意义上来说没有必要,但推荐你加上去。这样,脚本在遇到致命错误(Fatal Error)的时候并不会停止运行。并且给开发者去捕获(catch )那些PDOException异常。
第一个setAttribute()是必须的。这告诉PDO去关闭模拟预处理,然后使用真正的预处理语句。这将保证语句和值在被交到Mysql服务器上没有被解析(让攻击者没有机会去进行sql注入。)
尽管你可以在构造函数里设置字符集(charset ),但你也要注意旧版本的PHP(<5.3.6)会忽略在DSN中设置的字符集参数。
解释
到底发生了什么呢? 你的SQL语句交给prepare 之后被数据库服务器解析和编译了 。通过制定参数(不管是“?”还是命名占位符:name),你都可以告诉数据库引擎哪里你想过滤掉。 然后当你执行execute方法时,预处理语句会把你所指定的参数值结合起来。
这里很重要的就是参数值和编译过的语句绑定在了一起,而不是简简单单的SQL字符串、SQL注入通过骗起脚本加入一些恶意的字符串,在建立sql发送到数据库的时候产生后果。 所以,通过分离的从参数中发送真正的sql语句,你控制了风险 :在结尾的时候你不打算干的一些事。(译者注:请看开篇的例子)。当你使用预编译的时候,任何参数都会被当作字符串。在这个例子里,如果$name变量包含了’Sarah’; DELETE FROM employees 这个结果只会简单的搜索字符串“‘Sarah’; DELETE FROM employees”,所以你不会得到一张空表。
另外一个使用预编译的好处就是,如果你在同一个会话中执行一个statement多次,只会被解析和编译一次,对速度更友好。
哦,既然你问了增加语句的时候怎么使用,下面给你个例子:
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute(array('column' => $unsafeValue));
PDO如何解决sql注入
完整代码:
<?php $pdo = new PDO("mysql:host=192.168.0.1;dbname=test;charset=utf8","root"); $st = $pdo->prepare("select * from info where id =? and name = ?"); $id = 21; $name = 'zhangsan'; $st->bindParam(1,$id); $st->bindParam(2,$name); $st->execute(); $st->fetchAll(); ?>
在php5.3.6之后,pdo不会在本地对sql进行拼接然后将拼接后的sql传递给mysql server处理(也就是不会在本地做转义处理)。 pdo的处理方法是在prepare函数调用时,将预处理好的sql模板(包含占位符)通过mysql协议传递给mysql server,告诉mysql server模板的结构以及语义。 当调用execute时,将两个参数传递给mysql server。由mysql server完成变量的转移处理。将sql模板和变量分两次传递,即解决了sql注入问题。
0X10 补充:使用了PDO就一定安全了吗???
建议去看一下PDO 的官方文档,文章中有这样一句话:
(however,
if other portions of the query are being built up with unescaped
input, SQL injection is still possible).
翻译过来就是
开发人员可以确保不会发生SQL注入(然而,如果查询的其他部分是用未转义输入构建的,那么SQL注入就仍然可能)。
因为有些查询语句并不适合使用PDO 进行处理,可能使用PDO处理比较困困难,于是就有一些没有做处理,还有就是有些挂羊头卖狗肉(估计开发的也不懂PDO),真正用的时候还是老方法,再有就是开发人员对PDO本地预处理的错误开放,以及一些编码问题的处理上可能还是存在问题。
当然这是面试经常问的问题,请看这三篇文章,虽然有点老,但是我认为对原理的理解还是很有帮助的。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Wireshark网络分析就这么简单
林沛满 / 人民邮电出版社 / 2014-11-6 / 39.00
Wireshark可能是世界上最好的开源网络包分析器,能在多种平台上(比如Windows、Linux和Mac)抓取和分析网络包,在IT业界有着广泛的应用。 《Wireshark网络分析就这么简单》采用诙谐风趣的手法,由浅入深地用Wireshark分析了常见的网络协议,读者在学习Wireshark的同时,也会在不知不觉中理解这些协议。作者还通过身边发生的一些真实案例,分享了Wireshark的......一起来看看 《Wireshark网络分析就这么简单》 这本书的介绍吧!