Пример #1
0
    def test_unicode(self):
        in_list = [u'ñ', u'ý']
        imi = MultiIn(in_list)

        result = to_list(imi.query('abcn'))
        self.assertEqual(0, len(result))

        result = to_list(imi.query('abcñ'))
        self.assertEqual(1, len(result))
        self.assertEqual('ñ', result[0])
Пример #2
0
    def test_unicode(self):
        in_list = [u'ñ', u'ý']
        imi = MultiIn(in_list)

        result = to_list(imi.query('abcn'))
        self.assertEqual(0, len(result))

        result = to_list(imi.query('abcñ'))
        self.assertEqual(1, len(result))
        self.assertEqual('ñ', result[0])
Пример #3
0
    def test_simplest(self):
        in_list = ['123', '456', '789']
        imi = MultiIn(in_list)

        result = to_list(imi.query('456'))
        self.assertEqual(1, len(result))
        self.assertEqual('456', result[0])

        result = to_list(imi.query('789'))
        self.assertEqual(1, len(result))
        self.assertEqual('789', result[0])
Пример #4
0
    def test_simplest(self):
        in_list = ['123', '456', '789']
        imi = MultiIn(in_list)

        result = to_list(imi.query('456'))
        self.assertEqual(1, len(result))
        self.assertEqual('456', result[0])

        result = to_list(imi.query('789'))
        self.assertEqual(1, len(result))
        self.assertEqual('789', result[0])
Пример #5
0
    def test_assoc_obj(self):
        in_list = [('123456', None, None), ('abcdef', 1, 2)]
        imi = MultiIn(in_list)

        result = to_list(imi.query('spam1234567890eggs'))
        self.assertEqual(1, len(result))
        self.assertEqual('123456', result[0][0])
        self.assertEqual(None, result[0][1])
        self.assertEqual(None, result[0][2])

        result = to_list(imi.query('foo abcdef bar'))
        self.assertEqual(1, len(result))
        self.assertEqual('abcdef', result[0][0])
        self.assertEqual(1, result[0][1])
        self.assertEqual(2, result[0][2])
Пример #6
0
    def test_special_char(self):
        in_list = ['javax.naming.NameNotFoundException', '7', '8']
        imi = MultiIn(in_list)

        s = 'abc \\n javax.naming.NameNotFoundException \\n 123'
        result = to_list(imi.query(s))
        self.assertEqual(1, len(result))
        self.assertEqual('javax.naming.NameNotFoundException', result[0])

        in_list = [u'abc(def)', u'foo(bar)']
        imi = MultiIn(in_list)

        result = to_list(imi.query('foo abc(def) bar'))
        self.assertEqual(1, len(result))
        self.assertEqual('abc(def)', result[0])
Пример #7
0
    def test_special_char(self):
        in_list = ['javax.naming.NameNotFoundException', '7', '8']
        imi = MultiIn(in_list)

        s = 'abc \\n javax.naming.NameNotFoundException \\n 123'
        result = to_list(imi.query(s))
        self.assertEqual(1, len(result))
        self.assertEqual('javax.naming.NameNotFoundException', result[0])

        in_list = [u'abc(def)', u'foo(bar)']
        imi = MultiIn(in_list)

        result = to_list(imi.query('foo abc(def) bar'))
        self.assertEqual(1, len(result))
        self.assertEqual('abc(def)', result[0])
Пример #8
0
    def test_assoc_obj(self):
        in_list = [('123456', None, None), ('abcdef', 1, 2)]
        imi = MultiIn(in_list)

        result = to_list(imi.query('spam1234567890eggs'))
        self.assertEqual(1, len(result))
        self.assertEqual('123456', result[0][0])
        self.assertEqual(None, result[0][1])
        self.assertEqual(None, result[0][2])

        result = to_list(imi.query('foo abcdef bar'))
        self.assertEqual(1, len(result))
        self.assertEqual('abcdef', result[0][0])
        self.assertEqual(1, result[0][1])
        self.assertEqual(2, result[0][2])
Пример #9
0
    def test_null_byte(self):
        in_list = ['\x00']
        imi = MultiIn(in_list)

        result = to_list(imi.query('abc\x00def'))
        self.assertEqual(1, len(result))
        self.assertEqual('\x00', result[0])
Пример #10
0
    def test_null_byte(self):
        in_list = ['\x00']
        imi = MultiIn(in_list)

        result = to_list(imi.query('abc\x00def'))
        self.assertEqual(1, len(result))
        self.assertEqual('\x00', result[0])
