Пример #1
0
def funScan(lstHosts, boolCache):
    # Initiate the scan
    strHLen = str(len(lstHosts))
    for i, strHost in enumerate(lstHosts):
        if not boolCache and not objSLA.funAnalyze(strHost):
            # If cached reports aren't allowed (default) - but a new assessment has failed to start
            continue
        funResult(objSLA.funOpStatus(strHost))
        log.funLog(2, '[%s/%s] Done.' % (str(i + 1), strHLen))
Пример #2
0
def funResult(amStatus):
    # Add grades to list or print assessment JSON
    global lstGrades, intRCount
    if isinstance(amStatus, list):
        for i in amStatus:
            log.funLog(1, i)
        lstGrades.extend(amStatus)
        intRCount += 1
    elif isinstance(amStatus, dict):
        print(json.dumps(amStatus, indent=4))
Пример #3
0
def funReadCfg(strCFile):
    # Read external config file
    if not os.path.isfile(strCFile):
        log.funLog(1, 'Config file: %s is missing.' % strCFile, 'err')
        return {}

    try:
        # Open the config file
        with open(strCFile, 'r') as f:
            return json.load(f)

    except Exception as e:
        log.funLog(2, repr(e), 'err')
        return {}
Пример #4
0
    def funAnalyze(self, strHost):
        # Initiate a new assessment for a host
        while True:
            try:
                objHResp = self.objHS.get(self.strAPIE + self.strAnalyze +
                                          strHost + self.strAnStNew)
                if objHResp.status_code in [429, 503, 529]:
                    # 429 - client request rate too high or too many new assessments too fast
                    # 503 - the service is not available (e.g. down for maintenance)
                    # 529 - the service is overloaded
                    log.funLog(
                        2,
                        'Request rate too high or service unavailable [%s]! Sleeping for %s sec.'
                        % (str(objHResp.status_code), str(self.intCool)))
                    # Update cool-off period
                    self.funInfo()
                    time.sleep(self.intCool)
                elif objHResp.status_code == 200:
                    log.funLog(
                        1, 'New assessment started for %s: %s' %
                        (strHost, json.loads(objHResp.content)['status']))
                    return True

                else:
                    log.funLog(
                        1, 'New assessment failed for %s [%s]' %
                        (strHost, str(objHResp.status_code)))
                    return False

            except Exception as e:
                log.funLog(2, repr(e), 'err')
                break
Пример #5
0
    def funInfo(self, boolConTune=False):
        # Check availability of SSL Labs servers, adjust concurrency and cool-off period
        if self.boolIM:
            self.strAnStNew += '&ignoreMismatch=on'
        try:
            objHResp = json.loads(
                self.objHS.get(self.strAPIE + self.strInfo).content)
            self.intCool = objHResp['newAssessmentCoolOff'] / 1000
            log.funLog(
                2, 'Cool-off period after each new assessment: %s sec.' %
                str(self.intCool))
            intDelta = objHResp['maxAssessments'] - objHResp[
                'currentAssessments']
            if boolConTune and self.intConc > intDelta:
                # Trim concurrency on init
                self.intConc = intDelta
            return True if intDelta > 0 else False

        except Exception as e:
            log.funLog(2, repr(e), 'err')
Пример #6
0
def funConScan(lstHosts, boolCache):
    # Concurrent scan
    strHLen = str(len(lstHosts))
    # Split the hosts list into groups of the concurrency size (2D list)
    lstMatrix = [
        lstHosts[i:i + objSLA.intConc]
        for i in range(0, len(lstHosts), objSLA.intConc)
    ]
    # Initiate the scan
    for g, lstGroup in enumerate(lstMatrix):
        # New assessment
        if not boolCache:
            for i, strHost in enumerate(lstGroup):
                log.funLog(
                    2, '[%s/%s] Starting...' %
                    (str(g * objSLA.intConc + i + 1), strHLen))
                objSLA.funAnalyze(strHost)
                time.sleep(objSLA.intCool)
        # Check status
        intReady = 0
        while intReady < len(lstGroup):
            # Completed assessments counter
            intReady = 0
            time.sleep(objSLA.intPoll)
            for i, strHost in enumerate(lstGroup):
                if strHost.endswith('#'):
                    intReady += 1
                    continue
                # Non-blocking operation status
                amStatus = objSLA.funOpStatus(strHost, True)
                if amStatus:
                    # Mark host as completed
                    log.funLog(
                        2, '[%s/%s] Done.' %
                        (str(g * objSLA.intConc + i + 1), strHLen))
                    lstGroup[i] += '#'
                    intReady += 1
                    funResult(amStatus)
