Beispiel #1
0
def session_finish(scan_id, session_id, state, t, failure=None):
    #  params = [scan['id'], session['id'], msg['data']['state'], time.time(), msg['data']['failure']]
    logger.debug("Finishing session [%s] for scan [%s]" % (session_id, scan_id))
    s = Session.get_session(session_id)
    s.state = state
    s.finished = datetime.datetime.utcfromtimestamp(t)
    s.failure = failure
    db.session.commit()
Beispiel #2
0
def post_scan_create():
    # try to decode the configuration
    configuration = request.json
    
    # See if the plan exists
    plan = Plan.get_plan(configuration['plan'])
    if not plan:
        return jsonify(success=False)
    # Merge the configuration
    # Create a scan object

    scan = Scan()

    scan.meta = json.dumps({ "user": configuration['user'], "tags": [] } )

    scan.configuration = json.dumps(configuration['configuration'])
    scan.site = Site.get_site_by_url(configuration['configuration']['target'])

    scan.plan = json.dumps( { "name": plan.name, "revision": 0 })

    db.session.add(scan)
    db.session.commit()
    for step in plan.workflows:

        session_configuration = {}
        if step.configuration:
         session_configuration = json.loads(step.configuration)
        session_configuration.update(configuration['configuration'])


        session = Session()
        session.configuration = json.dumps(session_configuration)
        session.description = step.description

        session.plugin = json.dumps(Plugin.plugins[step.plugin_name]['descriptor'])

        scan.sessions.append(session)
        db.session.add(session)
        db.session.commit()

    db.session.commit()

    return jsonify(success=True, scan=sanitize_scan(scan))
Beispiel #3
0
def session_report_issue(scan_id, session_id, issue):
    logger.debug("Starting session [%s] for scan [%s]" % (session_id, scan_id))
    session = Session.get_session(session_id)

    i = Issue()
    i.code = issue["Code"]
    i.description = issue["Description"]

    i.further_info = json.dumps(issue["FurtherInfo"])
    i.urls = json.dumps(issue["URLs"])
    i.severity = issue["Severity"]
    i.summary = issue["Summary"]

    db.session.add(i)
    session.issues.append(i)
    db.session.commit()
