Example #1
0
    async def scan(
        self,
        content: bytes,
        payload_meta: Optional[PayloadMeta] = None,
        request_meta: Optional[RequestMeta] = None,
        add_start_dispatch: Optional[List[str]] = None,
        ratelimit: Optional[str] = None,
    ) -> StoqResponse:
        """

        Wrapper for `scan_request` that creates a `Payload` object from bytes

        :param content: Raw bytes to be scanned
        :param payload_meta: Metadata pertaining to originating source
        :param request_meta: Metadata pertaining to the originating request
        :param add_start_dispatch: Force first round of scanning to use specified plugins
        :param ratelimit: Rate limit calls to scan

        """
        self.log.debug(
            f'Content received ({len(content)} bytes): '
            f'PayloadMeta: {helpers.dumps(payload_meta, indent=0)}, '
            f'RequestMeta: {helpers.dumps(request_meta, indent=0)}'
        )
        payload_meta = payload_meta or PayloadMeta()
        payload = Payload(content, payload_meta)
        request_meta = request_meta or RequestMeta()
        request = Request(payloads=[payload], request_meta=request_meta)
        return await self.scan_request(request, add_start_dispatch)
Example #2
0
 async def test_dont_dest_archive_yara(self):
     s = Stoq(base_dir=utils.get_data_dir(), dest_archivers=['dummy_archiver'])
     response = await s.scan(
         self.generic_content, request_meta=RequestMeta(archive_payloads=True)
     )
     # The yara rule 'similar_simple_rule' should set save = False
     self.assertNotIn('dummy_archiver', response.results[0].archivers)
Example #3
0
    def scan_payload(
        self,
        payload: Payload,
        request_meta: Optional[RequestMeta] = None,
        add_start_dispatch: Optional[List[str]] = None,
        add_start_deep_dispatch: Optional[List[str]] = None,
    ) -> StoqResponse:
        """

        Scan an individual payload

        :param payload: ``Payload`` object of data to be scanned
        :param request_meta: Metadata pertaining to the originating request
        :param add_start_dispatch: Force first round of scanning to use specified plugins
        :param add_start_deep_dispatch: Force second round of scanning to use specified plugins

        :return: Complete scan results
        :rtype: StoqResponse

        """
        request_meta = RequestMeta() if request_meta is None else request_meta
        add_start_dispatch = [] if add_start_dispatch is None else add_start_dispatch
        add_start_deep_dispatch = ([] if add_start_deep_dispatch is None else
                                   add_start_deep_dispatch)
        scan_results: List = []
        errors: DefaultDict[str, List[str]] = defaultdict(list)
        scan_queue = [(payload, add_start_dispatch, add_start_deep_dispatch)]
        hashes_seen: Set[str] = set(helpers.get_sha256(payload.content))

        for _recursion_level in range(self.max_recursion + 1):
            next_scan_queue: List[Tuple[Payload, List[str], List[str]]] = []
            for payload, add_dispatch, add_deep_dispatch in scan_queue:
                payload_results, extracted, p_errors = self._single_scan(
                    payload, add_dispatch, add_deep_dispatch, request_meta)
                scan_results.append(payload_results)
                # TODO: Add option for no-dedup
                for ex in extracted:
                    ex_hash = helpers.get_sha256(ex.content)
                    if ex_hash not in hashes_seen:
                        hashes_seen.add(ex_hash)
                        next_scan_queue.append(
                            (ex, ex.payload_meta.dispatch_to, []))
                errors = helpers.merge_dicts(errors, p_errors)
            scan_queue = next_scan_queue

        response = StoqResponse(results=scan_results,
                                request_meta=request_meta,
                                errors=errors)

        self._apply_decorators(response)

        for connector in self._loaded_connector_plugins:
            try:
                connector.save(response)
            except Exception:
                self.log.exception(
                    f'Failed to save results using {connector.__module__}: {response}'
                )
        return response
Example #4
0
 async def test_archiver_in_results(self):
     s = Stoq(base_dir=utils.get_data_dir(), dest_archivers=['simple_archiver'])
     response = await s.scan(
         self.generic_content, request_meta=RequestMeta(archive_payloads=True)
     )
     self.assertIn('simple_archiver', response.results[0].archivers)
     self.assertIn('file_save_id', response.results[0].archivers['simple_archiver'])
     self.assertEqual(len(response.errors), 0)
Example #5
0
 async def test_dest_archive(self):
     s = Stoq(base_dir=utils.get_data_dir(), dest_archivers=['dummy_archiver'])
     dummy_archiver = s.load_plugin('dummy_archiver')
     dummy_archiver.archive = asynctest.create_autospec(
         dummy_archiver.archive, return_value=None
     )
     response = await s.scan(
         self.generic_content, request_meta=RequestMeta(archive_payloads=True)
     )
     dummy_archiver.archive.assert_awaited_once()
     self.assertIn('dummy_archiver', response.results[0].plugins_run['archivers'])
