SQL注入的一般过程
- 概述
- SQL注入的步骤
- 一道例题
- 总结
概述
SQL注入是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。SQL注入大致可以分为两类:数字型注入和字符型注入。
SQL注入的步骤
这里介绍下SQL注入的基本流程,需要明确的是在实际注入中,我们都是无法看到具体的后端代码的,也就是无法知晓具体的SQL拼接过程。一般SQL注入分为以下7个步骤,目的是通过一步一步的注入来猜测出查询语句和数据库结构,最终暴露出数据库中表的信息,实际操作中大家可以根据需要来进行取舍。
1、判断注入类型,数字型还是字符型:
首先,我们需要确定目标是数字型还是字符型注入漏洞,以便我们进一步进行其它注入操作。如下面的php代码片段所示,第一行代码代表数字型的sql拼接,其中的变量在sql查询语句拼接时并没有用引号括起来;第二行代码是字符型,其中的变量使用了引号进行拼接。
# 数字型
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
# 字符型
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
下面介绍如何进行判断,如下代码所示,首先我们假设注入类型为数字型,分别输入如下的语句。如果被测试对象是数字型,那么第一行测试语句会返回user_id为1的查询结果,而第二行语句由于条件and 1=2
不成立,所以查询结果为空。如果被测试对象为字符型的话,将下面语句拼接到sql中,由于user_id的值都不匹配,所以应该是都不返回任何结果,但是注意如果user_id本身是int类型,实际查询过程中是会返回结果的,这可能是因为对输入的字符进行了截断并转换了类型,造成1 and 1=2
在字符类型中会返回user_id为1的查询结果。当然如果第二个语句返回了结果,我们也可以以此判断出该注入类型是字符型。
1 and 1=1
1 and 1=2
对于字符型注入判断,我们也可以这样进行操作,如下面的两条注入语句所示,如果是在数字型注入中,由于变量没有加引号,所以拼接后sql语法错误,直接报错,这和不回显信息是有区别。因此如果下面的语句注入后提示sql语法错误,那么我们可以直接判断测试对象为数字型注入。而对于字符型注入,第一行语句输入后和原本的引号前后完全闭合,且逻辑成立,所以回显出user_id为1的数据;第二行语句输入后,前后引号也完全闭合,但逻辑不成立,所以返回结果为空。
1' and '1'='1
1' and '1'='2
最后展示一下直接在MySql数据库中的操作,大家可以更加直观的感受下结果。
2、猜解sql查询语句中的字段数
在这一步中,我们尝试去猜测出查询语句中的字段个数,如下注入语句所示,假设为字符型注入,先利用1'
实现引号闭环,再利用or 1=1
这样可以暴露出表中所有的数据,最后利用order by num#
去看是否报错来明确查询语句中的字段数,其中#号用于截断sql查询语句。
1' or 1=1 order by 1 #
1' or 1=1 order by 2 #
......
......
当然也可以采用1' or 1=1 union select 1, 2, 3 #
的方式,sql查询截图示意如下。
3、确定字段的显示顺序
这里我们直接使用union就行,如下截图和代码所示,这里我们故意扰乱了first_name和last_name的两个位置,查询出来结果中的1,2会指明数据字段在查询语句中的位置。
1' union select 1, 2 #
4、获取当前数据库
通过前面的字段数确定以及显示顺序确定,我们就可以结合union操作来获取数据库中的信息了。如下代码和截图所示,展示了获取数据库名的操作,根据前面已经获取到的字段数以及位置关系,假设有两个字段,那么下面的查询语句将会把数据库的名称放在第二个字段中。
1' union select 1, database() #
5、获取数据库中的表
在获取到当前数据库名后,我们可以进一步获取其中表的信息。如下代码和截图所示,展示了获取数据库中表的信息,information_schema是MySql自带的信息数据库,用于存储数据库元数据(关于数据的数据),例如数据库名、表名、列的数据类型、访问权限等,其中的表实际上都是视图。information_schema的tables表记录了数据库中表的信息,指定table_schema的名称即可显示对应数据库中表的信息。
1' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database() #
6.获取表中的字段名
进一步获取其中的字段名,假设要获取的表为users,如下面的代码和截图所示。information_schema的columns表存储了表中列的信息,也就是表中字段信息,指定table_name表名,即可获取到对应表的字段信息。
1' union select 1, group_concat(column_name) from information_schema.columns where table_name='users' #
7、下载数据
最后根据需要,我们获取表中的信息。这里需要注意的是,我们假设原来的注入语句中只有两个查询字段,所以这里select后也只能跟两个group_concat,想一次性看多余的信息,只能在一个group_concat中进行组合。
1' or 1=1 union select group_concat(user_id, first_name, last_name), group_concat(password) from users #
一道例题
这里以2019年的极客大挑战的LoveSQL为例题进行讲解,远程环境可以在BUUCTF上获取。打开远程环境,网站的页面是这样的,给了些许提示表明是sql注入,所以接下来我们直接按照sql注入的一般过程看看。
1、确定注入类型
该题目中有两个输入框,也就是说有两个可以拼接sql的地方,我们在其中任意一个地方进行注入即可。如下截图所示,1' or 1=1 #
,这是按照字符型注入的规则输入的万能密码,也就是说后台数据库在查询时,该sql语句的逻辑一定成立。
如下图所示,是尝试字符型注入后的结果,数字型的注入大家可以自己尝试一下,不会成功。
2、确定字段数和显示顺序
在初步尝试登录成功后,回显了一个奇怪的字符串,看起来很像flag,经过各种尝试后发现并不是,所以接下来还是按照sql注入的步骤进行。我们注入1' union select 1, 2, 3 #
,注入过程和效果如下图所示,发现Hello处显示了2,Your password is处显示了3,再经过其它测试发现查询的字段数只有3列。结合之前登录成功的界面,可以猜到第2列对应的是用户名,第3列对应的是密码。
3、获取数据库以及表信息
这里我将sql注入的步骤融合了,经过前面的注入类型确定和字段数确定,接下来的步骤才是真正寻找信息的过程。如下代码和截图所示,展示了泄露出数据库表信息的过程。
# 注入语句
1' union select 1, 2, group_concat(table_name) from information_schema.tables where table_schema=database() #
根据ctf独特的命名风格,我们猜测大概率flag在表l0ve1ysq1
中,接下来是泄露出表字段信息,如下代码和截图所示。
# 注入语句
1' union select 1, 2, group_concat(column_name) from information_schema.columns where table_name="l0ve1ysq1" #
最后看一下表中的数据到底是啥,可以发现flag在password字段中,由于数据太长,flag前半部分在右边无法全部截图。
# 注入语句
1' union select 1, 2, group_concat(password) from l0ve1ysq1 #
当然进一步确定flag所在id后,可以这样打印出来。
# 注入语句
1' union select 1, 2, group_concat(password) from l0ve1ysq1 where id=16 #
总结
不忘初心,砥砺前行!