<Python>编写通过调用Medusa对不同IP端口服务混合列表里的目标进行批量弱口令爆破脚本

        看着这个题目大家可能都有点要被绕晕的感觉,我来简单解释下编写这个脚本的目的和适用情况。Medusa很多人应该都用过,一款功能强大的通过并行登陆暴力破解的方式尝试获取远程验证服务访问权限的工具,支持很多主流服务的弱口令爆破。但是它所支持的爆破模式是有一定缺陷的,我记得是只有针对同一IP的不同端口服务的爆破,或者针对同一服务的不同IP目标主机的爆破,就算参数指定为list,它也是只能单一载入某个列表,Medusa工具本身并不能实现IP,端口,服务的多元组合的爆破。

       而这种情况其实十分常见的,我相信很多人在实际的渗透测试中都遇到过这样的情况。比如公司的运维部门给出了一份公司资产的不同主机相应端口服务列表需要你做弱口令爆破检查,或者针对Nmap等端口服务扫描工具批量扫描出的目标服务列表由于效率原因你想首先批量做服务的弱口令爆破测试,并且服务的默认端口往往已经被修改过了,例如类似的待测试的下图的资产列表。


       像这样的目标列表,想直接通过Medusa读入列表文件来进行服务的弱口令爆破是无法实现的。因此就编写了一个这样的脚本,通过解析目标列表文件的各项参数,来构造medusa的命令,再通过开启子进程调用Medusa工具来依次对这些目标服务开始爆破,并将中间日志文件和结果分别保存下来。这些都需要你先根据Medusa工具的输出进行分析来进行一定的解析和调整。

       脚本由两个文件构成:target_list_brute.py 和 call_medusa.py

       前者是解析目标列表参数根据需要来构成使用的medusa命令,处理结果信息并输出到指定文件以及日志文件的保存,后者主要是形成自己的参数解析规则和输出简单使用帮助,和通过打开子进程的方式以执行系统命令的方式调用medusa开始爆破。


target_list_brute.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = "sw1t0"

from call_medusa import *
import os, sys

def extract_result(run_time):
try:
        result_time_file = open(savePath + service + "/result_time.txt", 'a+')
except IOError:
        os.makedirs(savePath + service + "/brute_log")
        result_time_file = open(savePath + service + "/result_time.txt", 'a+')
    result_time_file.write(ip + " finished time: " + str(run_time) +" s \n")
    result_time_file.close()
    result_final_file = open(savePath + "/result_final.txt", 'a+')
try:
        brute_log_file = open(savePath + service + "/brute_log/"+ ip + ".txt")
for line in brute_log_file:
if line.find("SUCCESS") != -1:
                result_success_file = open(savePath + service + "/result_success.txt", 'a+')
                result_success_file.write(line)
                result_final_file.write(line)
                result_success_file.close()
else:
pass
    except:
pass
    if run_time <= 10:
        result_failed_file = open(savePath + service + "/result_failed.txt", 'a+')
        result_failed_file.write(ip + '\t' + port + '\t' + service + '\n')
        result_failed_file.close()
else:
pass
result_final_file.close()


if __name__ == '__main__':
    filepath, filename = os.path.split(sys.argv[0])
    os.chdir(filepath)
try:
for arg in sys.argv:
if arg.lower() == '--file' or arg.lower() == '-f':
                fileName = str(sys.argv[int(sys.argv.index(arg))+1]).strip('\'')
elif len(sys.argv) <= 1:
print "Usage: python target_list_brute.py [-f brutelist.txt]"
except Exception as e:
print "[-] Cheak your parametars input\n"
brute_file = open(fileName, 'r')

while 1:
        line = brute_file.readline()
if not line:
break
line_list = line.split()
        line_list.remove(line_list[3])
        ip = line_list[0]
        port = line_list[1]
        service = 'smbnt' if line_list[2] == 'netbios' else line_list[2]
        medusa_command = make_medusa_command(ip, port,'username.txt', 'top800.txt', service)   # 因为没有单独读字典,所以字典文件需放在和脚本同一路径下
run_time = run(medusa_command)
        extract_result(run_time)


call_medusa.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = "sw1t0"

import subprocess
import shlex
import sys
import re
import datetime
import os
import signal
import time

global savePath
savePath = "/root/result/test/"

def help():
print "Usage:  python call_medusa.py  [-t ip] [-p port] [-U --usernamelist] [-P --passwordlist] [-h]"
sys.exit(1)

