SQL注入是一种常见的web安全漏洞。sql注入是通过恶意的sql查询或者添加语句插入到应用的输入参数中,再在后台sql服务器解析执行进行的攻击。
在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的"数据"拼接到SQL语句中,被当作是SQL语句的一部分执行,从而导致数据库受损。是目前黑客对数据库进行攻击的最常用手段之一。
数据库
web分别前端和后端,前端负责进行展示后端负责处理来自前端的请求并提供前端展示的资源。后端有资源就要有储存资源的地方——如mysql数据库。服务器需要使用SQL语句这一语法结构进行查询获取数据。
当我们访问动态网页时,Web服务器会向数据访问层发起sql查询请求,如果权限验证通过就会执行sql语句。这种网站内部直接发送的sql请求一般不会有危险,但实即情况是很多时候都需要结合用户的输入数据动态构造sql语句,如果用户输入的数据被构造成恶意的sql代码,web应用未对动态构造的sql语句使用使用的参数进行审查,就有可能导致sql注入漏洞。
三层架构是将整个业务应用划分为:
界面层
业务逻辑层
数据访问层
由数据库驱动的Web应用程序依次从三层架构的思想也分为了三层:
表示层
业务逻辑层
数据访问层
猜解后台数据库,盗取网站敏感信息
绕过认证,例如绕过验证登录后台网站
注入可以借助数据库的存储过程进行提权等
数据库的功能就是用来组织很多很多的数据。这些数据通常是存储在外存(磁盘)。数据库提供的核心功能,也就是针对数据的增删查改。数据库是一个软件,能够帮助我们在磁盘上管理数据即对数据进行增删查改。另外并不是所有的数据库都是把数据放在磁盘的。
数据表是一个临时保存数据的网络虚拟表(表示内存中数据的一个表)。它无须代码就可以简单的绑定数据库。数据表是由表名、表中的字段、表的记录三部分组成
字段的概念是一个成员,它表示与对象或类关联的变量。数据库字段比如说是一张二维表,有姓名性别年龄。这些就都是这些表的一个字段。
这里全篇以sql-labs靶场为例,对sql注入有一个更深刻的了解和认知。
系统数据库information_schema数据库中含有很重要的三张表:schemata,tables和columns。
schemata表中存储了Mysql中所有数据库的信息,包括数据库名,编码类型等,show databases的结果取之此表。
tables表中的schema_name字段为数据库名。表中存储了Mysql中所有数据库的表的信息(索引根据数据库名),show tables from schema_name的结果取之此表。 tables表有table_name和table_schema两个字段,分别为表名和表所在数据库。
columns表中存储了Mysql中所有表的字段信息,show columns from schema_name.table_name的结果取之此表。表中有column_name、table_name和table_schema三个字段,分别为字段名、字段所在表名和表所在数据库。
我们可以在火狐浏览器中安装一个hackbar插件
hackbar是浏览器中的一个插件,包含一些黑客常用的工具,比如SQL injection XSS和加密等。它的功能类似于地址栏,但是它里面的数据不受服务器的相应触发的重定向等其他变化的影响。
打开一个网页按F12即可看到有hackbar选项。
Load URL:复制地址栏中的地址
Split URL:剪切地址
Execute:执行hackbar中的网址
Encyption:四种加密方式
Encoding:三种编码方式
SQL:提供一些方便的查询语句
xss:提供一些XSS工具语句
Post data:以post的方式执行数据
没有对输入的语句进行运算即为字符型
对输入的语句进行运算了则为数字型
比如我们输入id=1 and 1=2
如果报错就是数字型不报错就是字符型
搭建靶场网上教程一搜一大把这里就不写详细过程了。打开sqli-labs靶场第一关。
判断一下是否存在注入,如果存在的话是什么类型的注入。
看这个页面它提示你输入数字值的id作为参数,我们在这里输入?id=1
页面出现了id=1时的用户名和密码,我们换几个id数字出现返回的结果也不一样。这证明我们输入的内容被执行了,带到了数据库里面查询了。说明存在注入漏洞。
接下来判断漏洞类型是字符型还是数字型。
尝试用单引号闭合出现报错。
刚学的时候我会有疑问,为什么输入这样然后报错了就能确定是字符型注入?
字符型注入:当输入的参数为字符串,如果存在注入漏洞,则为字符型注入。
数字型注入:数字型不需要单引号闭合,而字符型一般需要。
本来语句是一个完整的语句,它是用单引号闭合的,然后我们输入的单引号参数,导致多出了一个单引号而让语句闭合不了,产生报错,所以我们可以判断是单引号闭合。从刚才的报错语句中我们可以猜出部分查询语句'1''和limit 0,1。其中1'是我们刚才输入的参数,它导致闭合不了所以报错了。
测试?id=1' and'1'='1
and后面肯定为真,如果是单引号字符注入不会报错。
?id=1'and '1'='2
and后面的一定为假,如果是数字型这个会被运算然后报错
如果是字符型不会被运算不会报错但是页面回显不正常。
综上所述这关为字符型。
验证一下我们在刚才输入的参数后面加上--+。--+是可以将后面的语句注释掉。一般字符型需要--+注释而数字型不需要--+注释。我们可以看到--+将我们输入的参数'注释掉了页面不再报错。
因为我们的页面存在回显,我们就可以用联合查询语句来查询。
联合查询就是两个sql语句一起查询,两张表具有相同的列数且字段名是一样的。
我们首先要知道表格有几列,一般先尝试3,如果报错就是超过了列数,如果回显正常则没有超过列数。
我们由此可以看出有三列。
然后我们判断是第几列有回显。就是看看表格里面那一列是在页面显示的。这里注意id后面的数字要采用一个不存在的数字比如-1或者0这些。这是因为如果用存在的id会优先执行前面的语句,导致后面的查询无法回显。
然后我们获取当前数据库名。
两个查询返回的列数必须相同,两个select语句对应列所返回的数据类型必须相同。
通常利用联合查询的特点,使原查询左边为空,是我们定义的查询结果返回出来。
就比如该表有三个字段,界面显示第2.3个字段。我们就要构造select*from users where id= - union select 1,2,3 from users
然后2.3随便一个可以替换成任意想要的结果。这就是上面查数据库,将3位置换成了我们要查询的结果。
concat()函数:用于连接字符串
group_concat(column_name):将字段的所有数据用,连接作为字符串输出。
然后我们爆表。
接着爆字段名。我们通过上面查询知道当前数据库有四个表,根据表名知道可能用户的账号和密码在users表内。然后我们要做的是得到该表下的字段以及内容。
通过上述操作我们可以得到两个敏感字段username和password、接下来我们要得到这两个字段里面对应的内容。
我们爆出了用户名和密码这关就算完成了。查询语句不会的可以百度搜索一下。查询语句不唯一。
第一步先判断是什么类型的注入。
先尝试用单引号闭合。报错。
输入id=1'and'1'='1。仍然报错说明不是字符型。
再测试id=1 and 1=2。页面回显不正常,由此可以判断是数字型。
后面的步骤同第一关
先用单引号闭合测试报错。
我们用id=1' and '1'='1判断回显正常说明可能是单引号闭合,排除数字型。
用--+将'注释测试报错,排除单引号闭合。那就有可能是跟单引号相关的闭合方式。
这里猜测用')闭合
由此判断闭合方式为')
后面的步骤同上。
先尝试单引号闭合报错
用id=1' and '1'='1.回显如下排除数字型注入。
用--+注释'报错,证明不是单引号闭合应该是其他。
尝试")闭合
由此可以判断为")字符型注入,后面操作同第一关。
先输入id=1'发现报错
然后还是用上面的方法测试最后判断出是单引号闭合,字符型注入。
然后我们想要知道表格有几列,发现页面没有回显。
这里我们想到了类似是布尔盲注,报错注入,时间延迟型盲注。下面我先来介绍一下分别是什么。
适用环境:页面只有成功失败这两种情况,可以使用布尔盲注。
盲注步骤:
使用 length()函数 判断查询结果的长度
使用 substr()函数 截取每一个字符,并穷举出字符内容
?id=1' and length(database())=1 --+
页面异常显示表明我们猜解的长度有误。页面如果显示you are in...显示正常表示我们猜解的长度正确。
可以依次猜解1.2.3直到正确。
猜测到8的时候回显正常,证明数据库字符长度为8.
我们查询到的结果由一个一个字符组成,每一个字符可以是数字、英文字母、特殊符号,总共有95种可能,对应的ascll编码是32-126.
使用MySQL的 substr()函数截取查询结果的第一个字符,使用 ascii()函数 将截取的字符转换成 ASCLL编码,依次判断是否等于32,33,34……126。
SCLL'编码 115 对应的字符是 's',确定第一个字符是:s
然后猜解后面字符
正常情况页面只能返回用户信息不能返回其他信息。
报错注入payload
?id=1' and updatexml(1,concat(0x7e,version()),3) --+
我们也可以利用报错注入显示数据库内的所有数据。
0x7e等价于~
参数2包含特殊符号 ~,触发数据库报错,并将参数2的内容显示在报错信息中。
updatexml()函数的报错内容长度不能超过32个字符,我们可以用一下两种方式解决一下这个问题:
limit分页
substr()截取字符
适用于页面不会返回错误信息,只会回显一种界面。其主要特征是利用sleep函数,制造时间延迟,由回显时间来判断是否报错。
利用sleep()或benchmark()等函数让mysql执行时间变长经常与if(expr1,expr2,expr3)语句结合使用,通过页面的响应时间来判断条件是否正确。if(expr1,expr2,expr3)含义是如果expr1是True,则返回expr2,否则返回expr3。
因为我们要用到这个时间盲注的时候,由于页面无法返回正确的值,所以我们只能通过if加sleep函数解决问题。
if(expr1,expr2,expr3)含义是如果expr1是True,则返回expr2,否则返回expr3。
sleep(x)函数:延迟x秒后回显
if(1=1,1,sleep(1)):1=1成立正确,结果返回1,否则延迟1秒回显。
这里有报错,我们可以利用报错注入。
先判断有几列,输入为4的时候,报错了。所以为4.
然后利用报错注入爆出表名
爆出其他与这个操作基本相同不再展示
我们还可以使用布尔盲注。先用布尔布尔盲注判读数据库有几位。
判断出数据库名有8位,我们可以利用burpsuite来一位一位爆破出数据库名。
由此可以直到第一位是s
也可以用时间盲注 ,可以看到第一位是s有明显的延迟,当我换成a就没有延迟,所以可以判断第一位是s。
sqlmap的详解我后面会再出一篇文章写。
上面同样方法测试,双引号包裹后可以闭合。其他的操作同上面一关。
输入id=1测试
输入id=1’测试发现报错,但是没有具体的报错信息。
经过测试我们发现闭合方式是'))
它只有正常显示和不正常两种状态而且没有报错信息。我们其实可以使用时间盲注和布尔盲注来爆。操作和上面关卡方法相同。但是它的页面提示有use outfile...。那就说明它开了读写权限,这关我们用sql注入读写文件来尝试一下。outfile函数可以将sql语句的结果导出再文件中
我们先查询一下导出的目录位置
我们可以查询到导出的目录位置在D:\phpstudy_pro