Пример #11
0
    def test_many_start_similar(self):

        prefix = '0000000'

        def generator(count):
            for _ in xrange(count):
                a = rand_number(5)
                yield prefix + a

        fixed_samples = [prefix + '78912']
        in_list = itertools.chain(generator(5000), fixed_samples)

        imi = MultiIn(in_list)

        result = to_list(imi.query(prefix + '78912'))
        self.assertEqual(1, len(result))
        self.assertEqual(prefix + '78912', result[0])
Пример #12
0
    def test_dup_keys(self):
        def generator(count):
            for _ in xrange(count):
                a = rand_number(5)
                yield a

                a = int(a)
                b = int(rand_number(5))
                yield str(a * b)

        fixed_samples_1 = ['123', '456']
        fixed_samples_2 = ['123', '456', '789']
        in_list = itertools.chain(fixed_samples_1, generator(5000),
                                  fixed_samples_2)

        imi = MultiIn(in_list)

        result = to_list(imi.query('789'))
        self.assertEqual(1, len(result))
        self.assertEqual('789', result[0])
Пример #13
0
    def test_very_large_multiin(self):

        # Change this to test larger sizes
        COUNT = 5000

        def generator(count):
            for _ in xrange(count):
                a = rand_number(5)
                yield a

                a = int(a)
                b = int(rand_number(5))
                yield str(a * b)

        fixed_samples = ['123', '456', '789']
        in_list = itertools.chain(fixed_samples, generator(COUNT))

        imi = MultiIn(in_list)

        result = to_list(imi.query('456'))
        self.assertEqual(1, len(result))
        self.assertEqual('456', result[0])
Пример #14
0
    def test_dup(self):
        in_list = ['123', '456', '789']
        imi = MultiIn(in_list)

        result = to_list(imi.query('456 456'))
        self.assertEqual(1, len(result))
Пример #15
0
class keys(GrepPlugin):
    """
    Grep every page for public and private keys.

    :author: Yvonne Kim
    """
    def __init__(self):
        GrepPlugin.__init__(self)

        self.PUBLIC = 'public'
        self.PRIVATE = 'private'

        PUBLIC = 'public'
        PRIVATE = 'private'

        KEY_FORMATS = (
            # RSA (PKCS1)
            ('-----BEGIN RSA PRIVATE KEY-----', ('RSA-PRIVATE', PRIVATE)), 
            ('-----BEGIN RSA PUBLIC KEY-----', ('RSA-PUBLIC', PUBLIC)),
            ('ssh-rsa', ('RSA-PUBLIC', PUBLIC)),
            
            # DSA
            ('-----BEGIN DSA PRIVATE KEY-----', ('DSA-PRIVATE', PRIVATE)),
            ('-----BEGIN DSA PUBLIC KEY-----', ('DSA-PUBLIC', PUBLIC)),
            ('ssh-dss', ('DSA-PUBLIC', PUBLIC)),
            
            # Elliptic Curve
            ('-----BEGIN EC PRIVATE KEY-----', ('EC-PRIVATE', PRIVATE)),
            ('-----BEGIN EC PUBLIC KEY-----', ('EC-PUBLIC', PUBLIC)),
            ('ecdsa-sha2-nistp256', ('EC-PUBLIC', PUBLIC)),
            
            # SSH2
            ('---- BEGIN SSH2 PUBLIC KEY ----', ('SSH2-PRIVATE', PRIVATE)),
            ('---- BEGIN SSH2 PRIVATE KEY ----', ('SSH2-PUBLIC', PUBLIC)),

            # ed25519 (OpenSSH)
            ('-----BEGIN OPENSSH PRIVATE KEY-----', ('ED25519-SSH-PRIVATE', PRIVATE)),
            ('-----BEGIN OPENSSH PUBLIC KEY-----', ('ED25519-SSH-PUBLIC', PUBLIC)),
            ('ssh-ed25519', ('ED25519-SSH-PUBLIC', PUBLIC)),
            
            # PKCS8
            ('-----BEGIN PRIVATE KEY-----', ('PKCS8-PRIVATE', PRIVATE)),
            ('-----BEGIN PUBLIC KEY-----', ('PKCS8-PUBLIC', PUBLIC)),
            ('-----BEGIN ENCRYPTED PRIVATE KEY-----', ('PKCS8-ENCRYPTED-PRIVATE', PRIVATE)),
            ('-----BEGIN ENCRYPTED PUBLIC KEY-----', ('PKCS8-ENCRYPTED-PUBLIC', PUBLIC)),
            
            # XML
            ('<RSAKeyPair>', ('XML-RSA', PRIVATE)),
            ('<RSAKeyValue>', ('.NET-XML-RSA', PUBLIC))
        )        

        self._multi_in = MultiIn(KEY_FORMATS)


    def grep(self, request, response):
        """
        Plugin entry point, find the error pages and report them.

        :param request: The HTTP request object.
        :param response: The HTTP response object
        :return: None
        """
        if not response.is_text_or_html():
            return

        if not response.get_code() == 200:
            return

        for _, (key, keypair_type) in self._multi_in.query(response.body):
            desc = u'The URL: "%s" discloses a key of type: "%s"'
            desc %= (response.get_url(), key)

            if keypair_type == self.PUBLIC:
                item = Info(
                    'Public key disclosure', desc, response.id, self.get_name())

            elif keypair_type == self.PRIVATE:
                item = Vuln(
                    'Private key disclosure', desc, severity.HIGH, response.id, self.get_name())                

            item.set_url(response.get_url())
            item.add_to_highlight(key)

            self.kb_append(self, 'keys', item)
        

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """

        return """
