Python 基于字典的目录扫描工具(命令行版)

6 篇文章 0 订阅
5 篇文章 0 订阅

之前做过一个简单的目录扫描工具,是一个简单的原理代码。这次将其完善,目的为了使用cmd操作。先看一下做出来的效果。三个参数,分别是url 线程数 字典名,目标网址我的虚拟机在这里插入图片描述

也不是很难,只是中途出现的小问题比较多。扫描出存在目录要将其记录然后输出,这整个过程多个线程必须使用一个list来记录扫描出来的目录,在这一块绕了很长时间,到了解释代码的时候看一下。

1~整体结构

首先根据需要设置参数的个数,目录扫描,要有 1.目标网址 2.目录字典 3.线程数。多线程的考虑是因为若有庞大的字典只能使用并发增加扫描速率(其实没卵用,扫描速度快大多数网站会直接拦截)。所以一共需要三个参数。初步假设开始运行的命令为
python Dirbute.py -u url -t number -d filename

三个核心函数。start(),multi_scan(url,threads,dic),scan(url,directory)
依次说一下三个函数是干嘛用的

  • start()
    逻辑中的开始部分。使用python的命令行模块,功能就是接收用户输入的命令,获取参数值。
  • multi_scan(url,threads,dic)
    读取字典文件,根据用户定义线程数的值,计算每个线程应该执行字典多少行,调用scan开始线程,线程结束后列举扫描出的目录。
  • scan(url,directory)
    每个线程执行的具体过程,实现扫描,向目标发送请求包。

2~各部分

2.1~ def start()

第一步是实现和命令行模块。关于这个我学到了两种方式,都解释一下。

  1. getopt
    第一种方式是使用getopt和sys这两个库。这种方法对参数少的工具也比比较友好,但是如果参数多了的话,会很麻烦。先看一这部分代码。
def start():
    if len(sys.argv) == 7:
        opts,args = getopt.getopt(sys.argv[1:],"u:t:d:")
        # opts的值是一个list,list的内容是多个元组,元组有对应的键和值
        for key,value in opts:
            if key == "-u":
                url = value
            elif key == "-t":
                threads = value
            elif key == "-d":
                directory = value
        multi_scan(url, threads, directory)
    else:
        print("error!")
        sys.exit()

有两个地方我弄了好长时间才明白,说明一下。
首先解释第一个if。if len(sys.argv) == 7:
sys.argv[] 是接收到的参数列表,这个接收到的参数不止上面说的u t d三个参数。
sys.argv[]说白了就是一个接收程序外部输入进来的东西,比如我们在cmd中输入
python Dirbute.py -u “http://192.168.17.130” -t 2 -d “dic.txt”
除了python外,Dirbute.py 、-u 、“http://192.168.17.130”、 -t、 2 、-d 、“dic.txt” 这7个东西,python都会接收,并且他们会储存在sys.argv这个list中。所以开头if判断 sys.argv的长度是不是7,是7的话说明用户输入命令格式正确,然后进入传值,否则提示出错并退出。

然后是下一句opts,args = getopt.getopt(sys.argv[1:],"u:t:d:")
opts列表存储的是opts(选项,选项值)这样的元组组成的列表。args列表存储的是不在getopt方法内的参数的值,即不属于格式信息的剩余的命令行参数。举个例子

opts,args = getopt.getopt(sys.argv[1:],"u:t:d:")
print(opts)
print(args)

我在终端输入python testgetopts.py -u “123123” -t 2 -d “sdf” more hahah
输出结果为

[('-u', '123123'), ('-t', '2'), ('-d', 'sdf')]	#opts的值
['more', 'hahah']	#args的值

应该解释明白了。
这两点之后,下面的if就是简单的根据参数判断获取对应的值,url 、threads和directory,然后传给multi_scan

  1. optparse
    第二种方式是引用optparse库。这种方式理解起来也好理解,但是要比第一种方便很多。getopt传值还需要很多的if判断,optparese就不用。先看一下代码
def start():
    try:
        parse = optparse.OptionParser()
        parse.add_option("-u", "--url", dest="url", type="string", help="<input url>", default="noURL")
        parse.add_option("-t", "--threads", dest="threads", type="int", help="<input threads number>", default=1)
        parse.add_option("-d", "--dir", dest="directory", type="string", help="<input directory file name>",default="dic.txt")
        (options, args) = parse.parse_args()
        multi_scan(options.url, options.threads, options.directory)
    except:
        print("参数输入有误!")
        (options, args) = parse.parse_args(['-h'])

然后解释一下add_options的各个参数的意义
以这句为例parse.add_option("-d","--dir",dest="directory",type="string",help="<input directory file name>",default="dic.txt")
“-d” “–dir” 是添加的命令行要输入的参数,-d是短参数,–dir是长参数,写命令时这俩写一个就够了,比如我可以写
python Dirbute.py -u “http://192.168.17.130” -t 2 -d "dic.txt"
也可以写成
python Dirbute.py -u “http://192.168.17.130” -t 2 –dir "dic.txt"

然后是dest=,这里dest=directory ,可以认为python申请了一个新的变量叫directory ,命令行写过-d “dic.txt” 后,directory的值就为"dic.txt" 。之后写(options, args) = parse.parse_args()将设定的命令行参数都添加进options,下面要传值的时候,直接用options.directory 就可以

