"""
# Web Services Fuzzer
#
# FILENAME      : genUtils.py
# AUTHORS       : Andres Andreu <andres [at] neurofuzz dot com>
# DATE          : 8/3/2008
# LAST UPDATE   : 9/12/2010
# ABSTRACT      : A file with general functions that WSFuzzer uses.
#               : This tool is meant to be part of a web app pen testers toolkit.
#
# Copyright (c) 2008 - 2010 neuroFuzz, LLC
# Get the latest versions from:
# http://www.neurofuzz.com/modules/software/wsfuzzer.php
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with WSFuzzer; if not, write to:
# Free Software Foundation, Inc.
# 51 Franklin St, Fifth Floor
# Boston, MA  02110-1301  USA
#
# This program is distributed in the hope that it will be useful
# to the application security community.  It is in no way written
# to be used for malicious purposes and the target audience are
# penetration testers who have all legal right to perform these
# types of audits against a given target.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
"""

import sys
import re
import os

import versionchecker
VERSION = '1.9.5'

# NAME     : usage
# PARAMS   : None
# RETURN   : program exit
# DESC     : provides a simple usage statement
def usage(self):
    print """
    Usage: python WSFuzzer.py   [
                                -w wsdl_url |
                                -e endpoint -n namespace |
                                --xml |
                                -h host |
                                --conf |
                                --bauser username --bapass password |
                                --xsseuser username --xssepass password |
                                --keyfile keyfile --certfile certfile |
                                --proxy proxyserver --proxyport port |
                                --attacks
                                ]

    -w WSDL_URL --- A FQDN WSDL URL - i.e. http://host/service/service.asmx?wsdl

    -e endpoint -n namespace --- -e and -n are used together
        -e is the web service endpoint --- i.e. WSDL URL
        -n is the web service namespace --- i.e. URI
        ** When using -e and -n you will have to manually establish the method to be attacked

    --xml --- A text file of the XML payload to be used against the target

    -h host --- A URL of the target host.  This option will do some digging into
                the target URL, it will scrape for anything WSDL or DISCO related and construct"
                a list of verified WSDL URL's

    --conf file --- A file containing some config data so as to automate some of the"
                    normally interactive parts of WSFuzzer

    --bauser username --bapass password --- these 2 optional arguments are used together whenever HTTP Basic Auth needs to be used
        --bauser is a Basic Auth username
        --bapass is a Basic Auth password to be used with the \"bauser\" username

    --xsseuser username --xssepass password --- these 2 optional arguments are used together whenever WS-Security Auth needs to be used
        --xsseuser is a WS-Security username
        --xssepass is a WS-Security password to be used with the \"xsseuser\" username

    --keyfile keyfile --certfile certfile --- these 2 optional arguments are used together whenever client-side certs need to be used
        --keyfile is the PEM formatted file that contains the respective private key to be used
        --certfile is the PEM formatted file that contains the X.509 certificate to be used with the \"keyfile\"

    --proxy server_info --proxyport port --- these 2 optional arguments are used together
        --proxy is a Proxy server's IP address or FQDN
        --proxyport is the number of the TCP port for the server identified in switch --proxy

    --attacks is the name of a XML attack vector file previously generated by WSFuzzer

    """

    sys.exit(0)
# EOF: usage

# NAME     : defineDirName
# PARAMS   : None
# RETURN   : None or string
# DESC     : asks the user for a specific directory
#            name where the results of the audit will
#            be stored.  Returns None if no data is
#            entered.
def defineDirName(self):
    print "\nIf you would like to establish the directory name for the"
    dirName = raw_input("results then type it in now (leave blank for the default): ")
    dirName = dirName.strip('\r')
    if dirName == '':
        return None
    else:
        return dirName
# EOF: defineDirName

# NAME     : checkVal
# PARAMS   : val
# RETURN   : boolean
# DESC     : checks val to see if it is in the form
#            of an input argument, "--x" or "-x"
def checkVal(val):
	dashes = re.compile(r"--")
	# would represent another prog switch
	if val is None or val == "" \
        or val.startswith("-") or dashes.search(val):
		return False
	# value is good
	return True
# EOF: checkVal

# NAME     : echoStatus
# PARAMS   : attrib,val
# RETURN   : Nothing
# DESC     : print out stmt about the conf file
#            attrib that was processed
def echoStatus(attrib,val):
    if val is not None:
        print "Attribute: \"%s\" was processed with value: \"%s\"\n" % (attrib,val)