Пример #7
0
    def funOpStatus(self, strHost, boolAsync=False):
        # Check operation status
        # boolAsync = True: non-blocking, gets current status and exits (returns results only on 'ERROR' or 'READY')
        # boolAsync = False: blocking, loops while 'IN_PROGRESS', exits (with results) only on 'ERROR' or 'READY'
        strStatus = 'DNS'
        strURL = self.strAPIE + self.strAnalyze + strHost
        while strStatus == 'DNS':
            # Initial status
            try:
                diOper = json.loads(self.objHS.get(strURL).content)
                strStatus = diOper['status']
                log.funLog(3, 'Transaction status: %s' % strStatus)
                if strStatus == 'ERROR':
                    return ['[X] %s, %s' % (strHost, diOper['statusMessage'])]

            except Exception as e:
                log.funLog(2, repr(e), 'err')
        if not boolAsync:
            # Log container for per-endpoint messages
            lstMessages = [None] * len(diOper['endpoints'])
            log.funLog(2,
                       'Total number of endpoints: %s' % str(len(lstMessages)))
            while strStatus == 'IN_PROGRESS':
                try:
                    if log.intLogLevel >= 3:
                        for i, diEP in enumerate(diOper['endpoints']):
                            if diEP['statusMessage'] == 'In progress':
                                strDetMess = diEP['statusDetailsMessage']
                                if strDetMess != lstMessages[i]:
                                    lstMessages[i] = strDetMess
                                    # Show actual endpoint IP address or the first 8 chars of its SHA-256 hash
                                    strIP = diEP[
                                        'ipAddress'] if self.boolIPs else hashlib.sha256(
                                            diEP['ipAddress']).hexdigest()[:8]
                                    log.funLog(
                                        3, '%s, IP: %s, %s' %
                                        (strHost, strIP, lstMessages[i]))
                    else:
                        time.sleep(self.intPoll)
                    diOper = json.loads(self.objHS.get(strURL).content)
                    strStatus = diOper['status']
                except Exception as e:
                    log.funLog(2, repr(e), 'err')
        if strStatus == 'READY':
            log.funLog(1, 'Assessment complete for: %s' % strHost)
            return diOper if self.boolJSON else self.funGrades(diOper)
Пример #8
0
def funExit():
    log.funLog(1, 'Exiting...')
Пример #9
0
def funBadExit(type, value, traceback):
    log.funLog(2, 'Unhandled Exception: %s, %s, %s' % (type, value, traceback))
Пример #10
0
def main():
    global objSLA, strCFile, lstGrades, strMHead
    objArgs = funArgParser()

    # If run interactively, stdout is used for log messages (unless -j is set)
    if sys.stdout.isatty() and not objArgs.json:
        log.strLogMethod = 'stdout'

    # Set log level
    if objArgs.log:
        log.intLogLevel = objArgs.log

    # Ignore server certificate mismatch
    objSLA.boolIM = objArgs.im

    # Show real IP addresses argument
    objSLA.boolIPs = objArgs.ips

    # Full assessment JSON argument
    objSLA.boolJSON = objArgs.json

    # Config file location
    if objArgs.cfile:
        strCFile = objArgs.cfile

    # Concurrency
    if objArgs.conc:
        objSLA.intConc = objArgs.conc

    # Read config file
    diCfg = cfg.funReadCfg(strCFile)
    try:
        lstHosts = diCfg['hosts']
    except Exception as e:
        log.funLog(1, 'Invalid config file: %s' % strCFile, 'err')
        log.funLog(2, repr(e), 'err')
        sys.exit(objExCodes.cfile)

    # List of hosts to scan (override)
    if objArgs.HOST:
        lstHosts = objArgs.HOST

    # Hosts list cleanup (remove invalid domains)
    lstHClean = [strHost for strHost in lstHosts if objSLA.funValid(strHost)]
    if len(lstHosts) > len(lstHClean):
        log.funLog(
            1, 'Ignoring invalid hostname(s): %s' %
            ', '.join(list(set(lstHosts) - set(lstHClean))), 'err')
    lstHosts = lstHClean

    # Check SSL Labs availability
    if not objSLA.funInfo(True):
        log.funLog(
            1,
            'SSL Labs unavailable or maximum concurrent assessments exceeded.',
            'err')
        sys.exit(objExCodes.nosrv)

    log.funLog(
        1, 'Scanning %s host(s)... [Cache: %s, Concurrency: %s]' %
        (str(len(lstHosts)), bool(objArgs.cache), str(objSLA.intConc)))

    # Scan
    if objSLA.intConc > 1:
        # Concurrency
        funConScan(lstHosts, objArgs.cache)
    else:
        funScan(lstHosts, objArgs.cache)

    # Sort the grades in reverse and add line breaks
    strReport = '\r\n'.join(sorted(lstGrades, reverse=True))

    # Send the report to a Slack channel
    if objArgs.slack and not objArgs.json:
        log.funLog(1, 'Slacking the report...')
        try:
            objSlack = slackclient.SlackClient(diCfg['token'].decode('base64'))
            diSResp = objSlack.api_call('chat.postMessage',
                                        username=strMFrom,
                                        channel=diCfg['channel'],
                                        text='```\n%s\n```' % strReport,
                                        icon_url=strSIcon)
            if not diSResp['ok']:
                raise Exception(diSResp['error'])
        except Exception as e:
            log.funLog(2, repr(e), 'err')

    # Mail the report
    if objArgs.mail and not objArgs.json:
        # Format MIME message
        objMIME = email.MIMEText(
            'Total Hosts: [%s/%s], Concurrency: %s\r\n%s' %
            (str(intRCount), str(len(lstHosts)), str(
                objSLA.intConc), strReport))
        log.funLog(1, 'Mailing the report...')
        try:
            objMIME['From'] = '%s <%s>' % (strMFrom, diCfg['from'])
            # Remove spaces and split recipients into a list delimited by , or ;
            lstTo = re.split(r',|;', diCfg['to'].replace(' ', ''))
            objMIME['To'] = ', '.join(lstTo)
            objMIME['Subject'] = strMSubj
            # Connect to SMTP server
            objMail = smtplib.SMTP(diCfg['server'])
            # Identification
            objMail.ehlo()
            # Encryption
            objMail.starttls()
            # Authentication
            objMail.login(diCfg['user'], diCfg['pass'].decode('base64'))
            # Send mail
            objMail.sendmail(diCfg['from'], lstTo, objMIME.as_string())
            log.funLog(1, 'Success!')
            # Terminate the SMTP session and close the connection
            objMail.quit()
        except Exception as e:
            log.funLog(2, repr(e), 'err')
    else:
        print(strReport)