type= 是规定参数值的类型,这里type=“string” 就意味 -d这个参数后面写的值是字符串。 即options.drectroy的值为字符串类型

help=,这个写的是帮助,当输入命令-h时,会弹出各种参数的说明,help就是对当前参数的说明,就是我写在<>里的文字
在这里插入图片描述

2.2~ multi_scan(url,threads,dic)

这个函数的内容有一点点复杂。没有什么不明白的函数,都是一些逻辑问题。我把解释写在注释里,理一理就下来了

'''这个list用来记录爆破出来的目录,他必须是一个全局变量,因为有多个线程在使用它,并且是在scan(url,directory)函数中使用'''
exist_dir = []
def multi_scan(url,threads,dic):
	'''result_list用来存储每个线程所要执行的从字典获取的文本,它是一个元素为list的list
	【0】储存第一个线程执行的内容,【1】储存第一个线程执行的内容 以此类推'''
    result_list = []
    #打开文件
    with open(dic,"r",errors='ignore') as file:	# "r"只读,ignore是为了忽略识别gbk编码的中文
        #文件读行
        dic_list = file.readlines()
        #根据行数计算多线程的数量 每个线程需要执行的行数 = 字典行数/线程数
        threads_readline_num = math.ceil(len(dic_list)/int(threads))	#math.ceil是向上取整,如果有余数(即字典行数不能平均分配给线程),那么只能让threads_readline_num多不能让它少
        #i用来记录分配到第几行
        i = 0;
        #临时存储列表,每次将其加进结果列表然后清空
        temp_list = []
        threads_list = []
        for line in dic_list:
            i = i+1
            if i % threads_readline_num == 0:
                temp_list.append(line.strip())  #strip用来去掉字符串中的/n和/r
                result_list.append(temp_list)
                temp_list = []
            else:
                temp_list.append(line.strip())
        #如果计算最后一轮有剩余,则这些剩余的为最后一组线程所需要的执行的
        if temp_list:
            result_list.append(temp_list)

    # 向多线程列表中添加线程
    for line in  result_list:
        # threading.Thread(target=,args=) target表示线程函数即scan(url,directory),args表示需要传递的参数
        threads_list.append(threading.Thread(target=scan,args=(url,line)))

    #启动每个线程
    for t in threads_list:
        t.start()	

'''
从这里开始下面的部分都是在线程结束后开始执行的,期间每个线程都在执行scan(url,directory)函数,先去看下一部分
'''


    #等待每个进程结束
    '''
    join(timeout) 方法的功能是在程序指定位置,优先让该方法的调用者使用 CPU 资源
    没有指定具体的 timeout 参数值,意味着如果程序想继续往下执行,必须先执行完 t 线程。
    '''
    for t in threads_list:
        t.join()
    print("*" * 20 + "扫描结束" + "*" * 20)
    
    #在线程执行过程中,已经将爆破出来的目录存到了开头定义的全局变量exist_dir中
    if exist_dir:	#如果存在爆破出来的目录
        print(url + " 存在以下目录:")
        for i in exist_dir:
            print(i)
    else:	#没有爆破出来目录
        print(url + " 中没有字典中的目录")

如果挨着一句一句念,应该是可以看懂代码的。

2.3~ scan(url,directory)

这部分比较简单,就是用requests发送请求。
如果响应体的状态码是200,则说明存在当前爆破的目录,然后将这个目录添加进之前定义的全局变量exist_dir中

def scan(url,directory):

    headers = {
        "User-Agent": get_UA()	#这个函数的功能是随机生成一个ua头
    }
    for line in  directory:
        newurl = url + line.strip()
        response = requests.get(newurl,headers)
        print(newurl + "    响应: " + str(response.status_code))
        if response.status_code == 200:
            exist_dir.append(newurl)
        time.sleep(0.5)

放一下随机生成ua头的代码,这部分可有可无,当时为了瞎玩随便搞的。这部分可以不要

user_agent = [
    "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
    "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0",
    "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3; rv:11.0) like Gecko",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
    "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
    "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
    "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
    "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
    "Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
    "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
    "Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
    "MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
    "Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10",
    "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13",
    "Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+",
    "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0",
    "Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)",
    "UCWEB7.0.2.37/28/999",
    "NOKIA5700/ UCWEB7.0.2.37/28/999",
    "Openwave/ UCWEB7.0.2.37/28/999",
    "Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999",
    # iPhone 6:
    "Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25",

]
#随机获取一个UA头
def get_UA():
    return random.choice(user_agent)

3~ 运行

三部分代码都调试成功后,全局中直接调用start函数。

start()

总结

整体这个多线程爆破运行起来就是,先是star()接收命令行输入的参数以及参数值,然后传给multi_scanmulti_scan根据字典和线程数计算出每个线程该执行的量后,线程执行线程函数scan,在此期间,multi_scan这个函数还没有执行完毕,只是在等各个线程执行scan函数,scan函数在期间会将爆破出来的目录记录到exist_dir这个全局变量当中,当所有线程(所有scan函数)都执行完毕后,再接着执行multi_scan剩下的部分,即输出爆破出来的目录。

然后还有一点不对劲的地方就是,在脚本运行前如果想看帮助,直接输入-h会自动判断为我输入参数有误,然后抛出异常,于是有了下面的效果在这里插入图片描述
不过不影响工具的使用。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值