"""
    Created on September 14, 2015
    
    :Program: WAFNinja
    :ModuleName: fuzzer
    :Version: 1.0
    :Revision: 1.0.0
    :Author: Khalil Bijjou
    :Description: The purpose of the fuzz function is to automate the reverse-engineering of the WAF's rule set by sending various fuzzing strings and analyse what is blocked and what not. 
                  In contrast to reverse-engineer the rule set manually, this function saves time, enhances the result by using a very broad amount of symbols and keywords and displays 
                  results in a clear and concise way. The result is either displayed in form of a table directly in the CLI or written to a HTML file if the '-o' argument is provided.

"""

import ssl
import urllib
import urllib2
import copy
import random
import codecs
import string
from time import sleep
from progressbar import *
from prettytable import PrettyTable

def fireFuzz(type, fuzz, url, params, header, delay, outputFile, proxy, prefix, postfix):
    """
        :Description: This function iterates through a list of fuzzing strings retrieved from the database, sends them to the target site and displays a progress bar of this process.

        :param type:  Type of the fuzzing strings to send [sql | xss].
        :type type: String
        
        :param fuzz:  Fuzzing strings
        :type fuzz: List
        
        :param url: Target URL
        :type url: String
        
        :param params: POST Parameter
        :type params: String
        
        :param header: Cookie header
        :type header: String
        
        :param delay: Delay between requests
        :type delay: Float
        
        :param outputFile:  Name of Output file
        :type outputFile: String
        
        :note: This function calls the showOutput() file with the saved outputs as argument.
        :todo: Add threads in order to send requests simultaneously.
		
    """
    print '''
    ___       ______________________   ______       ________        
    __ |     / /__    |__  ____/__  | / /__(_)____________(_)_____ _
    __ | /| / /__  /| |_  /_   __   |/ /__  /__  __ \____  /_  __ `/
    __ |/ |/ / _  ___ |  __/   _  /|  / _  / _  / / /___  / / /_/ / 
    ____/|__/  /_/  |_/_/      /_/ |_/  /_/  /_/ /_/___  /  \__,_/  
                                                    /___/           
                                                    
    WAFNinja - Penetration testers favorite for WAF Bypassing
    '''
    pbar = ProgressBar(widgets=[SimpleProgress(), ' Fuzz sent!    ', Percentage(), Bar()])
    if proxy is not '':
        httpProxy = urllib2.ProxyHandler({'http': proxy, 'https': proxy})
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        opener = urllib2.build_opener(urllib2.HTTPSHandler(context=ctx),httpProxy)
        urllib2.install_opener(opener)
    else:
        opener = urllib2.build_opener()
    opener.addheaders = [('User-Agent', 'Mozilla/5.0')]
    for h in header:
        opener.addheaders.append(h)
    result = []
    
    for fuzz in pbar(fuzz):
        expected = fuzz[1]
        fuzz = prefix + fuzz[0] + postfix
        fuzz_enc = fuzz.encode('utf-8')
        try:
            sleep(float(delay))
            if params is None: # GET parameter
                randomString, url_with_fuzz = insertFuzz(url, fuzz_enc) 
                response = opener.open(url_with_fuzz)
            else: # POST parameter
                randomString, params_with_fuzz = setParams(params, fuzz_enc) 
                response = opener.open(url, urllib.urlencode(params_with_fuzz))
            content = response.read()
            occurence = content.find(randomString)+len(randomString) # get position of the randomString + length(randomString) to get to the fuzz
            result.append({
                'fuzz' : fuzz_enc, 
                'expected' : expected, 
                'httpCode' : response.getcode(), 
                'contentLength': len(content),  
                'output' : content[occurence:occurence+len(expected)]}) # take string from occurence to occurence+len(expected)
        except urllib2.HTTPError, error: # HTTP Status != 200
            if error.code == 404:
                print 'ERROR: Target URL not reachable!'
                sys.exit()
            else: # HTTP Status != 404
                result.append({
                    'fuzz' : fuzz_enc, 
                    'expected' : expected, 
                    'httpCode' : error.code, 
                    'contentLength': '-', 
                    'output' : '-'})
    showOutput(type, url, result, outputFile, delay, proxy, prefix, postfix)  

        
            
