前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SQL注入攻击与防御举例

SQL注入攻击与防御举例

作者头像
KevinBruce
发布2020-03-11 13:52:38
1K0
发布2020-03-11 13:52:38
举报
文章被收录于专栏:CTF及算法学习

SQL注入攻击与防御实例

  • 1.1

以下是一段普普通通的登录演示代码,该脚本需要username和password两个参数,该脚本中sql语句没有任何过滤,注入起来非常容易,后续部分将逐步加强代码的防注入功能。

代码语言:javascript
复制
<?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注入的例子:

代码语言:javascript
复制
username='or''='
password='or''='
如果这样的话SQL语句就变成了select * from users where username=''or''=' and password=''or''='' limit 1,显然条件是个永真式,查询一定成功。
或者
username='or''=' limit 1#
password=任意非空值
SQL语句可以自己写一下。

除了上述的payload,还有很多其他的payload可用。

  • 1.2

如何将上述代码加强一下呢?上述代码在进行查询时同时查询了username和password,查询时用户能操作的参数越多,不确定性就越大。可以换一种思路,查询时拼接的字符串只用到主键username,后面在检查password和数据库中的是否一致。即,可以调整查询的结构,减少用户可控的参数拼接

数据库中密码明文不太好,顺便md5处理一下,加盐效果更好,可以防止数据库被黑了导致敏感信息泄漏。

代码语言:javascript
复制
$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''='显然是不可以了,除非你知道数据库中第一个用户的密码。但是毕竟还是可以破解,因此可以在借助过滤函数来帮忙。在这个例子中,由于username参数两侧是单引号,如果构造sql注入一定需要加入额外的单引号来破坏原语句,所以可以直接借助addslashes()函数将username中的单引号转义。

代码语言:javascript
复制
$username = addslashes($_POST['username']);
$password = md5($_POST['password']);

在这个最简单的例子中,经过这样简单的修改似乎已经没有办法注入了。后面会给一些其他的例子,并给出一些新方法来防御sql注入。

  • 1.3

之前提到了过滤函数,用到的是PHP自带的转义函数,但是这个有时候是不够用的。这种情况下可以自定义过滤函数。

常见的过滤手段就是限制关键字,通过正则实现。

以下是节选的某CTF赛题中的一段代码,CTF中经常使用留有余地的过滤函数,让选手可以进行SQL注入。

代码语言:javascript
复制
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注入有一定效果,但是有缺陷。如果考虑的不太全还是会被注入,过滤函数设置的对关键词过于敏感会让很多正常信息的查询也变得不易。

代码语言:javascript
复制
$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注入。

举个例子

代码语言:javascript
复制
<?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");
}
?>

以下是比较核心的几行

代码语言:javascript
复制
$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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-02-25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SQL注入攻击与防御实例
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档