0x00
sql注入的注入点有两种,一种是get型注入,一般像url上的参数(?id=1);一种是post型注入,在表单的提交处
一般的注入过程如下:
- 找到注入点(即能发数据到服务器,服务器带着发送的数据与数据库交互的地方)
- 判断注入类型(数字型注入 还是 字符型注入)
- 如果是字符型注入,判断闭合符号
- 判断原来语句在数据库中查询出来的列数(用于union注入)
一般的注入方法分为以下几类:
- union联合注入
- 报错注入
- bool盲注
- 时间盲注
- 二次注入
- 堆叠注入
- 带外注入
0x01 判断注入类型
and 1=2
字符型注入会把传入的参数放在一个闭合符中,假设服务器对接收参数的原代码可能是这样写的:
select * from users where id='参数'
而数字型注入没有闭合符。因此可以使用 and 语句来判断是数字型还是字符型注入。
?id=1 and 1=2
and语句只有两边同时为真时,语句才为真,才能执行。因此在数字型注入中,以上的参数传到源码中,显示为:select * from users where id=1 and 1=2 后面为假,无法执行,页面回显错误或与id=1时候回显不同。
当注入为字符型注入时,源码中执行的语句为 select * from users where id=’1 and 1=2′ 这样只会解析字符串中第一个数字,回显的页面与id=1一样。
2 & 2-1
原理类似,如果是数字型注入,那么服务器中源码是 select * from users where id=2-1 最终会回显id=1的结果;字符型注入 select * from users where id=‘2-1’ 则显示 id=2的结果
0x02 判断闭合方式
丢单引号、双引号
如果页面有报错回显 可以尝试丢单、双引号的方法。
如图所示,可以知道闭合方式是单引号包裹‘1’
盲注猜测
如果没有报错回显,可以尝试使用bool盲注。在参数的右边猜右端闭合符 如 ’ ” ) 等
http://127.0.0.1:10001/Less-1/?id=1' --+
回显内容不是报错内容,与id=1内容一致时,则闭合方式为所猜测的闭合符
0x03 正常提交后查询几列数据
order by
order by是一个排序的函数,对查询出的内容按照选定的列进行排序(能选该表中的所有列,即便不是查询的列),默认是从小到大。因此最大的能不报错回显的数字 就是源码中查询语句得到的数据的列数。
插点题外话,order by和 group_concat() 连用的时候,order by 要放group_concat的括号中,并且在查询的列的后面,不然order by的排序功能会失效。group_concat是一个将查询内容拼接成一行的函数
group by
group by 是一个用于分组的函数,一般也能进行列数的判断,在一些 waf 对 order by 拦截时有奇效
0x04 判断回显位置
并不是说源码中的查询语句查询到几列数据,就回显几列。因此我们要判断回显位,即回显的数据是源码中查询的数据的第几列。
由图可知,查询到的第二列和第三列数据回显,那么第二列和第三列的位置就可以作为我们的payload点
有一点要注意的是,有的页面只能回显一行数据,比如这个靶场,因此可以把union前面查询的参数改成一个不存在的数,这样查询不到 union 前的语句,就能显示出union后我们真实需要的语句了。
0x05 less-1 使用union注入爆出数据库所有账号密码
我们知道了是字符型注入,当前的表有三列,回显位为2、3列
首先要得到所有库名:
Mysql中有一个自带的数据库 infomation_schema 记录当前mysql数据库中所有 库、表、列 的信息(以当前用户连接的mysql能访问的数据库)。其中有个表名 schemata ,记录所有库的信息,表中的 schema_name 记录着所有的库名。
其次就是要得到security中所有表名:
information_schema 的表 tables 中的列 table_name 记录了Mysql数据库中所有库的表名。但是表名实在太多,记录表名所在的库的那一列的列名 叫做table_schema,因此可以加一个where限定。有的时候直接写库名会被waf拦截,因此可以写table_schema=database()
下一步就是拿到 users 这个表中所有列名,根据列名推断出哪些列是账号密码。infomation_schema 这个库中的表 columns 存储着所有的列名信息,这个表中的 column_name 列存储着列名,再加上所在表、库的限定:
易得username 和 password列中存储着用户名和密码:
最终拿到所有的用户名和密码
0x06 数字型union注入
没什么好说的,不需要手动添加闭合符,因此不需要判断闭合符;有的时候结尾甚至不用加–+,视具体情况而定
0x07 extractvalue() 报错注入—-less-5
有的时候查询语句正常执行,但是页面并不回显;但是输入错误的查询语句页面会返回报错信息。那我们就可以尝试用报错注入。报错注入,简单来说就是 构造一个让错误信息中夹杂着可以显示数据库内容的查询语句。
因此,报错注入使用的条件就是 网站的前端能够动态地显示数据库中查询的报错信息,写死的或者服务器中自定义的报错信息不算(比如错误404之类的)。
extractvalue(doc,path)函数是一个能返回查询的xml文档中符合路径内容的函数,第一个参数写的是xml文档的对象,其实就是数据库中的列名。第二个参数写的是xml文档中要查询的路径,当路径不正确时,会返回关于路径的错误信息。那么我们就可以在第二个参数中加入我们想查询的数据,通过报错将其回显出来
xml路径第一个必须是 / ,因此可以在第二个参数开头写一个~符号让其报错。~的ascii码值是0x7e
这里extractvalue第一个参数1指的是第一列,concat()函数的作用是把第一个参数和第二个参数拼接起来,仅此而已
接下来我们尝试用extractvalue()函数爆出所有数据库名、表名、列名,拿到所有用户的用户名和密码:
但是我们发现,extractvalue()函数的报错信息只能回显32个字符,这时我们可以用substring函数解决这个问题。
接下来查询security这个数据库中的所有表名
接下来查询users这张表中所有列名:
之后爆出来所有用户名和对应的密码
0x08 updatexml报错注入—-less-5
updatexml(doc,path,new_value)函数是sql语句中用来更新xml文档内容的语句,第一个参数是要xml文档对象,也就是数据库中的列;第二个参数是要更改的路径;第三个参数是把符合路径下的内容修改成的新的值。和extractvalue函数一样,依旧是当路径错误时,返回错误路径的信息,所以依旧是在第二个参数中做文章。
具体的过程和前面的extractvalue报错注入一样,也是只能回显32个字符,这里不过多演示,直接放最后爆出来用户名和密码的截图
0x09 floor报错注入—-less-5
floor报错是一个利用count函数计数时的逻辑漏洞来返回错误信息的报错注入方式,其中要用到很多函数,下面一一介绍:
- rand() 随机返回0-1之间的小数
- floor() 将数字向下取整 (向上取整ceiling())
- concat_ws() 将括号内的数据用第一个参数连接起来
- group by 分组函数,常与count()函数连用
- as 别名
- count() 用于统计数量
- limit 用于显示指定行数
rand()函数有一个用法 就是可以判断一个表中存在几行数据,返回几个数字说明这个表中就有几行数据
假设我要查询的信息是当前数据库名,那么我要先用concat_ws(‘-‘,database(),floor(rand()*2))把查询的数据和一个0或1的数拼接起来作为一个别名a;然后选用一个有多行数据的表,让rand()函数显示多次,即进行多次拼接,这里我用information_schema.tables这张表;然后用count(*) 和group by a查询a每种总共有几个(因为拼接的数字只有1或2,所以只有两种),这个时候就会产生报错。
产生报错的原因是因为count()函数的计算机制逻辑上存在漏洞,这里可能会回显security~1,也有可能回显security~0,还有可能正常回显。可以在rand()函数中间加入参数0,这样就可以报错回显,因为这时返回的小数就是固定的,还有其他参数也可以让报错固定。
接下来就是把想要查询的内容放在原来database()的位置,即可进行报错注入了,这里就只从拿到数据库列名开始:
接下来爆出所有用户名和密码:
我们发现执行不出来结果,可能是因为floor注入回显的报错信息过长导致的。这时我们可以简单地拼接用户名和密码,并用 limit 来显示数据的第几行(一行一行地显示)
0x0A bool盲注—-less-8
假设执行正常语句页面不回显,使用报错注入,页面也不回显错误信息,这个时候可以观察页面对于查询出内容与查询不出内容,是否对应有真值、假值两种状态,如果有,那么页面存在bool盲注
and语句是跟在where限定后面进行多条件判断的,只有 对and前后两个条件都为真 的数据,才能查询出来,因此可以在and语句上写一些恒为真或恒为假的语句。bool盲注的原理就是对查询语句添加and条件,当条件为真时,页面返回“真”对应的状态;条件为“假”时,页面返回条件为假的状态。
条件为真,能够查询到数据时,返回真页面:
条件为假,返回假页面:
我们可以通过ascii()函数对要查询的内容转码成ascii码,并将这个转换后的ascii码在and语句中进行盲猜判断,通过页面回显的真、假值来确定要查询的内容:
我们发现这一段注入语句页面返回为真,所以and后的语句条件为真;因此可以再试一试,把值改为120,此时发现页面为假
那么之后就很显然了,在110到120之间去盲猜,就可以得到当前数据库的数据库名的第一个字符
之后以此类推,substr返回数据库名第二个字符,开始猜第二个字符……
当然也可以不用ascii()去转换:
之后把想要查询的数据放在substr的第一个参数位置上,就可以了,这里从查询到了表名为users之后开始:
注意这里要加limit 0,1保证返回一个查询结果
0x0B 时间盲注—-less-9
如果页面没有回显、报错、真假值, 这个时候就要考虑服务器到底有没有执行我们的注入代码。怎么判断服务器执没执行我们的注入代码呢?接下来就要用到 sleep() 函数。
sleep()函数就是一个延时的函数,中间参数写几就延时几秒。对于less9,不管我输入什么,页面返回总是不变,总是‘you are in……’,没有回显、报错信息、页面真假值,这个时候就要判断服务器有没有执行我的代码。我猜测他是一个数字型注入,添加sleep()函数延时5秒:
发现没有等到五秒,页面很快就有反应,说明要么查询语句错误,页面并不是数字型注入;要么服务器没执行我的代码。接下来尝试字符型注入,猜测他的闭合方式:
发现页面响应了5秒以上才加载完成,因此可以判断服务器执行了我的代码,并且是字符型注入,闭合方式是单引号。因此网站的服务器可以进行时间盲注。
接下来介绍 if() 函数。if( 条件,2 , 3 ) 函数中有三个参数,第一个是判断的条件,如果为真,则执行第二个参数中的语句;如果为假,则执行第三个参数中的语句。
把 if 语句中的判断条件写成 自己要查询的数据的ascii码的大小判断,后两个执行语句写上不同的延时,就可以通过页面的响应时间来判断要查询的值。
接下来依旧是一个个判断 不再演示
0x0C DNS外带注入
dns带外注入属于mysql注入,1
0x0D 二次注入
所谓二次注入,就是先把要注入的语句想办法存进数据库中,然后在web程序中,某些逻辑操作会调用你传入的这个语句,从而进行注入。
举个例子,比如说我注册一个账户时,我的账户名写一个注入的语句。然后我登录进账号,当我尝试修改密码时,后端代码就要找到我这个用户的账户名和旧的密码,假如查找的后端代码是这样的:
select * from users where id=' xxx ' and password=' xxx '
假如我的用户名是
admin' and updatexml(1,concat(0x7e,database()),3) --+
那么最后拼接的语句就是
select * from users where id='admin' and updatexml(1,concat(0x7e,database()),3) --+ ' and password=' xxx '
最后就是报错的回显信息中返回数据库名。
但这里要注意一点,就是在创建账户插进数据时,假如说传到后端的数据不进行魔术引号转义,因为用户名和密码肯定是字符型,不可能存在数字型的,因此假设插入的sql语句是这样的:
insert into users (username, password) values ( ' ' , ' ' );
所以当我们提交我们的用户名时,实际上后端代码里的插入语句就变成了:
insert into users (username, password) values ( 'admin' and updatexml(1,concat(0x7e,database()),3) --+ ' , ' admin666 ' );
本来后面的括号内要两个参数,但是因为闭合符和–+注释的原因,只有一个参数了,因此需要添加一个转义字符 \ 原来的插入语句变成
insert into users (username, password) values ( 'admin\' and updatexml(1,concat(0x7e,database()),3) --+ ' , ' admin666 ' );
这里转义字符的作用是将具有特殊含义的字符标识为普通字符,因此能够insert进数据库。
魔术引号转义本来是用来防止一次注入的,因为会把用户输入的引号之类的符号转义成普通字符,让注入不起作用;但是魔术引号转义却成了二次注入的必要条件。
0x0E 堆叠注入
堆叠注入简单来说就是通过添加分号 ; 来执行多条sql语句,因此必须满足一下条件:
- 服务器在访问数据库时能一次执行多条语句,比如php中的 mysqli_multi_query函数,打开后能一次执行多条语句
- 目标未对 ; 进行过滤