def showOutput(type, url, result, outputFile, delay, proxy, prefix, postfix):
    """
        :Description: This function prints the result of the fireFuzz() function in a nice fashion.

        :param type:  Type of the fuzzing strings that were sent
        :type type: String
        
        :param result: Contains the sent Fuzz, HTTP Code, Content-Length, expected string and the response's output
        :type result: List
        
        :param outputFile:  Name of Output file
        :type outputFile: String
        
        :note: This function saves the output in a HTML file or prints the output directly in the CLI.
    """
    
    table = PrettyTable(['Fuzz', 'HTTP Status', 'Content-Length', 'Expected', 'Output', 'Working'])
    for value in result:
        if (value['httpCode'] != 200):
            table.add_row([value['fuzz'], value['httpCode'], value['contentLength'], value['expected'], value['output'].strip(), 'No'])
        else:
            if(value['expected'] in value['output']): 
                table.add_row([value['fuzz'], value['httpCode'], value['contentLength'], value['expected'], value['output'], 'Yes'])
            else: 
                table.add_row([value['fuzz'], value['httpCode'], value['contentLength'], value['expected'], value['output'], 'Probably'])

    if outputFile is not None:
        table = table.get_html_string(attributes={"class":"OutputTable"})
        table = '<h1>WAFNinja - Penetration testers favorite for WAF Bypassing</h1>' + '<b>URL</b>: ' + url + '<br>' + '<b>TYPE: </b>' + type + '<br>' + '<b>DELAY: </b>' + str(delay) + '<br>' + '<b>PROXY: </b>' + proxy + '<br>' + '<b>PREFIX: </b>' + prefix + '<br>' + '<b>POSTFIX: </b>' + postfix + '<br><br>' + table
        table = '''<meta charset="utf-8"/><style>
        .OutputTable {
	margin:0px;padding:0px;
	width:100%;
	border:1px solid #000000;
	
	-moz-border-radius-bottomleft:10px;
	-webkit-border-bottom-left-radius:10px;
	border-bottom-left-radius:10px;
	
	-moz-border-radius-bottomright:10px;
	-webkit-border-bottom-right-radius:10px;
	border-bottom-right-radius:10px;
	
	-moz-border-radius-topright:10px;
	-webkit-border-top-right-radius:10px;
	border-top-right-radius:10px;
	
	-moz-border-radius-topleft:10px;
	-webkit-border-top-left-radius:10px;
	border-top-left-radius:10px;
	table-layout: fixed;
        }.OutputTable table{
            border-collapse: collapse;
                border-spacing: 0;
                width:310px;
                height:100%;
                margin:0px;padding:0px;
        }.OutputTable tr:last-child td:last-child {
                -moz-border-radius-bottomright:10px;
                -webkit-border-bottom-right-radius:10px;
                border-bottom-right-radius:10px;
        }
        .OutputTable table tr:first-child td:first-child {
                -moz-border-radius-topleft:10px;
                -webkit-border-top-left-radius:10px;
                border-top-left-radius:10px;
        }
        .OutputTable table tr:first-child td:last-child {
                -moz-border-radius-topright:10px;
                -webkit-border-top-right-radius:10px;
                border-top-right-radius:10px;
        }.OutputTable tr:last-child td:first-child{
                -moz-border-radius-bottomleft:10px;
                -webkit-border-bottom-left-radius:10px;
                border-bottom-left-radius:10px;
        }.OutputTable tr:hover td{
                background-color:#ffffff;
        }
        .OutputTable td{
                vertical-align:middle;
                background-color:#ffffff;
                width:500px; 
                word-wrap: break-word;
            height: 15px;
                border:1px solid #000000;
                border-width:0px 1px 1px 0px;
                text-align:center;
                padding:9px;
                font-size:15px;
                font-family:Helvetica;
                font-weight:normal;
                color:#000000;
        }.OutputTable tr:last-child td{
                border-width:0px 1px 0px 0px;
        }.OutputTable tr td:last-child{
                border-width:0px 0px 1px 0px;
        }.OutputTable tr:last-child td:last-child{
                border-width:0px 0px 0px 0px;
        }
        .OutputTable tr:first-child th{
                        background:-o-linear-gradient(bottom, #007fff 5%, #007fff 100%);	background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #007fff), color-stop(1, #007fff) );
                background:-moz-linear-gradient( center top, #007fff 5%, #007fff 100% );
                filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#007fff", endColorstr="#007fff");	background: -o-linear-gradient(top,#007fff,007fff);

                background-color:#007fff;
                border:0px solid #000000;
                text-align:center;
                border-width:0px 0px 1px 1px;
                font-size:15px;
                font-family:Courier;
                font-weight:bold;
                color:#ffffff;
        }
        .OutputTable tr:first-child:hover td{
                background:-o-linear-gradient(bottom, #007fff 5%, #007fff 100%);	background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #007fff), color-stop(1, #007fff) );
                background:-moz-linear-gradient( center top, #007fff 5%, #007fff 100% );
                filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#007fff", endColorstr="#007fff");	background: -o-linear-gradient(top,#007fff,007fff);

                background-color:#007fff;
        }
        .OutputTable tr:first-child td:first-child{
                border-width:0px 0px 1px 0px;
        }
        .OutputTable tr:first-child td:last-child{
                border-width:0px 0px 1px 1px;
        }
        .OutputTable td.Yes{
                background-color:#00FF00;
        }
        .OutputTable td.No{
                background-color:#FF0000;
        }
        .OutputTable td.Probably{
                background-color:#00CCFF;
        }
            </style>''' + table
        table = table.replace('<td>Yes</td>', '<td class="Yes">Yes</td>')
        table = table.replace('<td>No</td>', '<td class="No">No</td>')
        table = table.replace('<td>Probably</td>', '<td class="Probably">Probably</td>')
        file = codecs.open(outputFile, 'w', encoding='utf-8')
        file.write(table) 
        file.close() 
        print 'Output saved to ' + outputFile + '!'
    else:
        print table
        
def insertFuzz(url, fuzz):
    """
        :Description: This function inserts the Fuzz as GET Parameter in the URL 

        :param url: Target URL
        :type type: String
        
        :param fuzz: Fuzzing string
        :type fuzz: String

        :return: The URL with a concatenated string consisting of a random string and the fuzz.
        :note: Some fuzzing symbols can be part of a normal response. In order to distinctly find the fuzz that was sent, a random string is added before the fuzz.

    """

    fuzz = urllib.quote_plus(fuzz) #url encoding
    randomString = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(6))
    return randomString, url.replace('FUZZ', randomString + str(fuzz))
    
def setParams(params, fuzz):
    """
        :Description: This function sets the Fuzz in the POST Parameter.

        :param url: Target URL
        :type type: String
        
        :param fuzz: Fuzzing string
        :type fuzz: String

        :return: The post parameter with a concatenated string consisting of a random string and the fuzz
        :note: Some fuzzing symbols can be part of a normal response. In order to distinctly find the fuzz that was sent, a random string is added before the fuzz.

    """
    
    randomString = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(6))
    parameter = copy.deepcopy(params) #makes a deep copy. this is needed because using a reference does not work
    for param in parameter:
        if parameter[param] == 'FUZZ':
            parameter[param] = randomString + str(fuzz)
    return randomString, parameter;