本文使用世界上最好的语言(PHP)针对某h站系统而开发的自动化注入工具。
首先来明确工具开发中的几个点:
1、正常页面和错误页面的区别
2、该工具自动化判断表名以及表中字段的数据长度
3、使用缓存机制(读取和写入)
4、使用php cli(命令行)模式
这里可能会有人疑问了“为啥使用php?不使用python”,因为这个注入点比较特殊,是一个302页面跳转处的注入,从数据库中查询到某个域名后会输出到页面,然后跳转,否则查询不到某个域名的情况下造成死循环,输出页面的代码如下:
<script language=”javascript” type=”text/javascript”>
document.write(‘<a id=”go” href=”http://www.52six.xyz”></a>’);
go.click();
</script>
Href参数有值的情况下为正常,否则异常
<script language=”javascript” type=”text/javascript”>
document.write(‘<a id=”go” href=””></a>’);
go.click();
</script>
异常之后就会存在死循环,因为href都为空了。
这里因为是302页面,python获取不到页面内容,而php则可以获取到内容,或许是我不擅长使用python,对php情有独钟的原因吧!
对于工具开发,必不可少的是http请求函数,php现成的有file_get_contents,相对来说,这个系统函数并不好用,因为请求不到的使用就会产生异常,当然我们不希望程序运行中突然出现异常就被中断了,然而我们要用的curl函数,下面将curl封装了一个函数:
我们先来测试一下,主要我们还是要测试是否能获取到302页面的内容,代码如下:
获取到的内容如下:
我使用———————————————–将正确页面和错误页面分割开来,href参数中有值的是正常页面,否则为异常页面
我们接着往下,因为现在大多数自动化工具都是采用接收命令行参数的方式,比如SQLMAP:
Python sqlmap.py -u “” –batch –dbms “”
也就是这样的,我们也采用这样的方式吧!下面我们来具体的实现:
Php cli模式接收命令行参数的超全局数组为$argv
首先我们要想到如何实现-u啊这些的,所以这里我们还是将获取的东西封装并且命名为GetHostId,在这个函数中我们不需要参数,我们需要获取全局变量可以使用两种形式$GLOBALS和global $argv
这里我们还是使用$GLOBALS的方式获取,因为我们最后会将这些函数封装在一个类里面,这样更加灵活,好了,我们继续吧!
根据上图我们可以知道,数组下标为0的数值是无用的,所以我们需要删掉$GLOBALS[“argv”][0]
这里第一行就将下标为0的值删掉了,因为第1个就是执行的文件名,所以直接删掉,然后进行赋值,我们接着往下来,我们命令行中需要两个参数一个为–host或者-h,另一个为–id,函数已封装完毕
这里我们当前文件接收命令行参数就有了两种形式:
程序这样做了以后就显得不灵活了,因为这里的位置不能随意调换就比较懊恼,那么我们再封装一个函数
这样获取命令行参数就可以不分先后顺序了!!这里已经获取到了域名以及id,我们继续往下,获取到域名以及id过后,我们还需要另一个参数–batch,如果这个参数存在那么我们默认使用http,否则则在命令行中获取,已将获取这部分封装成了函数:
GetInput函数是获取命令行中的值,而GetHttp是调用GetInput获取用户输入的是https还是http协议
这里获取到http或者https协议拼接到host后,我们将拼接的值进行存活的判断,我们此时封装一个is_survival函数,存活返回true,否则false,判断存活本应判断状态码,但是判断状态码又不是很严格,这里我们使用正则表达式来进行匹配,返回值如下:
我们正则所匹配的东西应该是href中的值,那么我们的正则表达式如下:
/href=”(.+)”/i
以上就是判断存活的必要条件,我们继续:
这里存活条件为href中有值的情况下存活,否则是死亡状态,死亡状态有两种情况,一种是无法访问或者输入的id值不正确,我们接着往下走
查询不到内容的时候!
这个函数中同时会自动测试五次,若五次过后依然获取不到那么自动退出,这里我们验证了是否存活,接着我们从字典中获取表名,然后测试取出的表是否存在,那么我们在本地创建一个dictionary目录,并且写入一个名为tables.txt的字典文件:
我们接着往下走,这里我们封装两个函数,一个为GetContents,另一个为GetTables
这里我们通过GetTables函数中调用GetContents函数获取字典内容,然后返回,返回到GetTables函数中,将内容以回车分割为数组并且返回数组,那么接下来我们还要再接着封装一个名为TablesSurvival的函数,然后返回值为数组,这里我们将查表的SQL拼接上去
+and+exists(select%20*%20from%20admin)
这里我们成功测试表名存在之后再封装一个名为GetColumns的函数,此函数获取dictionary目录下的columns.txt文件中的内容,并以回车分割,具体实现:
这里字段名的格式和表名的格式略有不同,格式如下:
到这里为止,我们的程序已经可以自动化测试表名以及字段名,因为前面相同功能的函数有些重复,我们这里来进行合并,比如GetColumns和GetTables,将这两个函数合并后名称改为GetTablesOrColumns:
接着我们再将TablesSurvival和ColumnsSurvival合并为TablesOrColumnsSurvival:
运行一下
代码精简了以后,还是一样的运行结果,我们接着来,这个时候我们知道了表名以及字段名称,既然是自动化工具,自然少不了测试字段中内容的长度,也将获取字符长度的代码封装成一个名为GetStringLength,返回值为数组:
这里可以看到我们返回的格式为[表名,字段长度=>字段名,字段长度=>字段名],然后我们已经得知了表名,字段名以及字段内容长度,接着我们定义一个名为SqlInjection,我们这里就不需要返回值了。
到这里呢!基础的已经写完成了,但是我们还需要一个机制,那就是缓存,我们将获取到的内容写入到文件夹下,我们的缓存目录结构如下
Data目录->域名目录->表名目录->字段文本,那么我们现在封装一个名为TextPut函数:
随后我们在GetStringLength中调用textput函数,将字段中字符长度以及字段名写入到以域名的名称命名的文本中
写入文本格式如下:
接着往下走,然后在SqlInjection函数中调用并将注入到的数据写入文件中
这样我们就成功将获取到的数据写入到文件中,但是还要进行一次判断,封装一个名为is_cache函数:
并且将SqlInjection函数增加一个cache形参,方便缓存机制
最终程序调用如上,我们还应该写一个名为DumpContent的函数,将注入到的数据打印出来
程序调用最终如下:
这里我们来测试一下缓存机制
这里我们的username字段测试到第四个字符,我们退出程序!
程序果断退出了,接着我们再来运行一下:
这里说一下为什么还是在获取第四个字符呢?因为咱们设置的是,只有读取到了写入文件之后才会跳到第五个字符,然而我们这里还没读取到第四个字符,所以还是从第四个字符开始。。。静静的等待程序执行完毕。。。
本次分享就到这里了,勿嫌弃,其实工具开发旨在思路,用什么语言都是一样的,只要可以实现就行。
自动化测试工具地址:https://github.com/only-wait/tools/tree/master/h%E7%AB%99%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7
来源:freebuf.com 2020-01-31 22:40:45 by: Gcow安全团队
请登录后发表评论
注册