php防止sql注入

yuyu888 于 2020-12-22 发布

现在很多框架都自带防sql注入的方法,并且封装的很好,使用着不必关心,但是需要依赖框架本身的约束,当需要做复杂查询的时候往往开发者更倾向于使用原生的sql 这会就需要开发者自行处理, 具体我们可以使用哪些手段来处理呢?

addslashes()

addslashes转义的字符是单引号(’)、双引号(”)、反斜线(\)与NUL(NULL 字符)

在防注入方面,addslashes()可以防止掉大多数的注入,但是此函数并不会检查变量的编码,当使用例如中文gbk的时候,由于长度比较长 ,会将某些gbk编码解释成两个ascii编码,造成新的注入风险(俗称宽字节注入

如果从网页表单、php、mysql都使用utf8编码,则没有这个问题。

mysql_real_escape_string()

与addslashes()相比,不仅会将’ “ NOL(ascii的0)转义,还会把r n进行转义。同时会检测数据编码。 按php官方的描述,此函数可以安全的用于mysql。

此函数在使用时会使用于数据库连接(因为要检测字符集),并根据不同的字符集做不同的操作。如果当前连接不存在,刚会使用上一次的连接。

注意:mysql_real_escape_string必须是连接数据库之后才能使用

mysql_real_escape_string()防注入详解

此方法在php5.5后不被建议使用,在php7中废除

预处理查询 (Prepared Statements)

使用prepared statements(预处理语句)和参数化的查询,可以有效的防止sql注入。

为什么预处理和参数化查询可以防止sql注入呢? 在传统的写法中,sql查询语句在程序中拼接,防注入(加斜杠)是在php中处理的,然后就发语句发送到mysql中,mysql其实没有太好的办法对传进来的语句判断哪些是正常的,哪些是恶意的,所以直接查询的方法都有被注入的风险。 在mysql5.1后,提供了类似于jdbc的预处理-参数化查询。它的查询方法是:

先预发送一个sql模板过去 再向mysql发送需要查询的参数 就好像填空题一样,不管参数怎么注入,mysql都能知道这是变量,不会做语义解析,起到防注入的效果,这是在mysql中完成的。

参考资料:

https://www.cnblogs.com/damugua/p/12905804.html

使用mysqli:prepare()实现

$sql = "select * from tmp where myname=? or sex =?";
$stmt = $mysqli->conn->prepare($sql);

$name ="a";$sex="b";
$stmt->bind_param('ss',$name,$sex);//必须要这样传参,且在mysqli等的预处理参数绑定中,必须要指定参数的类型且只能一次性绑定全部参数,不能像PDO那样一个个绑定

//$stmt->bind_param('ss',"a","b");//这种方式的会报错:Fatal error: Cannot pass parameter 2 by reference
// 该函数绑定了 SQL 的参数,且告诉数据库参数的值。 "ss" 参数列处理其余参数的数据类型。s 字符告诉数据库该参数为字符串。
// 参数有以下四种类型:
// i - integer(整型)
// d - double(双精度浮点型)
// s - string(字符串)
// b - BLOB(布尔值)
// 每个参数都需要指定类型。

$stmt->execute();
if($mysqli->conn->affected_rows){
    $result = $stmt->get_result();
    while($row = $result->fetch_assoc()){
        var_dump($row);
    }
}

使用pdo实现

参考资料:

pdo调用方法以及防sql注入原理

宽字节注入

宽字符是指多个字节宽度的编码,如UNICODE、GBK、BIG5等。转义函数在对这些编码进行转义时会丢掉转义字符“\”(将它转换为%5C),你可以在它前面再输入一个单字符编码与它组成一个新的多字符编码,使得转义实际上没有发生作用(转义就是要留下“\”防范注入的)。

举个例子:

输入……php?name=a%df’ or 1=1;%20%23 转义函数会将%df’改成%df\’,如果你的编码是GBK,%df\’就对应着%df%5c’,在GBK中,这两个字节%df%5c又对应着一个汉字“運”,也就是说\已经失去了作用,a%df’被认为是a運’,成功消除了转义函数的影响。

总结一下,可以进行宽字节注入的条件有两个:

举一个典型的宽字节注入例子:

<?php  
$name=$_GET['name'];
$name=addslashes($name);
$conn = mysqli_connect('localhost', 'root', 'root', 'test');//连接MySQL服务
if (!$conn) {
    die('Could not connect to MySQL: ' . mysqli_connect_error()); 
} 
$conn->set_charset("GBK");
@mysql_select_db("test",$conn);
$sql="select * from user where username='".$name."'";
$result=mysqli_query($conn,$sql);
@$row = mysqli_fetch_assoc($result);
echo "Hello ".$row['username']."</br>";
echo "Your password is:".$row['password']."</br>";
?>

若将这段代码保存为1.php,数据库如图设置:

数据库信息

则注入语句:

..php?name=a%df’ or 1=1; %20%23

得到结果:

结果

自定义方法实现

其实如果明确知道变量类型,可以做下强类型校验/转换, 比如使用intval()强制转换, Bool类型只接受true,false;枚举类型用in_array()强校验,这样既可以有效防止xss 也可以防止sql注入

附一段自定义函数,根据需要修改

class Tools
{
    /*  防sql注入,xss攻击  */
    public static function StrClean($str)
    {
        $str=trim($str);
        $str=strip_tags($str);
        $str=stripslashes($str);
        $str=addslashes($str);
        $str=rawurldecode($str);
        $str=quotemeta($str);
        $str=htmlspecialchars($str);
        //去除特殊字符
        $str=preg_replace("/\/|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\{|\}|\:|\<|\>|\?|\[|\]|\,|\.|\/|\;|\'|\`|\-|\=|\\\|\|/", "" , $str);
        $str=preg_replace("/\s/", "", $str);//去除空格、换行符、制表符
        return $str;
    }

    //防止xss攻击
    public static function FilterWords($str)
    {
        $farr = array(
            "/<(\\/?)(script|i?frame|style|html|body|title|link|meta|object|\\?|\\%)([^>]*?)>/isU",
            "/(<[^>]*)on[a-zA-Z]+\s*=([^>]*>)/isU",
            "/select|insert|update|delete|drop|\'|\/\*|\*|\+|\-|\"|\.\.\/|\.\/|union|into|load_file|outfile|dump/is"
        );
        $str = preg_replace($farr,'',$str);
        return $str;
    }
}