# EOF: echoStatus

# NAME     : printIDSOpts
# PARAMS   : None
# RETURN   : Nothing
# DESC     : print out list of IDS Evasion
#            options
def printIDSOpts():
    print "\nChoose an option for IDS Evasion."
    print "0) null method processing - ** Windows targets only"
    print "1) random URI (non-UTF8) encoding"
    print "2) directory self-reference (/./)"
    print "3) premature URL ending"
    print "4) prepend long random string"
    print "5) fake parameter"
    print "6) TAB as request spacer"
    print "7) random case sensitivity - ** Windows targets only"
    print "8) directory separator (\) - ** Windows targets only"
    print "10) URI (non-UTF8) encoding"
    print "11) double percent hex encoding - ** Windows targets only"
    print "12) double nibble hex encoding - ** Windows targets only"
    print "13) first nibble hex encoding - ** Windows targets only"
    print "14) second nibble hex encoding - ** Windows targets only"
    print "R) choose an option at random"
# EOF: printIDSOpts

# NAME     : handleFileMsg
# PARAMS   : fn, msg, exit
# RETURN   : Nothing
# DESC     : prints out stmt and may exit
#            if bool (exit) is set to True
def handleFileMsg(fn, msg=False, exit=False):
    print "Opening File Failed (%s)" % fn
    if msg:
        print msg
    else:
        print "Please check the path & name and try again."
    if exit:
        print "This run cannot continue ..."
        sys.exit(0)
# EOF: handleFileMsg

# NAME     : detectOS
# PARAMS   : None
# RETURN   : String
# DESC     : returns platform identifying string
def detectOS():
	return sys.platform
# EOF: detectOS

# NAME     : detectWindowsOS
# PARAMS   : None
# RETURN   : Bool
# DESC     : returns true if windows is detected
def detectWindowsOS():
	os = detectOS()
	# detect win
	if os.startswith("win"):
		return True
	# rest of the world
	return False
# EOF: detectWindowsOS

# NAME     : getstatusoutput
# PARAMS   : String
# RETURN   : tuple
# DESC     : patched version of commands.getstatusoutput
#			 the default one doesnt work with windows, this
#			 one does.
def getstatusoutput(cmd):
    """Return (status, output) of executing cmd in a shell."""

    mswindows = (sys.platform == "win32")

    if not mswindows:
    	cmd = '{ ' + cmd + '; }'

    pipe = os.popen(cmd + ' 2>&1', 'r')
    text = pipe.read()
    sts = pipe.close()
    if sts is None:
    	sts = 0
    if text[-1:] == '\n':
    	text = text[:-1]
    return sts, text
# EOF: getstatusoutput

# NAME     : getAutoAttackResponse
# PARAMS   : None
# RETURN   : string
# DESC     : asks the user about enabling automated
#			 fuzzing and returns the submitted answer
def getAutoAttackResponse(self):
	automate = "n"
	print "\nWould you like to enable automated fuzzing"
	print "to augment what you have already chosen?"
	print "This option generates a lot of traffic,"
	print "mostly with a bad attitude &-> (y/n)"
	automate = raw_input("Answer: ")
	automate = automate.strip('\r')

	return automate
# EOF: getAutoAttackResponse

# NAME     : defineDictionaryName
# PARAMS   : None
# RETURN   : None or string
# DESC     : asks the user for a specific dictionary
#            name.
def defineDictionaryName(self):
	dictionary = raw_input("Input name of Fuzzing dictionary(full path): ")
	dictionary = dictionary.strip('\r')
	print "\nDictionary Chosen: " + dictionary

	return dictionary
# EOF: defineDictionaryName

# NAME     : defineFuzzType
# PARAMS   : param
# RETURN   : None or string
# DESC     : asks the user for a fuzz type
def defineFuzzType(param):
	print "\tParameter: " + param
	print "\nChoose fuzz type by numerical index (e.g. 1)"
	print "0) Do not fuzz this parameter"
	print "1) Dictionary - one entry per line in form: vector:::attack type"
	fuzzChosen = input("\nFuzzType: ")

	return fuzzChosen
# EOF: defineFuzzType