Пример #16
0
class phishtank(CrawlPlugin):
    """
    Search the phishtank.com database to determine if your server is (or was)
    being used in phishing scams.

    :author: Andres Riancho ([email protected])
    :author: Special thanks to http://www.phishtank.com/ !
    """
    PHISHTANK_DB = os.path.join(ROOT_PATH, 'plugins', 'crawl', 'phishtank',
                                'index.csv')

    def __init__(self):
        CrawlPlugin.__init__(self)
        self._multi_in = None

    @runonce(exc_class=RunOnce)
    def crawl(self, fuzzable_request, debugging_id):
        """
        Plugin entry point, performs all the work.

        :param debugging_id: A unique identifier for this call to discover()
        :param fuzzable_request: A fuzzable_request instance that contains
                                    (among other things) the URL to test.
        """
        to_check = self._get_to_check(fuzzable_request.get_url())

        # I found some URLs, create fuzzable requests
        pt_matches = self._is_in_phishtank(to_check)

        if not pt_matches:
            return

        for ptm in pt_matches:
            fr = FuzzableRequest(ptm.url)
            self.output_queue.put(fr)

        desc = ('The URL: "%s" seems to be involved in a Phishing scam.'
                ' Please see %s for more info.')
        desc %= (ptm.url, ptm.more_info_url)

        v = Vuln('Phishing scam', desc, severity.MEDIUM, [], self.get_name())
        v.set_url(ptm.url)

        kb.kb.append(self, 'phishtank', v)
        om.out.vulnerability(v.get_desc(), severity=v.get_severity())

    def _get_to_check(self, target_url):
        """
        :param target_url: The url object we can use to extract some information
        :return: From the domain, get a list of FQDN, rootDomain and IP address.
        """
        def addrinfo(url):
            return [x[4][0] for x in socket.getaddrinfo(url.get_domain(), 0)]

        def getfqdn(url):
            return [
                socket.getfqdn(url.get_domain()),
            ]

        def root_domain(url):
            if not is_ip_address(url.get_domain()):
                return [
                    url.get_root_domain(),
                ]

            return []

        res = set()
        for func in (addrinfo, getfqdn, root_domain):
            try:
                data_lst = func(target_url)
            except Exception:
                pass
            else:
                for data in data_lst:
                    res.add(data)

        return res

    def _is_in_phishtank(self, to_check):
        """
        Reads the phishtank db and tries to match the entries on that db with
        the to_check

        :return: A list with the sites to match against the phishtank db
        """
        try:
            phishtank_db_fd = file(self.PHISHTANK_DB, 'r')
        except Exception as e:
            msg = 'Failed to open phishtank database: "%s", exception: "%s".'
            raise BaseFrameworkException(msg % (self.PHISHTANK_DB, e))

        pt_matches = []
        self._multi_in = MultiIn(to_check)

        om.out.debug('Starting the phishtank CSV parsing.')

        pt_csv_reader = csv.reader(phishtank_db_fd,
                                   delimiter=' ',
                                   quotechar='|',
                                   quoting=csv.QUOTE_MINIMAL)

        for phishing_url, phishtank_detail_url in pt_csv_reader:
            pt_match = self._url_matches(phishing_url, phishtank_detail_url)
            if pt_match:
                pt_matches.append(pt_match)

        om.out.debug('Finished CSV parsing.')

        return pt_matches

    def _url_matches(self, phishing_url, phishtank_detail_url):
        """
        :param url: The url (as string) from the phishtank database
        :return: A PhishTankMatch if url matches what we're looking for, None
                 if there is no match
        """
        for query_result in self._multi_in.query(phishing_url):
            phish_url = URL(phishing_url)
            target_host_url = URL(query_result[0])

            if target_host_url.get_domain() == phish_url.get_domain() or \
            phish_url.get_domain().endswith('.' + target_host_url.get_domain()):

                phish_detail_url = URL(phishtank_detail_url)
                ptm = PhishTankMatch(phish_url, phish_detail_url)
                return ptm

        return None

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #17
0
 def test_is_generator(self):
     in_list = ['123', '456', '789']
     imi = MultiIn(in_list)
     results = imi.query('456')
     self.assertIsInstance(results, types.GeneratorType)
