• PHP常见的SQL防注入方法


    利用Mysqli和PDO

    产生原因主要就是一些数据没有经过严格的验证,然后直接拼接 SQL 去查询。导致产生漏洞,比如:

    $id = $_GET['id'];
    $sql = "SELECT name FROM users WHERE id = $id";
    
    • 1
    • 2

    因为没有对 $_GET[‘id’] 做数据类型验证,注入者可提交任何类型的数据,比如 " and 1= 1 or " 等不安全的数据。如果按照下面方式写,就安全一些。

    $id = intval($_GET['id']);
    $sql = "SELECT name FROM users WHERE id = $id";
    
    • 1
    • 2

    把 id 转换成 int 类型,就可以去掉不安全的东西。

    验证数据

    防止注入的第一步就是验证数据,可以根据相应类型进行严格的验证。比如 int 类型直接通过 intval 进行转换就行:

    $id = intval($_GET['id']);
    
    • 1

    字符处理起来比较复杂些,首先通过 sprintf 函数格式化输出,确保它是一个字符串。然后通过一些安全函数去掉一些不合法的字符,比如:

    $str = addslashes(sprintf("%s",$str));
    
    • 1

    也可以用mysqli_real_escape_string 函数替代 addslashes 这样处理以后会比较安全。当然还可以进一步去判断字符串长度,去防止 [缓冲区溢出攻击] 比如:

    $str = addslashes(sprintf("%s",$str));
    $str = substr($str,0,40);	//最大长度为40
    
    • 1
    • 2

    参数化绑定

    参数化绑定,防止 SQL 注入的又一道屏障。php MySQLi 和 PDO 均能提供这样的功能。比如 MySQLi 可以这样去查询:

    $mysqli = new mysqli('localhost','my_user','my_password','world');
    $stmt = $mysqli->prepare("INSERT INTO CountryLanguage VALUES (?,?,?,?)");
    $code = 'DEU';
    $language = 'Bavarian';
    $official = "F";
    $percent = 11.2;
    $stmt->bind_param('sssd',$code,$language,$official,$percent);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    PDO 的更是方便,比如:

    /* Execute a prepared statement by passing an array of values */
    $sql = 'SELECT name,color,calories
    FROM fruit
    WHERE calories < :calories AND color = :colour';
    $sth = $dbh->prepare($sql,array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    $sth->execute(array(':calories' => 150, ':colour' => 'red'));
    $red = $sth->fetchAll();
    $sth->execute(array(':calories' => 175, ':colour' => 'yellow'));
    $yellow = $sth->fetchAll();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们多数使用 php 的框架进行编程,所以最好不要自己拼写 SQL,按照框架给定参数绑定进行查询。遇到较为复杂的 SQL 语句,一定要自己拼写的时候,一定要注意严格的判断。没有用 PDO 或者 MySQLi 也可以自己写个 prepared,比如 wordpress db 查询语句,可以看出也是经过严格的类型验证。

    function prepare( $query, $args ){
    	if(is_null($query))
    		return;
    	// This is not meant to be foolproof --	
    	// but it will catch obviously incorrect usage.
    	if(strpos($query,'%') === false){
    		_doing_it_wrong('wpdb::prepare',sprintf(__('The query argument of %s must have a placeholder.'),'wpdb::prepare()'),'3.9');
    	}
    	$args = fun_get_args();
    	array_shift($args);
    	// If args were passed as an array (as in vsprintf), move them up
    	if(isset($args[0]) && is_array($args[0]))
    		$args = $args[0];
    	$query = str_replace("'%s'",'%s',$query);
    		//in case someone mistakenly already singlequoted it
    	$query = str_replace('"%s"','%s',$query);
    		//doublequote unquoting
    	$query = preg_replace('|(?,'%F',$query);	
    		//Force floats to be locale unaware
    	$query = pref_replace('|(?,"'%s'",$query);
    		//quote the strings,avoiding escaped strings like %%s
    	array_walk($args,array($this,'escape_by_ref'));
    	return @ vsprintf($query,$args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    总结

    安全性很重要,也可以看出一个人基本功,项目漏洞百出,扩展性和可维护性再好也没有用。平时多留意,树立安全意识,养成一种习惯,一些基本的安全当然也不会占用 coding 的时间。

    养成这个习惯,即便在项目急,时间短的情况下,依然可以做的质量很高,不要等到自己以后负责的东西,数据库都被拿走了,造成损失才重视。

    虽然国内很多PHP程序员仍在依靠addslashes防止SQL注入,还是建议大家加强中文防止SQL注入的检查。addslashes的问题在于黑客可以用0xbf27来代替单引号,而addslashes只是将0xbf27修改为0xbf5c27,成为一个有效的多字节字符,其中的0xbf5c仍会被看作是单引号,所以addslashes无法成功拦截。

    当然addslashes也不是毫无用处,它是用于单字节字符串的处理,多字节字符还是用mysql_real_escape_string吧。

    另外对于php手册中get_magic_quotes_gpc的举例:

    if(!get_magic_quotes_gpc()){
    	$lastname = addslashes($_POST[&lsquo;lastname&rsquo;]);
    }else{
    	$lastname = $_POST[&lsquo;lastname&rsquo;];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    最好对magic_quotes_gpc已经开放的情况下,还是对$_POST[‘lastname’]进行检查一下。

    再说下mysql_real_escape_string和mysql_escape_string这2个函数的区别:
    mysql_real_escape_string 必须在(PHP >= 4.3.0,PHP5)的情况下才能使用。否则只能用mysql_escape_string,两者的区别是:mysql_real_escape_string 考虑到连接的当前字符集,而mysql_escape_string 不考虑。

    总结一下:

    addslashes() 是强行加;
    mysql_real_escape_string() 会判断字符集,但是对PHP版本有要求;
    mysql_escape_string不考虑连接的当前字符集。

    参考文章链接:
    https://mp.weixin.qq.com/s/X6q0xpbnJ9lojIIwNmDzdA

    补充:

    一:addslashes

    php addslashes函数的作用是在预定义的字符前面加上反斜杠,这些预定义字符包括:

    单引号(')
    双引号(")
    反斜杠(\)
    NULL

    $str="my name's wxp";
    echo addslashes($str);//输出my name\'s wxp
    
    • 1
    • 2

    然后在拼接mysql字符串:

    $sql="insert into student(student_name)values('".addslashes($str)."')";
    mysql_query($sql);
    
    • 1
    • 2

    此时字符串被插入到数据库,那么大家是否知道插入的字符串是带反斜杠还是不带反斜杠呢?恐怕很多人都会认为肯定是带反斜杠的字符串。其实这个答案是错误的,插入的字符串是没有带反斜杠。至于为什么插入的字符串在数据库中是没有加反斜杠,请大家继续看下面讲解。

    如果字符串$str="my name’s wxp"是使用POST和GET提交的数据,这个时候插入数据库中的数据是带反斜杠的,由此可知addslashes只是在POST和GET数据插入数据库时才会把反斜杠同时插入到数据库,其他情况下不会将反斜杠插入到数据库。

    注释:默认地,PHP 对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。所以您不应对已转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测

    参考:
    原文链接:https://blog.csdn.net/MengJing_/article/details/89353942

    二:mysql_real_escape_string

    mysql_real_escape_string
    (PHP 4 >= 4.3.0, PHP 5)

    mysql_real_escape_string — 将字符串中的特殊字符进行转义,以在 SQL 语句中使用

    警告 本扩展自 PHP 5.5.0 起已废弃,并在自 PHP 7.0.0 开始被移除。应使用 MySQLi 或 PDO_MySQL
    扩展来替换之。参见 MySQL:选择 API 指南来获取更多信息。用以替代本函数的有:
    mysqli_real_escape_string() PDO::quote()

    说明

    mysql_real_escape_string(string $unescaped_string, resource $link_identifier = NULL): string
    
    • 1

    考虑到连接的当前字符集,对 unescaped_string 进行特殊字符转义,以便安全地将其放置在 mysql_query() 中。如果要插入二进制数据,则必须使用此函数。

    mysql_real_escape_string() 调用 mysql 库的函数 mysql_real_escape_string, 在以下字符前添加反斜线:\x00、\n、\r、\、'、" 和 \x1a.

    在向 MySQL 发送查询之前,必须始终(有少数例外)使用此函数来确保数据的安全。

    警告
    安全: 默认字符集 The character set must be set either at the server level,or with the API function mysql_set_charset() for it to affect mysql_real_escape_string(). See the concepts section on character sets for more information.

    示例:

    
    // Connect
    $link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password')
        OR die(mysql_error());
    
    // Query
    $query = sprintf("SELECT * FROM users WHERE user='%s' AND password='%s'",
                mysql_real_escape_string($user),
                mysql_real_escape_string($password));
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    三:mysql_escape_string

    mysql_escape_string
    (PHP 4 >= 4.0.3, PHP 5)

    mysql_escape_string — 转义字符串用于 mysql_query

    警告 本函数自 PHP 4.3.0 起已废弃,并且它和整个 MySQL 扩展自 PHP 7.0.0 开始被移除。 可以选择出于活跃开发中的MySQLi 或 PDO_MySQL 扩展来作为替代。 参见 MySQL:选择 API 指南来获取更多信息。用以替代本函数的有:mysqli_escape_string() PDO::quote()

    说明

    mysql_escape_string(string $unescaped_string): string
    
    • 1

    本函数将转义 unescaped_string,使之可以安全用于 mysql_query()。此函数已弃用。

    本函数和 mysql_real_escape_string() 相同,除了 mysql_real_escape_string() 接受连接处理程序并根据当前字符集进行转义。mysql_escape_string() 不接受连接参数,也不遵循当前字符集设定。

    示例

    
    $item = "Zak's Laptop";
    $escaped_item = mysql_escape_string($item);
    printf("Escaped string: %s\n", $escaped_item);
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    以上示例会输出:

    Escaped string: Zak\'s Laptop
    
    • 1

    注意:
    mysql_escape_string() 不转义 % 和 _。

  • 相关阅读:
    Lazarus网络编程
    E中国集装箱涂料行业竞争态势及投资盈利预测报告2022-2028年
    酷开科技依托酷开系统推动家庭智能化加速发展
    李沐机器学习环境配置相关
    Day721. 外部函数接口 -Java8后最重要新特性
    bpmn+vue 中文文档
    物理引擎介绍
    乙二醇除铁离子
    k8s 安全机制详解
    Mybatis 批量修改数据,,并判断非空数据插入
  • 原文地址:https://blog.csdn.net/weixin_43741253/article/details/133169038