浅析 SQL 注入和防御

Mr.R0boter 于 2020-08-24 发布

SQL 注入原理

产生 SQL 注入的原因:开发人员在开发过程中对用户输入过于信任,未对输入的数据进行检查,从而造成 SQL 注入漏洞的产生。

SQL 注入的原理:通过 web 层面中可与数据库产生交互的点,通过恶意拼接 SQL 语句,篡改后端原本的 SQL 语句,以达到获取数据库信息的目的

SQL 注入常见方式

  1. 常规注入:会产生回显

    例 1:以 PHP 中登录点为例,

    $con = mysqli_connect("localhost","root","123456","test");
    $name = $_POST['username'];
    $password = $_POST['password'];
    $sql = "select username, password FROM user WHERE username = '$name' and password = $password";
    $sqlq = mysqli_query($con, $sql);
    $row = mysqli_fetch_array($sqlq, MYSQLI_NUM);
    if($username == $row['username'] && $password == $row['password'])
        echo "<script>alert(\"用户名和密码正确\")";
    else
        echo "<script>alert(\"用户名密码错误\")";
    

    此处从 POST 方式提交处获取 username 和 password 的值后,未作任何处理就直接带入到 sql 查询语句中,此时如果将 username 的值填写为 admin' or '1' = '1'-- 此时后端的 sql 语句被拼接为 select username, password FORM user WHERE username = 'admin' or '1' = '1' -- and password = '$password' 此时无论 password 中输入的是什么内容都不重要的了,因为在 sql 语句中已经将后面的语句注释掉了,而剩下的语句执行结果为真,这就产生了 sql 注入问题了

    例 2:以 url 中常见的 ?id=xxx 为例

    $con = mysqli_connect("localhost","root","123456","test");
    $id = $_GET['id'];
    $sql = "select username, text FROM text WHERE id = '$id';
    $sqlq = mysqli_query($con, $sql);
    $row = mysqli_fetch_array($sqlq, MYSQLI_NUM);
    echo "$row[username]$row[text]"
    

    此处后端代码为根据 GET 提交的 id 的值查询用户名和文本,并回显出来。同样未对输入做任何检查与过滤。此类 sql 注入处一般遵循以下基本流程:

    1. 判断注入点类型:此处 id 的值由单引号包裹所以是一个 string 类型的值,需要考虑单引号的闭合问题,如果此处未使用引号包裹,则不需要闭合引号。此处构建的注入代码应如下 id' and '1' = '1 在 id 后和 1 前都有一个单引号,配合源码中的单引号就实现了闭合

    2. 判断查询了多少字段:此处的 sql 注入需要使用到联合查询的方法,而联合查询需要联合查询的语句和前面的查询语句查询的字段数一样,所以需要先判断它原本的查询语句查询了多少个字段,此处查询字段个数为 2 个 username 和 text 。一般判断查询字段数使用 order by 。注入代码为 id' order by 2 在实际运用中可以用二分法去猜解到底是多少个字段

    3. 判断回显点:通常 web 页面的回显不会显示查询的所有数据,因此必须找出哪些点会产生回显,再将注入代码填在回显处,才能得到想要的数据。注入代码为 id' union select 1,2 -- 此处在 id 处闭合单引号后,最后使用了注释将原本语句中的另一个单引号注释掉,防止报错。 and 1=2 用于不显示之前的查询结果,防止占用显示位

    4. 获取数据:利用 mysql 内置函数获取基本信息,构建 sql 语句获取数据库数据。

      1. 常用函数

      version() :数据库版本

      database() :当前数据库名

      user() :当前用户

      1. 得到数据库名后,就需要构建语句获取表名和字段名。此处涉及到 mysql 中一个数据库元数据库 information_schema 库。当然此处需要当前用户具有管理员权限才能查询。如果查询结果有多个值,但显示位只显示一个,可以使用 limit 进行排序显示。(假设之前的 2 为回显点)

        查表的代码: id' and 1=2 union select 1, table_name from information_schema.tables where table_schema=database() limit 0,1 --

        查字段的代码: id' and 1=2 union select 1, column_name from information_schema.columns where table_schema=database() and table_name='xxx' limit 0,1 -- (xxx 为前面注入出来的表名)

2) 盲注:不会产生回显

SQL 注入流程

  1. 判断注入点
  2. 判断注入类型
  3. 判断数据库类型
  4. 获取数据库、提权

SQL 注入防御方法