SQL注入攻击与防护实例
- 1.1
如下是一段普普统统的登陆演示代码,该脚本须要username和password两个参数,该脚本中sql语句没有任何过滤,注入起来很是容易,后续部分将逐步增强代码的防注入功能。php
<?php include 'config.php'; $username = $_POST['username']; $password = $_POST['password']; if(!empty($username) && !empty($password)) { $conn = new mysqli($db_server,$db_user,$db_pass,$db_name); if(!$conn) die('数据库链接失败!<br/>'); $sql = "select * from users where username='{$username}' and password='{$password}'limit 1"; $result = $conn->query($sql); if($result->num_rows==1){ echo "<script>alert(\"登陆成功!\")</script>"; } else{ echo "<script>alert(\"用户名或密码错误!\")</script>"; } } else{ header("Location:/index.php?display=1"); } ?>
针对上面的代码进行sql注入的例子:html
username='or''=' password='or''=' 若是这样的话SQL语句就变成了select * from users where username=''or''=' and password=''or''='' limit 1,显然条件是个永真式,查询必定成功。 或者 username='or''=' limit 1# password=任意非空值 SQL语句能够本身写一下。
除了上述的payload,还有不少其余的payload可用。mysql
- 1.2
如何将上述代码增强一下呢?上述代码在进行查询时同时查询了username和password,查询时用户能操做的参数越多,不肯定性就越大。能够换一种思路,查询时拼接的字符串只用到主键username,后面在检查password和数据库中的是否一致。即,<b>能够调整查询的结构,减小用户可控的参数拼接</b>。程序员
数据库中密码明文不太好,顺便md5处理一下,加盐效果更好,能够防止数据库被黑了致使敏感信息泄漏。sql
$password = md5($_POST['password']); if(!empty($username) && !empty($password)) { $conn = new mysqli($db_server,$db_user,$db_pass,$db_name); if(!$conn) die('数据库链接失败!<br/>'); $sql = "select * from users where username='{$username}' limit 1"; $result = $conn->query($sql); if($result->num_rows==1){ $row = mysqli_fetch_assoc($result); if($row['password']==$password) echo "<script>alert(\"登陆成功!\")</script>"; else echo "<script>alert(\"用户名或密码错误!\")</script>"; } else{ echo "<script>alert(\"用户名或密码错误!\")</script>"; } }
这样作的话若是继续用username='or''='显然是不能够了,除非你知道数据库中第一个用户的密码。可是毕竟仍是能够破解,所以能够在借助<b>过滤函数</b>来帮忙。在这个例子中,因为username参数两侧是单引号,若是构造sql注入必定须要加入额外的单引号来破坏原语句,因此能够直接借助addslashes()函数将username中的单引号转义。数据库
$username = addslashes($_POST['username']); $password = md5($_POST['password']);
在这个最简单的例子中,通过这样简单的修改彷佛已经没有办法注入了。后面会给一些其余的例子,并给出一些新方法来防护sql注入。函数
- 1.3
以前提到了过滤函数,用到的是PHP自带的转义函数,可是这个有时候是不够用的。这种状况下能够自定义过滤函数。fetch
常见的过滤手段就是限制关键字,经过正则实现。url
如下是节选的某CTF赛题中的一段代码,CTF中常用留有余地的过滤函数,让选手能够进行SQL注入。spa
if(!empty($_POST["user_name"]) && !empty($_POST["phone"])) { $msg = ''; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i'; $user_name = $_POST["user_name"]; $phone = $_POST["phone"]; if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ $msg = 'no sql inject!'; }else{ $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'"; $fetch = $db->query($sql); } if (isset($fetch) && $fetch->num_rows>0){ $row = $fetch->fetch_assoc(); if(!$row) { echo 'error'; print_r($db->error); exit; } $msg = "<p>å§å:".$row['user_name']."</p><p>, çµè¯:".$row['phone']."</p><p>, å°å:".$row['address']."</p>"; } else { $msg = "æªæ¾å°è®¢å!"; } }else { $msg = "ä¿¡æ¯ä¸å ¨"; } ?>
该段代码中限制了select,insert等不少关键字,对防止SQL注入有必定效果,可是有缺陷。若是考虑的不太全仍是会被注入,过滤函数设置的对关键词过于敏感会让不少正常信息的查询也变得不易。
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
关于该题目的注入可参考如下文章,这里用到了二次注入:
https://www.cnblogs.com/kevinbruce656/p/11347127.html#4355106
- 1.4
以前的各类方法都比较麻烦,对程序员不友好,有一种比较简单的方法就是预编译,既能有效的防止SQL注入,又容易编写。
预编译能防止SQL注入是由于SQL语句在执行前通过编译后,数据库将以参数化的形式进行查询,当运行时动态地把参数传给预处理语句时,即便参数里有敏感字符如 'or''='数据库也会将其做为一个字段的属性值来处理而不会做为一个SQL指令。
总结一下,SQL注入的核心就是构造SQL指令,预编译破坏了这个条件,所以能防止SQL注入。
举个例子
<?php include 'config.php'; $username = $_POST['username']; $password = md5($_POST['password']); if(!empty($username) && !empty($password)) { $conn = new mysqli($db_server,$db_user,$db_pass,$db_name); if(!$conn) die('数据库链接失败!<br/>'); $sql = "select * from users where username=? limit 1"; $result = $conn->prepare($sql); $result->bind_param('s',$username); $result->bind_result($users,$pass); $result->execute(); if($result->fetch()){ if($pass==$password) echo "<script>alert(\"登陆成功!\")</script>"; else echo "<script>alert(\"用户名或密码错误!\")</script>"; } else{ echo "<script>alert(\"用户名或密码错误!\")</script>"; } $conn->close(); } else{ header("Location:/index.php?display=1"); } ?>
如下是比较核心的几行
$sql = "select * from users where username=? limit 1"; $result = $conn->prepare($sql); $result->bind_param('s',$username); $result->bind_result($users,$pass); $result->execute();
第一行是一个SQL语句,?处须要被填充。
第二行是对SQL语句进行预编译。
第三行是限制填充的类型为字符串,使用username变量来填充SQL语句。
第四行是肯定查询结果存储到哪些变量中。
第五行是执行,执行完毕将会得到结果。
使用预编译的方式防止SQL语句简单有效,暂时没有发现防不住的状况,建议使用。
个人博客即将同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2k2m12zcg6nq