Example #6
0
 async def test_dont_dest_archive_request(self):
     s = Stoq(base_dir=utils.get_data_dir(), dest_archivers=['dummy_archiver'])
     dummy_archiver = s.load_plugin('dummy_archiver')
     dummy_archiver.archive = asynctest.CoroutineMock(return_value=None)
     response = await s.scan(
         self.generic_content,
         add_start_dispatch=['extract_payload'],
         request_meta=RequestMeta(archive_payloads=False),
     )
     dummy_archiver.archive.assert_not_awaited()
     self.assertNotIn('dummy_archiver', response.results[0].plugins_run['archivers'])
     self.assertNotIn('dummy_archiver', response.results[1].plugins_run['archivers'])
Example #7
0
 async def test_dont_dest_archive_payload(self):
     s = Stoq(base_dir=utils.get_data_dir(), dest_archivers=['dummy_archiver'])
     dummy_archiver = s.load_plugin('dummy_archiver')
     dummy_archiver.archive = asynctest.create_autospec(
         dummy_archiver.archive, return_value=None
     )
     response = await s.scan(
         self.generic_content,
         payload_meta=PayloadMeta(should_archive=False),
         add_start_dispatch=['extract_payload'],
         request_meta=RequestMeta(archive_payloads=True),
     )
     dummy_archiver.archive.assert_awaited_once()
     self.assertNotIn('dummy_archiver', response.results[0].plugins_run['archivers'])
     self.assertIn('dummy_archiver', response.results[1].plugins_run['archivers'])
Example #8
0
    async def test_reconstruct_all_subresponses(self):
        # Construct a fake stoq_response as if it were generated from a file
        # A.zip that contains two files, B.txt and C.zip, where C.zip contains D.txt
        results = [
            Payload(content=b'', payload_id='A.zip', payload_meta=PayloadMeta()),
            Payload(
                content=b'',
                payload_id='B.txt',
                payload_meta=PayloadMeta(),
                extracted_from='A.zip',
                extracted_by='fake',
            ),
            Payload(
                content=b'',
                payload_id='C.zip',
                payload_meta=PayloadMeta(),
                extracted_from='A.zip',
                extracted_by='fake',
            ),
            Payload(
                content=b'',
                payload_id='D.txt',
                payload_meta=PayloadMeta(),
                extracted_from='C.zip',
                extracted_by='fake',
            ),
        ]
        request = Request(request_meta=RequestMeta(extra_data={'check': 'me'}))
        payload_count = 1
        for result in results:
            result.results.workers['fake'] = f'result-{payload_count}'
            result.results.plugins_run['workers'].append('fake')
            request.payloads.append(result)
            payload_count += 1

        initial_response = StoqResponse(request)
        s = Stoq(base_dir=utils.get_data_dir(), decorators=['simple_decorator'])
        all_subresponses = [
            r async for r in s.reconstruct_all_subresponses(initial_response)
        ]
        # We expect there to be four "artificial" responses generated, one for
        # each payload as the root.
        self.assertEqual(len(all_subresponses), 4)
        # We expect the first response to have all 4 payloads, the second response
        # to have just the second payload, the third response to have the third
        # and fourth payload, and the fourth response to have just the fourth payload
        self.assertEqual(
            [len(stoq_response.results) for stoq_response in all_subresponses],
            [4, 1, 2, 1],
        )
        self.assertEqual(
            [
                stoq_response.results[0].workers['fake']
                for stoq_response in all_subresponses
            ],
            ['result-1', 'result-2', 'result-3', 'result-4'],
        )
        self.assertTrue(
            all(
                'simple_decorator' in stoq_response.decorators
                for stoq_response in all_subresponses
            )
        )
        # Assert that they all have the same scan ID
        self.assertEqual(
            len({stoq_response.scan_id for stoq_response in all_subresponses}), 1
        )
Example #9
0
 def test_requestmeta_to_str(self):
     response = RequestMeta()
     response_str = str(response)
     response_dict = json.loads(response_str)
     self.assertIsInstance(response_str, str)
     self.assertIsInstance(response_dict, dict)
Example #10
0
 async def test_archiver_not_in_results(self):
     s = Stoq(base_dir=utils.get_data_dir(), dest_archivers=['dummy_archiver'])
     response = await s.scan(
         self.generic_content, request_meta=RequestMeta(archive_payloads=True)
     )
     self.assertNotIn('dummy_archiver', response.results[0].archivers)