# 传递参数构造medusa命令
def make_medusa_command(ip, port, userlist, pwdlist, service):
if not os.path.exists(savePath+service+"/brute_log/") :
        os.makedirs(savePath + service + "/brute_log")
    command = "medusa -h " + ip +" -n " + port + " -U "+ userlist + " -P " + pwdlist + " -M " + service + " -f -e ns -t 30 -O " + savePath + service + "/brute_log/" + ip + ".txt"
args = shlex.split(command)
return args

# 传递构造好的命令作为参数调用medusa开始爆破并返回运行时间
def run(args):
print "Start brute attacking...\n"
start_time = datetime.datetime.now()
try:
        file_out = subprocess.Popen(args)
except:
print "Call medusa failed!"
file_out.wait()
    end_time = datetime.datetime.now()
    run_time = (end_time-start_time).seconds
return run_time

if __name__ == '__main__':
# 参数解析
try:
for arg in sys.argv:
if arg.lower() == "-t" or arg.lower() == "--target":
                ip = str(sys.argv[int(sys.argv.index(arg))+1])
elif arg == "-p" or arg.lower() == "--port":
                port = sys.argv[int(sys.argv.index(arg))+1]
elif arg == "-U" or arg.lower() == "--usernamelist":
                usernamelist = sys.argv[int(sys.argv.index(arg))+1]
elif arg == "-P" or arg.lower() == "--passwordlist":
                passwordlist = sys.argv[int(sys.argv.index(arg))+1]
elif arg == "-M" or arg == "--service":
                service = sys.argv[int(sys.argv.index(arg))+1]
elif arg.lower() == "-h" or arg.lower() == "--help":
                help()
elif len(sys.argv) <= 1:
                    help()
except Exception as e:                                          # 针对针对-h的异常捕获问题未解决
print "[-] Cheak your parametars input\n"

medusa_command = make_medusa_command(ip, port, usernamelist, passwordlist, service)
    run(medusa_command)
print "IP:" + ip + " port:" + port + " service:" + service + " brute finished."


       这个脚本比较简陋,只是提供一个处理此类工具调用问题的思路,还存在许多问题有待解决。主要问题有以下几个:

  1. 去看下Medusa的关于多线程的参数说明可以知道,在这里我为了提高爆破速度只有在构造工具命令的时候就将-t 30线程数写死在里面,而看了我的代码逻辑的同学可以知道,这里的多线程其实是Medusa跑单个目标的时候针对用户名密码字典文件开启的多线程,而不是我的调用代码针对多个目标而开启的多线程,目标其实是依次调用Medusa来进行爆破的(意味着目标列表上一条目标记录的爆破没有完成就不会开始下个目标的爆破),不知道能否将目标也写成多线程,如果能,会不会造成资源调用上的冲突或者其他一些效率上的问题?

  2. 在这里为什么我处理目标爆破结果的方式是去找Medusa每个日志文件并通过正则匹配它们的输出再最后重新将汇总结果再输出到一个文件呢?(我知道这样有很多的冗余文件并且十分麻烦,反复打开读写文件还大大降低了效率。)原因是这样的,我之前采用的是管道的方式是直接去读Medusa的标准输出来筛选我需要的爆破结果,把是结果的行输出到文本,这样看起来是不是就很简洁高效,一边在跑一边就读结果输出。结果这样的处理方式会出现的问题是,因为Medusa爆破输出到标准输出(stdin)的速度非常快,而我按行从标准输出读再通过正则筛选需要结果的速度远远低于它输出的速度,因此没跑几条程序就会卡住不动(这个时候我才意识到标准输出的存储也是有大小的,阻塞了是不是就卡死了?)。那这样的问题要怎么解决,或者除了像我那样输出到日志文件再依次筛选的方法还有什么更简洁的方法可以处理此类问题?

  3. 这个脚本跑SSH服务的爆破的时候,总是会不知道为什么就停下来不跑了,是随机不确定的,每次放在那跑几天去看日志卡在某条目标记录上就不跑了,但是都是出现在SSH服务的爆破上,其他已跑过的服务暂时正常。不知道是工具本身的问题还是脚本哪里没处理好造成的问题。等待高人解答。

  4. 同理,Hydra应该也可以实现此类的调用脚本,命令构造和结果的处理可能有所不同需要调整,期待看到其他同学Hydra版本的调用脚本。


       欢迎感兴趣的小伙伴来和我交流讨论,不吝赐教。

        

    


评论

© Sw1t0range | Powered by LOFTER