之前在 Freebuf 的文章 "关于渗透测试工具开发架构探讨",读者有提出,具体应该怎么写呢?第一步做什么?第二步做什么?现在我觉得自己有一些体会了,也自己觉得自己还是有把握把这个事情解释清楚的,那么我就来写一些东西,来讲一下我很喜欢的一种敏捷开发方法: TDD。
做过一些软件开发的朋友们可能会知道这个东西 TDD(Test-Driven Development),诚然我自己之前的开发也并不是 TDD,而是传统的开发,写文档,开发,写文档的 "瀑布" 开发。
当然自己也是开始尝试进行一些系统的 Web 开发训练的时候深深体会到了 TDD 开发的优势,想到了 Freebuf 这边还有一个坑没填,那就来结合现在的经验谈一下这个事情吧!
Test-Driven Development 顾名思义,就是利用测试来驱动 (督促) 开发进展中文名也就是测试驱动开发,简称 TDD。是一种敏捷开发模式。当然这么说大家会觉得非常抽象。那我就通俗的来解释一下,可能有不恰当的地方,希望读者在评论区指出:众所周知瀑布式开发一般来说写代码 - 写文档 - 写代码 - 写文档 -… 。每一个周期与周期之间唯一的衔接就是文档;但是 TDD 是测试驱动的,就是先有测试用例 (testCase),然后再有代码(听起来是不是非常的奇怪),其实想想非常的常见,不就是现有需求后代码么?没错啊,TDD 可以说是把这个大需求,直接反映在测试上,然后根据测试用例来开发代码,写出满足测试用例的最少的代码。
这样做有什么好处呢?时刻检查自己的代码是不是符合自己的目标,如果符合要求可以通过,如果不符合,则不能通过。除此之外,仔细想想,在未完成开发的时候,你可以随时停下你的开发,下次只需要运行测试用例,查看哪里没有通过,就可以继续之前的代码进行开发。当然,这只是其中的两条显而易见的好处。
理所当然的,作为一个渗透测试人员,需要一些短平快的脚本的时候,是根本不需要使用 TDD 方法去写代码的,显然啊,TDD 方法开发的代码量其实要比原来的开发要大(主要体现在有时候需要写大量的测试代码),这算是 TDD 一个缺点。不过按照我个人的体验来说,TDD 方法去开发工具的时候,效率不止是提高了一点,就算写了大量的测试代码,但是仍然是效率高于原来。
提到 HTTP 代理大家都还是挺熟悉的。那么既然要学习渗透工具开发,不妨就来做一个这样的实用的小玩意。在学习 Python 之余坚持完成了,还可以平时自己使用一波,还是挺不错的。
那么关于 HTTP 代理呢?大家应该都并不是特别陌生,我们经常会使用到 HTTP/HTTPS 代理去完成隐藏自己的真实 IP(隐藏自己真实信息这个的可行性我们暂且不谈),那么大家会发现,我们经常上网寻找一些公开的代理网站,(比如快代理啊,xici 之类的)
1. Python2.7+
2. Pylint 用于检查代码规范
3. unittest 用于 TDD 测试驱动开发
说实话,基本 Python 基础知识和 unittest 的基本使用方法我就不介绍了,如果你在阅读这篇文章,就说明你至少还有有一些 Python 基础的。
哦,除此之外,建议在写代码的时候使用 。
(嗨呀,首先是不是要写 Hello World 啊?)
当然,既然我们想要使用 TDD 的方法来开发,我们首先当然需要写一个测试用例。(暂且撇开测试套件什么的,我们就最简单的做一个测试用例)。
那么要写测试用例了,我们首先得知道,我们想要做出的功能是怎么样的?嗯… 既然是代理扫描器嘛,首先我们得知道怎么去扫描 HTTP 代理,其实并不用说的太玄乎,有个最简单的办法就是,我们就去把它当代理用,如果成功了,那么就说明这个 IP 的端口开启了代理模式,如果失败了,那就说明这个端口并不能作为代理来使用。
那么事情就简单了,我们想要的第一个功能就是使用 Python 完成检测目标 IP:PORT 是否是可用代理。
那么就动手吧!
当然我们需要先建立一个文件,根据功能,就叫 check_proxy.py 吧!在新建文件之后,我们需要开始编写测试用例了。(什么?为什么不是编写代码?)毕竟我们尝试的是 TDD 方法开发工具,显然首先写个函数什么的显然并不是符合我们的初衷。那么,我们就开始吧!
- # ! /usr/bin / env python#coding: utf - 8 """
- Author: --<VillanCH>
- Purpose: check proxy available
- Created: 2016/10/31
- """import unittest########################################################################class CheckProxyTest(unittest.
- case.TestCase):
- """Test CheckProxy"""pass
- if __name__ == '__main__': unittest.main()
在创建了这个段代码之后,显然,大家看到我
应该就知道接下来要编写测试用例了吧。那么 unittest 应该怎么使用我觉得我不用向大家解释太多,直接看代码更加直观。
- import unittest
在有了测试文件类之后呢,我们并不着急编写我们的 CheckProxy 的功能代码。我们不妨设想一下我们这个类的功能:姑且就是我们输入一个 IP:PORT 这个形式,运行模块,如果可以用作代理,返回结果表明这个地址可以用作 HTTP 还是 HTTPS 代理,那么我们就规定一下返回的格式吧:
- {
- 'result': True,
- 'proxy': {
- 'http': 'xxx.xxx.xxx.xx:port',
- 'https': 'xxx.xxx.xxx.xx:port'
- }
- }
如果没有 HTTPS 代理的功能的话那就是:
- {
- 'result': True,
- 'proxy': {
- 'http': 'xxx.xxx.xxx.xx:port',
- }
- }
当然这是理想情况,如果不是代理呢?
- {
- 'result': False,
- 'proxy': None
- }
嗯这样的话,我们就可以根据这个来写第一个测试用例了:
我们删除
然后添加
- pass
方法:
- def test_xxxxx
- def test_check_ip(self) : '''
- result should be
- {
- 'result ':True,
- 'proxy ':{
- 'http ':'xxx.xxx.xxx.xx: port ',
- 'https ':'xxx.xxx.xxx.xx: port '
- }
- }
- '''#创建想要测试的实例master = CheckProxy('78.6.5.45:8080') result = master.test()##对结果进行测试#测试结果必须是个dict类型self.assertIsInstance(result, dict)#必须有一个result和proxy的键self.assertTrue(result.has_key('result')) self.assertTrue(result.has_key('proxy'))#result键对应的值必须是一个bool类型self.assertIsInstance(result['result'], bool)#断言测试result['proxy']的类型
- if result['result'] : self.assertIsInstance(result['proxy'], dict)
- else: self.assertIsNone(result['proxy'])
显然我们看到我们的测试代码,如果能跑通的话,也就一定是我们想要的结果了(至少接口是符合我们需求的)。那么我们切换到命令行来执行这个测试用例。
- E === ===================================================================ERROR: test_check_ip(__main__.CheckProxyTest)----------------------------------------------------------------------Traceback(most recent call last) : File "check_proxy.py",
- line 26,
- intest_check_ip master = CheckProxy('78.6.5.45:8080') NameError: global name 'CheckProxy'is not defined----------------------------------------------------------------------Ran 1 test in 0.000s FAILED(errors = 1)
显然不用过多解释大家也知道这个是失败了,原因是什么呢?我们根本没有建立 CheckProxy 方法或者 CheckProxy 类啊,当然会失败,那么我们看到 Traceback 中告诉我们 CheckProxy 没有定义,那么我们肯定要去先解决这第一个问题了吧。 所以我们新建我们的类:
- class CheckProxy(object) : """Check Proxy
- Attributes:
- target: A str, the target you want to check.
- Example: 44.4.4.4:44"""#----------------------------------------------------------------------def __init__(self, target) : """Constructor"""self.target = target
然后我们再来测试我们的代码
:
- python check_proxy.py
- E === ===================================================================ERROR: test_check_ip(__main__.CheckProxyTest)----------------------------------------------------------------------Traceback(most recent call last) : File "check_proxy.py",
- line 43,
- intest_check_ip result = master.test() AttributeError: 'CheckProxy'object has no attribute 'test'----------------------------------------------------------------------Ran 1 test in 0.000s FAILED(errors = 1) * ***
显然又失败了,原因当然 unittest 他已经告诉我们了: 没有一个叫 test 的参数,当然啊我们毕竟之写了 CheckProxy 的构造器,我们并没有创建一个叫 test 的方法。所以我们又需要修改我们的代码…
我觉得到现在了,读者应该觉得挺累的也挺无聊的,不就是这个小东西么?我分分钟写出来啊。而且并不用写这么烦人的测试代码,而且很蠢的一次一次去测试。事实上我在实际做的时候也不会这么蠢,这么慢去写,当然熟练的话,可以根据测试用例直接写出符合规范的代码。嗯,确实是这样。但是既然是刚开始学习这项新的 "技术" 为了领会思想,还是乖乖照做吧。
经过上面各种各样循环,我们终于测试成功了,当然下面的代码可能并不是特别光荣。哈哈 但是至少我们知道成功是什么滋味了对吧?
- .----------------------------------------------------------------------Ran 1 test in 0.000s OK
我们的代码现在是什么样子呢?
- class CheckProxy(object) : """Check Proxy
- Attributes:
- target: A str, the target you want to check.
- Example: 44.4.4.4:44"""#----------------------------------------------------------------------def __init__(self, target) : """Constructor"""self.target = target def test(self) : '''Test the self.target whether is a http proxy addr
- Returns:
- A dict: if the result is True:
- Example:
- {
- 'result ':True,
- 'proxy ':{
- 'http ':'xxx.xxx.xxx.xx: port ',
- 'https ':'xxx.xxx.xxx.xx: port '
- }
- }
- if the target isn't the http proxy addr,
- the result['proxy'] will be None '''
- result = {}
- result['result '] = False
- result['proxy '] = None
- return result'
什么嘛!你这明明是欺骗 unittest 获得的通过测试,一点都不诚实。
为什么这么做呢?简单来说我们先保证接口是统一的,然后再对细节进行一些填充,可以很理所当然的增加新的测试用例,完成更加强大的功能。因为很有可能我们不让这个测试通过的话,会干扰我们后面很多选择,让我们觉得,这个 CheckProxy 的每一个功能似乎都与 test 有关,实际上啊,我们第一个测试用例是在测试接口啊。所以大胆放心吧,我们后面当然会完善。
当然,我们先尽量让它通过,然后在来继续写一些测试用例来完成细节,那么我们可以开始下一个测试用例了。
之前的算是熟悉我们需要用到的方法了,接下来我们要做的,肯定就是完成这个测试用例的功能了吧~
那么接下来怎么做呢?继续完善 CheckProxy.test() 方法还是?当然我们要继续补充我们的测试用例啊。
那么问题就来了,我们如果想验证我们的代理检测工具是不是可以正常工作,我们当然需要找到一个可以使用的匿名代理对不对?那么这些东西我们去哪里找呢?当然,笔者自然不会打没有准备的仗,哈~ 其实这次讲的这个工具,是我之间就做过的一个代理搜集 (扫描工具),当时做的有不成熟的地方,那么现在就准备重构一下,带领大家过一遍一个工具的开发流程。所以公开的代理,我就不用满大街去寻找了,就随便从我的旧版的 pr0xy 中寻找一些出来吧。
- Pr0xy - shell#proxy show proxy: {
- u 'http': u 'http://120.52.72.23:80'
- }
- check_time: Wed Aug 03 23 : 31 : 44 2016 proxy: {
- u 'http': u 'http://103.27.24.238:80'
- }
- check_time: Wed Aug 03 23 : 31 : 44 2016 proxy: {
- u 'http': u 'http://50.31.252.54:8080',
- u 'https': u 'https://50.31.252.54:8080'
- }
- check_time: Wed Aug 03 23 : 31 : 44 2016 proxy: {
- u 'http': u 'http://82.196.10.29:80',
- u 'https': u 'https://82.196.10.29:80'
- }
- check_time: Wed Aug 03 23 : 31 : 44 2016 proxy: {
- u 'http': u 'http://119.188.94.145:80',
- u 'https': proxy: {
- u 'http': u 'http://108.59.10.129:55555',
- u 'https': u 'https://108.59.10.129:55555'
- }
- check_time: Wed Aug 03 23 : 31 : 44 2016 proxy: {
- u 'http': u 'http://14.161.21.170:8080',
- u 'https': u 'https://14.161.21.170:8080'
- }
- check_time: Wed Aug 03 23 : 31 : 44 2016 proxy: {
- u 'http': u 'http://179.242.95.20:8080'
- }
当然笔者对上面的代理不保证永久的可用性,以上代理收集途径均为公共代理。很有可能在大家看到这篇文章的时候,上面代理已经没剩下几个活着的了。
自然我也是有办法验证自己的 IP 是不是被很好的隐藏了,我们平时怎么做的呢?就是打开百度,输入 IP:
那么如果成功了的话
大家看到红色箭头了么?我们想要在程序中使用这项功能,不妨可以去 ip138 中寻找一下接口看能不能使用~
所以经过一番查找,我们发现了接口是 至于这个网站是干什么的不妨大家自己去看一下。一切顺利,于是我们先添加测试代码吧!
- def test_ip_check_function(self) : #首先测试一个正确的实例(已知一定是个代理)addr = '108.59.10.129:55555'master = CheckProxy(target = addr) result = master.test()#在前一个例子中我们验证了接口#那么在这里我们只需要验证一下#一定是完成了代理检测的,而且成功了proxy = result['proxy'] self.assertTrue(proxy.has_key('http'))
没错我们新添加这么一点东西,运行一下看一下结果 (不用说,肯定会出错的,但是我们先看一下在说):
- .E === ===================================================================ERROR: test_ip_check_function(__main__.CheckProxyTest)----------------------------------------------------------------------Traceback(most recent call last) : File "check_proxy.py",
- line 92,
- intest_ip_check_function self.assertTrue(proxy.has_key('http')) AttributeError: 'NoneType'object has no attribute 'has_key'----------------------------------------------------------------------Ran 2 tests in 0.001s
我们看到了,断言错误了:这是当然嘛我们还没有做检测呢,那么,我们接下来要去完善 CheckProxy.test() 这里的方法了。
- #----------------------------------------------------------------------def test(self) : '''Test the self.target whether is a http proxy addr
- Returns:
- A dict: if the result is True:
- Example:
- {
- 'result ':True,
- 'proxy ':{
- 'http ':'xxx.xxx.xxx.xx: port ',
- 'https ':'xxx.xxx.xxx.xx: port '
- }
- }
- if the target isn't the http proxy addr,
- the result['proxy'] will be None '''
- result = {}
- result['result '] = False
- result['proxy '] = self._check_ip()
- return result
- #----------------------------------------------------------------------
- def _check_ip(self):
- """Check IP return the proxy or None
- Returns:
- A dict or None: if the result is True(the addr can be a proxy)
- the result is the dict like {'http ':'http: //xx.xx.xx.xx:xx',
- 'https': 'https://xx.xx.xx.xx:xx'
- }
- And
- if the https proxy can 't be used the key named 'https ',
- Well, if the result is False(the addr can't be used as a proxy) the result is None """
- check_ip_http = 'http://1212.ip138.com/ic.asp'
- check_ip_https = 'https://1212.ip138.com/ic.asp'
- addr_proxy = {}
- addr_proxy['http'] = 'http://' + self.target
- addr_proxy['https'] = 'https://' + self.target
- result = {}
- http_rsp = requests.get(check_ip_http, proxies=addr_proxy, timeout=5)
- if self.target in http_rsp.text:
- result['http'] = 'http://' + self.target
- https_rsp = requests.get(check_ip_https, proxies=addr_proxy,
- verified=False, timeout=5) # close https verify
- if self.target in https_rsp.text:
- result['https'] = 'https://' + self.target
- if result.has_key('http') or result.has_key('https'):
- return result
- else:
- return None"
这样写出来的代码实际上还是非常漂亮的对吧?基本符合 Google 开源 Python 规范,尝试一下 Pylint
- C: \xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > pylint check_proxy.py No config file found,
- using
- default configuration * ************Module pr0xy.lib.check_proxy C: 15,
- 0 : Trailing whitespace(trailing - whitespace) C: 17,
- 0 : Trailing whitespace(trailing - whitespace) C: 24,
- 0 : Trailing whitespace(trailing - whitespace) C: 25,
- 0 : Trailing whitespace(trailing - whitespace)...............Messages--------+-----------------------+------------+|message id | occurrences | +=======================+============+|trailing - whitespace | 10 | +-----------------------+------------+|too - few - public - methods | 1 | +-----------------------+------------+Global evaluation-----------------Your code has been rated at 7.56 / 10(previous run: 7.56 / 10, +0.00)
大致看了一下 trailing-whitespace 指的是结尾无意义的空格,too-few-public-methods 指的是公共方法太少了,实际上无意义空格这个是我的 IDE 导致的,应该在 IDE 的 preferences 中可以设置改掉的,嗨呀可是这导致扣了好多分,只能打 7.56 分,笔者好懒啊~
来运行一下测试用例
- EE === ===================================================================ERROR: test_check_ip(__main__.CheckProxyTest)----------------------------------------------------------------------Traceback(most recent call last) : File "check_proxy.py",
- line 100,
- intest_check_ip result = master.test()...ConnectTimeoutError( < requests.packages.urllib3.connection.HTTPConnection object at 0x000000000333BB00 > , 'Connection to 78.6.5.45 timed out. (connect timeout=5)')) === ===================================================================ERROR: test_ip_check_function(__main__.CheckProxyTest) test
- function----------------------------------------------------------------------Traceback(most recent call last) : File "check_proxy.py",
- line 121,
- intest_ip_check_function...object at 0x00000000033C90B8 > ,
- 'Connection to 108.59.10.129 timed out. (connect timeout=5)'))----------------------------------------------------------------------Ran 2 tests in 10.032s FAILED(errors = 2)
太惨了,全失败,自己看一下原因,好像都是因为 timeout, 那么,我们就去处理一下异常好了,顺便再看一下,好像需要调整一下 timeout 的时间,那么我们就顺手改一下 testCase 什么的。
- .E === ===================================================================ERROR: test_ip_check_function(__main__.CheckProxyTest) test
- function----------------------------------------------------------------------Traceback(most recent call last) : File "check_proxy.py",
- line 136,
- intest_ip_check_function self.assertTrue(proxy.has_key('http')) AttributeError: 'NoneType'object has no attribute 'has_key'----------------------------------------------------------------------Ran 2 tests in 12.039s FAILED(errors = 1)
好像又不能通过了,看下错误,别慌张,我们仔细看一下这个错误的原因,这显然就是这个代理已经失效了,所以我们换个能用的代理,总之通过就好了~
最后我们测试通过,这是现在的 check_ip 代码
- def _check_ip(self, timeout) : """Check IP return the proxy or None
- Returns:
- A dict or None: if the result is True(the addr can be a proxy)
- the result is the dict like {'http':'http://xx.xx.xx.xx:xx',
- 'https':'https://xx.xx.xx.xx:xx'}
- And if the https proxy can't be used the key named 'https',
- Well, if the result is False(the addr can't be used as a proxy)
- the result is None"""headers = {}
- headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'check_ip_http = 'http://1212.ip138.com/ic.asp'check_ip_https = 'https://1212.ip138.com/ic.asp'addr_proxy = {}
- addr_proxy['http'] = 'http://' + self.target addr_proxy['https'] = 'https://' + self.target result = {}
- http_rsp = ''
- try: http_rsp = requests.get(check_ip_http, proxies = addr_proxy, timeout = timeout, headers = headers).text except: pass
- if self.ip in http_rsp: result['http'] = 'http://' + self.target https_rsp = ''
- try: https_rsp = requests.get(check_ip_https, proxies = addr_proxy, verify = False, timeout = timeout, headers = headers).text#close https verify except: pass
- if self.ip in https_rsp: result['https'] = 'https://' + self.target
- if result.has_key('http') or result.has_key('https') : return result
- else: return None
大家看 已经要比原来的代码看起来美观很多了是不是?下面是我们的测试结果:
- C: \xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx > python check_proxy.py..----------------------------------------------------------------------Ran 2 tests in 6.030s OK
可是适当修改测试用例,添加一些说明:
- API test API test success ! .
- function test
- function test success ! .----------------------------------------------------------------------Ran 2 tests in 7.545s OK
当然 看到最后的测试成功,大家还是有很多细节需要改动的,那么经过轮番的测试,修改循环,我的这个检测模块已经感觉不错了,至少现在是满足我们所有的东西了。 具体的代码呢,可以见 github 地址。
那么收工之后呢,既然我们要做代理扫描器,针对单个的 IP:PORT 的地址进行代理检测已经完成了,那么我们接下来要考虑的事情,就是,提供一个大的 IP 段,然后可以对这个 IP 段中的 IP 进行代理检测,然后再把结果汇总,看起来很简单么?是吧!
其实并不是这样的,我们要考虑的问题还有特别多,比如:并发我们怎么处理?我们如何筛选高质量的 IP 段?(至少我们得知道国外和国内的 IP 段不同对吧?)我们扫描到的代理,如何使用?还有,直接扫描太慢了,有更快的方法么?
当然,上面的问题,我们在后面解决,包括针对渗透测试 Python 编程的各种细节,我都会在后面讲给大家,希望对大家有帮助~
当然今天讲了看起来很多的东西,其实也并没有多少,同样大多东西需要读者亲手去做了才会有体会。那么我反过来再来说一下之前有读者问到的问题:真正要开始写一个工具的时候,第一步或者说第一行代码应该是什么呢?那么现在,大家应该懂了吧,从测试用例开始一个功能模块一个功能模块开始写。
当然如果想要详细了解 TDD 这种开发思想的话,你可以去看一下《Python Web 开发:测试驱动方法》这本书,很详细的讲了 TDD 的开发思想,当然我只是把他用在了渗透工具的开发上了。
那么当然,这一篇文章显然还没有结束,关于 Python 编程的一大堆东西,准备在下一篇文章中分享给大家。
笔者水平有限,希望能对大家有所帮助。
所有代码地址:GITHUB 地址:
来源: http://www.tuicool.com/articles/nQrmuiU