Beispiel #4
0
def scan(scan_id):
    logger.debug("Starting scan [%s] (scan:572)" % scan_id)
    try:

        #
        # See if the scan exists.
        #
        logger.debug("Retrieving scan for scan() [%s]" % scan_id)
        scan = get_scan(cfg['api']['url'], scan_id)
        if not scan:
            logger.error("Cannot load scan %s" % scan_id)
            return

        #
        # Is the scan in the right state to be started?
        #

        if scan['state'] != 'QUEUED':
            logger.error("Scan %s has invalid state. Expected QUEUED but got %s" % (scan_id, scan['state']))
            return

        #
        # Move the scan to the STARTED state
        #
        db_scan = Scan.get_scan(scan['id'])
        db_scan.state = 'STARTED'
        scan['state'] = 'STARTED'
        db.session.commit()
        
        send_task("minion.backend.tasks.scan_start", [scan_id, time.time()], queue='state').get()

        #
        # Check this site against the access control lists
        #

        if not scannable(scan['configuration']['target'],
                         scan_config().get('whitelist', []),
                         scan_config().get('blacklist', [])):
            failure = {"hostname": socket.gethostname(),
                       "reason": "target-blacklisted",
                       "message": "The target cannot be scanned by Minion because its IP address or hostname has been blacklisted."}
            return set_finished(scan_id, 'ABORTED', failure=failure)

        #
        # Verify ownership prior to running scan
        #

        target = scan['configuration']['target']
        site = get_site_info(cfg['api']['url'], target)
        if not site:
            return set_finished(scan_id, 'ABORTED')

        if site.get('verification') and site['verification']['enabled']:
            verified = ownership.verify(target, site['verification']['value'])
            if not verified:
                failure = {"hostname": socket.gethostname(),
                           "reason": "target-ownership-verification-failed",
                           "message": "The target cannot be scanned because the ownership verification failed."}
                return set_finished(scan_id, 'ABORTED', failure=failure)

        #
        # Run each plugin session
        #

        for session in scan['sessions']:

            #
            # Mark the session as QUEUED
            #

            db_session = Session.get_session(session['id'])
            db_session.state = 'QUEUED'
            session['state'] = 'QUEUED'
            db.session.commit()
            #scans.update({"id": scan['id'], "sessions.id": session['id']}, {"$set": {"sessions.$.state": "QUEUED", "sessions.$.queued": datetime.datetime.utcnow()}})
            send_task("minion.backend.tasks.session_queue",
                      [scan['id'], session['id'], time.time()],
                      queue='state').get()

            #
            # Execute the plugin. The plugin worker will set the session state and issues.
            #
            db.session.commit()
            

            queue = queue_for_session(session, cfg)
            result = send_task("minion.backend.tasks.run_plugin",
                               [scan_id, session['id']],
                               queue=queue)

            #scans.update({"id": scan_id, "sessions.id": session['id']}, {"$set": {"sessions.$._task": result.id}})
            send_task("minion.backend.tasks.session_set_task_id",
                      [scan_id, session['id'], result.id],
                      queue='state').get()

            try:
                plugin_result = result.get()
            except TaskRevokedError as e:
                plugin_result = "STOPPED"

            db_session = Session.get_session(session['id'])
            db_session.state = plugin_result
            session['state'] = plugin_result

            db.session.commit()
            #
            # If the user stopped the workflow or if the plugin aborted then stop the whole scan
            #

            if plugin_result in ('ABORTED', 'STOPPED'):
                # Mark the scan as failed
                #scans.update({"id": scan_id}, {"$set": {"state": plugin_result, "finished": datetime.datetime.utcnow()}})
                send_task("minion.backend.tasks.scan_finish",
                          [scan_id, plugin_result, time.time()],
                          queue='state').get()
                # Mark all remaining sessions as cancelled
                for s in scan['sessions']:
                    if s['state'] == 'CREATED':
                        s['state'] = 'CANCELLED'
                        dbs =  Session.get_session(s['id'])
                        s.state = 'CANCELLED'
                        db.session.commit()
                        #scans.update({"id": scan['id'], "sessions.id": s['id']}, {"$set": {"sessions.$.state": "CANCELLED", "sessions.$.finished": datetime.datetime.utcnow()}})
                        send_task("minion.backend.tasks.session_finish",
                                  [scan['id'], s['id'], "CANCELLED", time.time()],
                                  queue='state').get()
                # We are done with this scan
                return

        #
        # Move the scan to the FINISHED state
        #

        scan['state'] = 'FINISHED'
        db_scan = Scan.get_scan(scan['id'])
        db_scan.state = 'FINISHED'
        db.session.commit()

        #
        # If one of the plugin has failed then marked the scan as failed
        #
        for session in scan['sessions']:
            if session['state'] == 'FAILED':
                db_scan = Scan.get_scan(scan['id'])
                db_scan.state = 'FAILED'
                db.session.commit()
                scan['state'] = 'FAILED'

        #scans.update({"id": scan_id}, {"$set": {"state": "FINISHED", "finished": datetime.datetime.utcnow()}})
        send_task("minion.backend.tasks.scan_finish",
                  [scan_id, scan['state'], time.time()],
                  queue='state').get()

    except Exception as e:

        #
        # Our exception strategy is simple: if anything was thrown above that we did not explicitly catch then
        # we assume there was a non recoverable error that made the scan fail. We mark it as such and
        # record the exception.
        #

        logger.exception("Error while running scan. Marking scan FAILED.")

        try:
            failure = { "hostname": socket.gethostname(),
                        "reason": "backend-exception",
                        "message": str(e),
                        "exception": traceback.format_exc() }
            send_task("minion.backend.tasks.scan_finish",
                      [scan_id, "FAILED", time.time(), failure],
                      queue='state').get()
        except Exception as e:
            logger.exception("Error when marking scan as FAILED")