# NAME     : validateIDSEvasionOptInput
# PARAMS   : string
# RETURN   : Boolean
# DESC     : Performs a whitelist check of param
#			 passed in and returns bool based on
#			 whitelist query
def validateIDSEvasionOptInput(mode):
    modes = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '10',
            '11', '12', '13', '14', 'R']
    # do some whitelisting based on known good input
    # otherwise return False
    if mode in modes:
		return True
    else:
		return False
# EOF: validateIDSEvasionOptInput

# NAME     : doProgVersionCheck
# PARAMS   : None
# RETURN   : None
# DESC     : Performs a version check for the prog
def doProgVersionCheck():
    # prog version check
    v = versionchecker.getremotedata('wsfuzzer_version.txt')
    if (v is not None):
        v.strip()
        if (VERSION == v.strip()):
            print "\n\n\nRunning WSFuzzer %s, the latest version\n" % v.strip()
        elif (VERSION < v):
            print "\n\n\nRunning WSFuzzer %s - version %s is out ...\n" % (VERSION, v.strip())
        elif (VERSION > v):
            print "\n\n\nYou either have a bogus copy of this product or have changed"
            print "the version number of it.  Version %s cannot be out when" % (VERSION)
            print "version %s is the latest official release ...\n" % (v).strip()
    else:
        print "\n\n\nRunning WSFuzzer %s - latest version could not be verified\n" % VERSION
# EOF: doProgVersionCheck

# NAME     : doFileVersionCheck
# PARAMS   : None
# RETURN   : None
# DESC     : Performs a version check for text files
def doFileVersionCheck():
    # data checks
    fpath = "dict/"
    check = ['All_attack.txt', 'dirs.txt', 'filetypes.txt']
    for i in check:
        try:
            f = open(fpath+i, 'r')
            '''
               versionchecker.gethash() gets the file from neurofuzz.com
               and hashes it.
               versionchecker.shasumfile(f) generates a hash from the local file
            '''
            res = versionchecker.simplecompare(versionchecker.gethash(i), versionchecker.shasumfile(f))
            f.close()

            # there is a mismatch
            if (res != 1) and (res != None):
                print "\nThere is a mismatch in the detected \"%s\" data set, " % i
                print "would you like to synchronize with neurofuzz(y/n)?"
                sync = raw_input("Answer: ")
                sync = sync.strip('\r')
                print
                if 'y' in sync:
                    print "synchronizing \"%s\" data with neurofuzz ...\n" % i
                    # overwrite the entire contents of the local file
                    f = open(fpath+i, 'w')
                    f.writelines(versionchecker.getremotedata(i))
                    f.close()
                    print "\"%s\" data synchronized with neurofuzz\n" % i
            else:
                print "Local \"%s\" data matches that on neurofuzz.com\n" % i
        except:
            print "Error checking \"%s\" data from neurofuzz\n" % i
# EOF: doFileVersionCheck

# NAME     : validateConfig
# PARAMS   : config
# RETURN   : Boolean
# DESC     : Verifies the existence of all
#            required elements for the usage of
#            a given config file and the mode
#            of operation.
#            Mode 1 is the equivalent of using
#            the -w switch
#            Mode 2 is the equivalent of using
#            the -e and -n switches
#            Mode 3 is the equivalent of using
#            the --xml switch
def validateConfig(config):
    if config["Mode"] == '1':
        required = ('wsdl', 'simultaneous', 'idsevasion')
        for e in required:
            if not config.has_key(e):
                return (False, e)
        return (True, None)
    if config["Mode"] == '2':
        required = ('endpoint', 'namespace', 'method', 'parameters',
                    'simultaneous', 'dictionary', 'automate', 'idsevasion')
        for e in required:
            if not config.has_key(e):
                return (False, e)
        return (True, None)
    if config["Mode"] == '3':
        required = ('host', 'uri', 'xml', 'simultaneous', 'dictionary',
                    'automate', 'idsevasion')
        for e in required:
            if not config.has_key(e):
                return (False, e)
        return (True, None)
# EOF: validateConfig

def validateCombo(mainval, maintype, attachedval, attachedvaltype):

    if mainval is not None:
        if not checkVal(str(mainval)):
            print "A %s value of that nature is invalid ...\n" % (maintype)
            sys.exit(2)
        if attachedval is None:
            print "%s values need to be used in conjunction with a %s value ...\n" % (attachedvaltype, maintype)
            sys.exit(2)