Пример #18
0
    def test_dup(self):
        in_list = ['123', '456', '789']
        imi = MultiIn(in_list)

        result = to_list(imi.query('456 456'))
        self.assertEqual(1, len(result))
Пример #19
0
 def test_is_generator(self):
     in_list = ['123', '456', '789']
     imi = MultiIn(in_list)
     results = imi.query('456')
     self.assertIsInstance(results, types.GeneratorType)
Пример #20
0
class ssi(AuditPlugin):
    """
    Find server side inclusion vulnerabilities.
    :author: Andres Riancho ([email protected])
    """
    def __init__(self):
        AuditPlugin.__init__(self)

        # Internal variables
        self._persistent_multi_in = None
        self._expected_mutant_dict = DiskDict(table_prefix='ssi')
        self._extract_expected_re = re.compile('[1-9]{5}')

    def audit(self, freq, orig_response, debugging_id):
        """
        Tests an URL for server side inclusion vulnerabilities.

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        ssi_strings = self._get_ssi_strings()
        mutants = create_mutants(freq, ssi_strings, orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      mutants,
                                      self._analyze_result,
                                      debugging_id=debugging_id)

    def _get_ssi_strings(self):
        """
        This method returns a list of server sides to try to include.

        :return: A string, see above.
        """
        # Generic
        yield '<!--#exec cmd="echo -n %s;echo -n %s" -->' % get_seeds()

        # Perl SSI
        yield ('<!--#set var="SEED_A" value="%s" -->'
               '<!--#echo var="SEED_A" -->'
               '<!--#set var="SEED_B" value="%s" -->'
               '<!--#echo var="SEED_B" -->' % get_seeds())

        # Smarty
        # http://www.smarty.net/docsv2/en/language.function.math.tpl
        yield '{math equation="x * y" x=%s y=%s}' % get_seeds()

        # Mako
        # http://docs.makotemplates.org/en/latest/syntax.html
        yield '${%s * %s}' % get_seeds()

        # Jinja2 and Twig
        # http://jinja.pocoo.org/docs/dev/templates/#math
        # http://twig.sensiolabs.org/doc/templates.html
        yield '{{%s * %s}}' % get_seeds()

        # Generic
        yield '{%s * %s}' % get_seeds()

    def _get_expected_results(self, mutant):
        """
        Extracts the potential results from the mutant payload and returns them
        in a list.
        """
        sent_payload = mutant.get_token_payload()
        seed_numbers = self._extract_expected_re.findall(sent_payload)

        seed_a = int(seed_numbers[0])
        seed_b = int(seed_numbers[1])

        return [str(seed_a * seed_b), '%s%s' % (seed_a, seed_b)]

    def _analyze_result(self, mutant, response):
        """
        Analyze the result of the previously sent request.
        :return: None, save the vuln to the kb.
        """
        # Store the mutants in order to be able to analyze the persistent case
        # later
        expected_results = self._get_expected_results(mutant)

        for expected_result in expected_results:
            self._expected_mutant_dict[expected_result] = mutant

        # Now we analyze the "reflected" case
        if self._has_bug(mutant):
            return

        for expected_result in expected_results:
            if expected_result not in response:
                continue

            if expected_result in mutant.get_original_response_body():
                continue

            desc = 'Server side include (SSI) was found at: %s'
            desc %= mutant.found_at()

            v = Vuln.from_mutant('Server side include vulnerability',
                                 desc, severity.HIGH, response.id,
                                 self.get_name(), mutant)

            v.add_to_highlight(expected_result)
            self.kb_append_uniq(self, 'ssi', v)

    def end(self):
        """
        This method is called when the plugin wont be used anymore and is used
        to find persistent SSI vulnerabilities.

        Example where a persistent SSI can be found:

        Say you have a "guest book" (a CGI application that allows visitors
        to leave messages for everyone to see) on a server that has SSI
        enabled. Most such guest books around the Net actually allow visitors
        to enter HTML code as part of their comments. Now, what happens if a
        malicious visitor decides to do some damage by entering the following:

        <!--#exec cmd="ls" -->

        If the guest book CGI program was designed carefully, to strip SSI
        commands from the input, then there is no problem. But, if it was not,
        there exists the potential for a major headache!

        For a working example please see moth VM.
        """
        fuzzable_request_set = kb.kb.get_all_known_fuzzable_requests()

        debugging_id = rand_alnum(8)
        om.out.debug('Starting stored SSI search (did=%s)' % debugging_id)

        #
        # TODO
        #
        # After identifying the issue we have in these lines, I should change
        # this code to use:
        #
        #   len(self._expected_mutant_dict)
        #   self._expected_mutant_dict.iterkeys()
        #
        # Those two methods are faster and consume less memory than the things
        # I'm doing below now.
        #
        expected_strings = self._expected_mutant_dict.keys()
        args = (len(expected_strings), debugging_id)
        om.out.debug('About to create MultiIn with %s keys (did=%s)' % args)

        self._persistent_multi_in = MultiIn(expected_strings)
        om.out.debug('Created stored SSI MultiIn (did=%s)' % debugging_id)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      fuzzable_request_set,
                                      self._analyze_persistent,
                                      cache=False,
                                      debugging_id=debugging_id)

        self._expected_mutant_dict.cleanup()

    def _analyze_persistent(self, freq, response):
        """
        Analyze the response of sending each fuzzable request found by the
        framework, trying to identify any locations where we might have injected
        a payload.

        :param freq: The fuzzable request
        :param response: The HTTP response
        :return: None, vulns are stored in KB
        """
        msg = 'Analyzing HTTP response %s to verify if SSI string is found'
        om.out.debug(msg % response.get_uri())

        for matched_expected_result in self._persistent_multi_in.query(
                response.get_body()):
            # We found one of the expected results, now we search the
            # self._expected_mutant_dict to find which of the mutants sent it
            # and create the vulnerability
            mutant = self._expected_mutant_dict[matched_expected_result]

            desc = ('Server side include (SSI) was found at: %s'
                    ' The result of that injection is shown by browsing'
                    ' to "%s".')
            desc %= (mutant.found_at(), freq.get_url())

            v = Vuln.from_mutant(
                'Persistent server side include vulnerability', desc,
                severity.HIGH, response.id, self.get_name(), mutant)

            v.add_to_highlight(matched_expected_result)
            self.kb_append(self, 'ssi', v)

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """
Пример #21
0
class ssi(AuditPlugin):
    """
    Find server side inclusion vulnerabilities.
    :author: Andres Riancho ([email protected])
    """
    def __init__(self):
        AuditPlugin.__init__(self)

        # Internal variables
        self._persistent_multi_in = None
        self._expected_mutant_dict = DiskDict(table_prefix='ssi')
        self._extract_expected_re = re.compile('[1-9]{5}')

    def audit(self, freq, orig_response, debugging_id):
        """
        Tests an URL for server side inclusion vulnerabilities.

        :param freq: A FuzzableRequest
        :param orig_response: The HTTP response associated with the fuzzable request
        :param debugging_id: A unique identifier for this call to audit()
        """
        ssi_strings = self._get_ssi_strings()
        mutants = create_mutants(freq, ssi_strings, orig_resp=orig_response)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      mutants,
                                      self._analyze_result,
                                      debugging_id=debugging_id)

    def _get_ssi_strings(self):
        """
        This method returns a list of server sides to try to include.

        :return: A string, see above.
        """
        # Generic
        yield '<!--#exec cmd="echo -n %s;echo -n %s" -->' % get_seeds()

        # Perl SSI
        yield ('<!--#set var="SEED_A" value="%s" -->'
               '<!--#echo var="SEED_A" -->'
               '<!--#set var="SEED_B" value="%s" -->'
               '<!--#echo var="SEED_B" -->' % get_seeds())

        # Smarty
        # http://www.smarty.net/docsv2/en/language.function.math.tpl
        yield '{math equation="x * y" x=%s y=%s}' % get_seeds()

        # Mako
        # http://docs.makotemplates.org/en/latest/syntax.html
        yield '${%s * %s}' % get_seeds()

        # Jinja2 and Twig
        # http://jinja.pocoo.org/docs/dev/templates/#math
        # http://twig.sensiolabs.org/doc/templates.html
        yield '{{%s * %s}}' % get_seeds()

        # Generic
        yield '{%s * %s}' % get_seeds()

    def _get_expected_results(self, mutant):
        """
        Extracts the potential results from the mutant payload and returns them
        in a list.
        """
        sent_payload = mutant.get_token_payload()
        seed_numbers = self._extract_expected_re.findall(sent_payload)

        seed_a = int(seed_numbers[0])
        seed_b = int(seed_numbers[1])

        return [str(seed_a * seed_b), '%s%s' % (seed_a, seed_b)]

    def _analyze_result(self, mutant, response):
        """
        Analyze the result of the previously sent request.
        :return: None, save the vuln to the kb.
        """
        # Store the mutants in order to be able to analyze the persistent case
        # later
        expected_results = self._get_expected_results(mutant)

        for expected_result in expected_results:
            self._expected_mutant_dict[expected_result] = mutant

        # Now we analyze the "reflected" case
        if self._has_bug(mutant):
            return

        for expected_result in expected_results:
            if expected_result not in response:
                continue

            if expected_result in mutant.get_original_response_body():
                continue

            desc = 'Server side include (SSI) was found at: %s'
            desc %= mutant.found_at()

            v = Vuln.from_mutant('Server side include vulnerability', desc,
                                 severity.HIGH, response.id,
                                 self.get_name(), mutant)

            v.add_to_highlight(expected_result)
            self.kb_append_uniq(self, 'ssi', v)

    def end(self):
        """
        This method is called when the plugin wont be used anymore and is used
        to find persistent SSI vulnerabilities.

        Example where a persistent SSI can be found:

        Say you have a "guest book" (a CGI application that allows visitors
        to leave messages for everyone to see) on a server that has SSI
        enabled. Most such guest books around the Net actually allow visitors
        to enter HTML code as part of their comments. Now, what happens if a
        malicious visitor decides to do some damage by entering the following:

        <!--#exec cmd="ls" -->

        If the guest book CGI program was designed carefully, to strip SSI
        commands from the input, then there is no problem. But, if it was not,
        there exists the potential for a major headache!

        For a working example please see moth VM.
        """
        fuzzable_request_set = kb.kb.get_all_known_fuzzable_requests()

        debugging_id = rand_alnum(8)
        om.out.debug('Starting stored SSI search (did=%s)' % debugging_id)

        self._persistent_multi_in = MultiIn(self._expected_mutant_dict.keys())
        om.out.debug('Created stored SSI MultiIn (did=%s)' % debugging_id)

        self._send_mutants_in_threads(self._uri_opener.send_mutant,
                                      fuzzable_request_set,
                                      self._analyze_persistent,
                                      cache=False,
                                      debugging_id=debugging_id)

        self._expected_mutant_dict.cleanup()

    def _analyze_persistent(self, freq, response):
        """
        Analyze the response of sending each fuzzable request found by the
        framework, trying to identify any locations where we might have injected
        a payload.

        :param freq: The fuzzable request
        :param response: The HTTP response
        :return: None, vulns are stored in KB
        """
        for matched_expected_result in self._persistent_multi_in.query(response.get_body()):
            # We found one of the expected results, now we search the
            # self._expected_mutant_dict to find which of the mutants sent it
            # and create the vulnerability
            mutant = self._expected_mutant_dict[matched_expected_result]

            desc = ('Server side include (SSI) was found at: %s'
                    ' The result of that injection is shown by browsing'
                    ' to "%s".')
            desc %= (mutant.found_at(), freq.get_url())

            v = Vuln.from_mutant('Persistent server side include vulnerability',
                                 desc, severity.HIGH, response.id,
                                 self.get_name(), mutant)

            v.add_to_highlight(matched_expected_result)
            self.kb_append(self, 'ssi', v)

    def get_long_desc(self):
        """
        :return: A DETAILED description of the plugin functions and features.
        """
        return """