Beispiel #5
0
def run_plugin(scan_id, session_id):

    logger.debug("This is run_plugin " + str(scan_id) + " " + str(session_id))

    try:

        #
        # Find the scan for this plugin session. Bail out if the scan has been marked as STOPPED or if
        # the state is not STARTED.
        #
        logger.debug("Retrieving scan to run plugin [%s]" % scan_id)
        scan = get_scan(cfg['api']['url'], scan_id)
        if not scan:
            logger.error("Cannot load scan %s" % scan_id)
            return

        if scan['state'] in ('STOPPING', 'STOPPED'):
            return

        if scan['state'] != 'STARTED':
            logger.error("Scan %s has invalid state. Expected STARTED but got %s" % (scan_id, scan['state']))
            return

        #
        # Find the plugin session in the scan. Bail out if the session has been marked as STOPPED or if
        # the state is not QUEUED.
        #

        session = find_session(scan, session_id)
        db_session = Session.get_session(session_id)

        if not session:
            logger.error("Cannot find session %s/%s" % (scan_id, session_id))
            return

        if db_session.state != 'QUEUED':
            logger.error("Session %s/%s has invalid state. Expected QUEUED but got %s" % (scan_id, session_id, session['state']))
            return

        #
        # Move the session in the STARTED state
        #
        send_task("minion.backend.tasks.session_start",
                  [scan_id, session_id, time.time()],
                  queue='state').get()
        scan['state'] = 'STARTED'

        db_scan = Scan.get_scan(scan['id'])
        db_scan.state = 'STARTED'
        db.session.commit()
        finished = None

        #
        # This is an experiment to see if removing Twisted makes the celery workers more stable.
        #

        def enqueue_output(fd, queue):
            try:
                for line in iter(fd.readline, b''):
                    queue.put(line)
            except Exception as e:
                logger.exception("Error while reading a line from the plugin-runner")
            finally:
                fd.close()
                queue.put(None)

        def make_signal_handler(p):
            def signal_handler(signum, frame):
                p.send_signal(signal.SIGUSR1)
            return signal_handler

        arguments = [ "minion-plugin-runner",
                      "-c", json.dumps(session['configuration']),
                      "-p", session['plugin']['class'],
                      "-s", session_id ]

        p = subprocess.Popen(arguments, bufsize=1, stdout=subprocess.PIPE, close_fds=True)

        signal.signal(signal.SIGUSR1, make_signal_handler(p))

        q = Queue.Queue()
        t = threading.Thread(target=enqueue_output, args=(p.stdout, q))
        t.daemon = True
        t.start()

        while True:
            try:
                line = q.get(timeout=0.25)
                if line is None:
                    break

                line = line.strip()

                if finished is not None:
                    logger.error("Plugin emitted (ignored) message after finishing: " + line)
                    return

                msg = json.loads(line)

                # Issue: persist it
                if msg['msg'] == 'issue':
                    send_task("minion.backend.tasks.session_report_issue",
                              args=[scan_id, session_id, msg['data']],
                              queue='state').get()

                # Progress: update the progress
                if msg['msg'] == 'progress':
                    pass # TODO

                # Finish: update the session state, wait for the plugin runner to finish, return the state
                if msg['msg'] == 'finish':
                    logger.debug("MESSAGE : %s" % json.dumps(msg))
                    finished = msg['data']['state']
                    
                    if msg['data']['state'] in ('FINISHED', 'FAILED', 'STOPPED', 'TERMINATED', 'TIMEOUT', 'ABORTED'):
                        try:
                          params = [scan['id'], session['id'], msg['data']['state'], time.time(), msg['data'].get('failure')]
                        except Exception as e:
                            logger.debug("[Error] %s" % e)
                        send_task("minion.backend.tasks.session_finish", args = params, queue='state').get()

            except Queue.Empty:
                pass

        return_code = p.wait()

        signal.signal(signal.SIGUSR1, signal.SIG_DFL)

        if not finished:
            failure = { "hostname": socket.gethostname(),
                        "message": "The plugin did not finish correctly",
                        "exception": None }
            send_task("minion.backend.tasks.session_finish",
                      [scan['id'], session['id'], 'FAILED', time.time(), failure],
                      queue='state').get()

        return finished

    except Exception as e:

        #
        # Our exception strategy is simple: if anything was thrown above that we did not explicitly catch then
        # we assume there was a non recoverable error that made the plugin session fail. We mark it as such and
        # record the exception.
        #

        logger.exception("Error while running plugin session. Marking session FAILED.")

        try:
            failure = { "hostname": socket.gethostname(),
                        "message": str(e),
                        "exception": traceback.format_exc() }
            send_task("minion.backend.tasks.session_finish",
                      [scan_id, session_id, "FAILED", time.time(), failure],
                      queue='state').get()
        except Exception as e:
            logger.exception("Error when marking scan as FAILED")

        return "FAILED"