├── .gitignore ├── LICENSE.md ├── README.md ├── _version.py ├── api └── callers │ ├── api_caller.py │ ├── feed │ ├── __init__.py │ ├── api_feed.py │ └── api_feed_latest.py │ ├── key │ ├── __init__.py │ ├── api_key_create.py │ └── api_key_current.py │ ├── overview │ ├── __init__.py │ ├── api_overview.py │ ├── api_overview_refresh.py │ ├── api_overview_sample.py │ └── api_overview_summary.py │ ├── report │ ├── __init__.py │ ├── api_report_bulk_summary.py │ ├── api_report_demo_bulk.py │ ├── api_report_dropped_file_raw.py │ ├── api_report_dropped_files.py │ ├── api_report_enhanced_summary.py │ ├── api_report_file.py │ ├── api_report_screenshots.py │ ├── api_report_state.py │ └── api_report_summary.py │ ├── scan │ ├── __init__.py │ ├── api_scan_convert_to_full.py │ ├── api_scan_file.py │ ├── api_scan_scan.py │ ├── api_scan_state.py │ ├── api_scan_url_for_analysis.py │ └── api_scan_url_to_file.py │ ├── search │ ├── __init__.py │ ├── api_search_hash.py │ ├── api_search_hashes.py │ ├── api_search_states.py │ └── api_search_terms.py │ ├── submit │ ├── __init__.py │ ├── api_submit_dropped_file.py │ ├── api_submit_file.py │ ├── api_submit_hash_for_url.py │ ├── api_submit_reanalyze.py │ ├── api_submit_url_for_analysis.py │ └── api_submit_url_to_file.py │ └── system │ ├── __init__.py │ ├── api_system_backend.py │ ├── api_system_environments.py │ ├── api_system_heartbeat.py │ ├── api_system_in_progress.py │ ├── api_system_php.py │ ├── api_system_queue_size.py │ ├── api_system_state.py │ ├── api_system_stats.py │ └── api_system_version.py ├── cli ├── __init__.py ├── arguments_builders │ ├── __init__.py │ ├── default_cli_arguments.py │ ├── demo_bulk_cli_arguments.py │ ├── search_cli_arguments.py │ └── submission_cli_arguments.py ├── cli_file_writer.py ├── cli_helper.py ├── cli_msg_printer.py ├── cli_prompts.py ├── formatter │ ├── cli_json_formatter.py │ └── cli_limits_formatter.py ├── types │ └── values_in_between_action.py └── wrappers │ ├── __init__.py │ ├── cli_caller.py │ ├── feed │ ├── __init__.py │ ├── cli_feed.py │ └── cli_feed_latest.py │ ├── key │ ├── __init__.py │ ├── cli_key_create.py │ └── cli_key_current.py │ ├── overview │ ├── __init__.py │ ├── cli_overview.py │ ├── cli_overview_refresh.py │ ├── cli_overview_sample.py │ └── cli_overview_summary.py │ ├── report │ ├── __init__.py │ ├── cli_report_bulk_summary.py │ ├── cli_report_demo_bulk.py │ ├── cli_report_dropped_file_raw.py │ ├── cli_report_dropped_files.py │ ├── cli_report_enhanced_summary.py │ ├── cli_report_file.py │ ├── cli_report_screenshots.py │ ├── cli_report_state.py │ └── cli_report_summary.py │ ├── scan │ ├── __init__.py │ ├── cli_scan_convert_to_full.py │ ├── cli_scan_file.py │ ├── cli_scan_scan.py │ ├── cli_scan_state.py │ ├── cli_scan_url_for_analysis.py │ └── cli_scan_url_to_file.py │ ├── search │ ├── __init__.py │ ├── cli_search_hash.py │ ├── cli_search_hashes.py │ ├── cli_search_states.py │ └── cli_search_terms.py │ ├── submit │ ├── __init__.py │ ├── cli_submit_dropped_file.py │ ├── cli_submit_file.py │ ├── cli_submit_hash_for_url.py │ ├── cli_submit_reanalyze.py │ ├── cli_submit_url_for_analysis.py │ └── cli_submit_url_to_file.py │ └── system │ ├── __init__.py │ ├── cli_system_backend.py │ ├── cli_system_environments.py │ ├── cli_system_heartbeat.py │ ├── cli_system_in_progress.py │ ├── cli_system_php.py │ ├── cli_system_queue_size.py │ ├── cli_system_state.py │ ├── cli_system_stats.py │ └── cli_system_version.py ├── colors.py ├── config_tpl.py ├── constants.py ├── exceptions.py ├── helper_classes ├── cli_helper.py └── file_helper.py ├── img ├── cli_example.png ├── icon.png └── scan_example.png ├── requirements.txt ├── tests ├── _data │ ├── archive-file.zip │ ├── current_key_cache_file.json │ ├── hashes │ ├── ids │ ├── my-archive.bin.gz │ └── screenshots │ │ ├── image1.png │ │ └── image2.png ├── _requests_scenarios │ ├── cache_1.py │ ├── cache_2.py │ ├── feed │ │ ├── feed.py │ │ └── feed_latest.py │ ├── key │ │ ├── key_create.py │ │ └── key_current.py │ ├── overview │ │ ├── overview.py │ │ ├── overview_refresh.py │ │ ├── overview_sample.py │ │ └── overview_summary.py │ ├── report │ │ ├── report_bulk_summary.py │ │ ├── report_demo_bulk.py │ │ ├── report_dropped_file_raw.py │ │ ├── report_dropped_files.py │ │ ├── report_enhanced_summary.py │ │ ├── report_file.py │ │ ├── report_screenshots.py │ │ ├── report_state.py │ │ └── report_summary.py │ ├── scan │ │ ├── scan_convert_to_full.py │ │ ├── scan_file.py │ │ ├── scan_scan.py │ │ ├── scan_state.py │ │ ├── scan_url_for_analysis.py │ │ └── scan_url_to_file.py │ ├── search │ │ ├── search_hash.py │ │ ├── search_hashes.py │ │ ├── search_states.py │ │ └── search_terms.py │ └── submit │ │ ├── submit_dropped_file.py │ │ ├── submit_file.py │ │ ├── submit_hash_for_url.py │ │ ├── submit_reanalyze.py │ │ ├── submit_url_for_analysis.py │ │ └── submit_url_to_file.py ├── base_test.py ├── conftest.py ├── feed │ ├── test_feed.py │ └── test_feed_latest.py ├── key │ ├── test_key_create.py │ └── test_key_current.py ├── overview │ ├── test_get_overview.py │ ├── test_get_overview_sample.py │ ├── test_get_overview_summary.py │ └── test_get_refreshed_overview.py ├── report │ ├── test_report_bulk_summary.py │ ├── test_report_demo_bulk.py │ ├── test_report_dropped_file_raw.py │ ├── test_report_dropped_files.py │ ├── test_report_enhanced_summary.py │ ├── test_report_file.py │ ├── test_report_screenshots.py │ ├── test_report_state.py │ └── test_report_summary.py ├── scan │ ├── test_scan_convert_to_full.py │ ├── test_scan_file.py │ ├── test_scan_scan.py │ ├── test_scan_state.py │ ├── test_scan_url_for_analysis.py │ └── test_scan_url_to_file.py ├── search │ ├── test_search_hash.py │ ├── test_search_hashes.py │ ├── test_search_states.py │ └── test_search_terms.py ├── submit │ ├── test_submit_dropped_file.py │ ├── test_submit_file.py │ ├── test_submit_hash_for_url.py │ ├── test_submit_reanalyze.py │ ├── test_submit_url_for_analysis.py │ └── test_submit_url_to_file.py └── test_cache.py └── vxapi.py /.gitignore: -------------------------------------------------------------------------------- 1 | /config.py 2 | /.idea 3 | /__pycache__ 4 | api_classes/__pycache__ 5 | cli_classes/__pycache__/*.pyc 6 | /output 7 | *.pyc 8 | /cli.log 9 | /cache 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Alt text](/img/icon.png?raw=true "Falcon Sandbox API Icon") 2 | 3 | # VxWebService Python API Connector 4 | The Falcon Sandbox Python API Connector (e.g. for https://www.hybrid-analysis.com/). 5 | 6 | ## Requirements 7 | 8 | - [Python](http://www.python.org) >= 3.4.0 9 | 10 | > To install the required python modules, please run `pip install -r requirements.txt` 11 | > Using Debian/Ubuntu OS, this can be done by calling `sudo apt-get install python3-pip`. It will then be available via `pip3` 12 | > Using Windows, this can be done automatically when installing `python` (proper checkbox on the installer has to be checked). It should be available via `pip` 13 | 14 | Versions 15 | --- 16 | 17 | ### V2 18 | 19 | This version has broad support for all capabilities of VxWebService APIv2 and much more. New features include: 20 | 21 | - support for APIv2 22 | - improved application performance 23 | - unified and simplified CLI schema 24 | - bulk quick scan and sandbox submissions 25 | - improved file handling 26 | - test coverage 27 | 28 | Example: `python3 vxapi.py scan_file C:\file-repo all` 29 | 30 | ![Alt text](/img/scan_example.png?raw=true "Falcon Sandbox API CLI Example Bulk Quick Scan") 31 | 32 | ### V1 33 | 34 | The legacy app utilizing the APIv1 is not supported anymore. For backward compatibility, it is still available in the `v1` branch. 35 | 36 | Usage 37 | --- 38 | 39 | ### Define configuration file 40 | 41 | Copy the `config_tpl.py` and name it `config.py`. 42 | 43 | The configuration file specifies a triplet of api key/secret and server: 44 | 45 | - api_key (should be compatible with API v2 - should contains at least 60 chars) 46 | - server - full url of the WebService instance e.g. `https://www.hybrid-analysis.com` 47 | 48 | Please fill them with the appropriate data. You can generate a public (restricted) API key by following these instructions: 49 | https://www.hybrid-analysis.com/knowledge-base/issuing-self-signed-api-key 50 | 51 | If you have the full version of Falcon Sandbox, create any kind of API key in the admin area: 52 | https://www.hybrid-analysis.com/apikeys 53 | 54 | ### Install python requests module if you're using python < 3.5 [python-requests](http://docs.python-requests.org/en/master/). 55 | 56 | Debian/Ubuntu OS: 57 | 58 | sudo apt-get install python3-requests 59 | 60 | or 61 | 62 | pip3 install requests 63 | 64 | Windows: 65 | 66 | pip install requests 67 | 68 | ### Install python colorama module, [python-colorama module](https://pypi.org/project/colorama/). 69 | 70 | Debian/Ubuntu OS: 71 | 72 | pip3 install colorama 73 | 74 | Windows: 75 | 76 | pip install colorama 77 | 78 | ### Run the connector. Use 'help' or '-h' (on any API endpoint) to get to know about the various endpoint options. Use '-v' for a more verbose output. 79 | 80 | > Depending on your API Key privileges, you will see different options. 81 | > Few actions connected with system state and file submit, are only available while using premium API Key. 82 | > If you are interested in obtaining one, please contact with our [support](https://www.payload-security.com/contact). 83 | 84 | python3 vxapi.py -h 85 | 86 | After choosing the `action_name` 87 | 88 | python3 vxapi.py action_name -h 89 | 90 | ### Use 'verbose' mode to get more wordy output 91 | 92 | python3 vxapi.py action_name -v 93 | 94 | ![Alt text](/img/cli_example.png?raw=true "Falcon Sandbox API CLI Example Output") 95 | 96 | ### Notes 97 | 98 | > Most Linux OSes have two versions of `python` installed. 99 | > To ensure that the program will work correctly, please use `python3`. 100 | > In Windows after having installed `python`, please add the parent folder to `PATH` environment variable. Now use `python` to callout the script. 101 | 102 | ### FAQ 103 | 104 | ##### My API Key authorization level was updated, but VxApi is still showing the old value. 105 | 106 | VxApi is caching key data response. To get the fresh one, please remove `cache` directory content and try to use application once again. 107 | 108 | ##### How can I run tests attached to the project? 109 | 110 | You should to call `pytest` from the project root directory. (installed testing library should is required) 111 | 112 | 113 | ### License 114 | 115 | Licensed GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007 116 | see https://github.com/PayloadSecurity/VxAPI/blob/master/LICENSE.md 117 | 118 | Copyright (C) 2021 Hybrid Analysis GmbH 119 | -------------------------------------------------------------------------------- /_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.0.9' 2 | -------------------------------------------------------------------------------- /api/callers/api_caller.py: -------------------------------------------------------------------------------- 1 | from exceptions import OptionNotDeclaredError 2 | from exceptions import ResponseObjectNotExistError 3 | from exceptions import UrlBuildError 4 | from exceptions import JsonParseError 5 | from exceptions import ConfigError 6 | import json 7 | 8 | 9 | class ApiCaller: 10 | 11 | CONST_REQUEST_METHOD_GET = 'get' 12 | CONST_REQUEST_METHOD_POST = 'post' 13 | 14 | CONST_EXPECTED_DATA_TYPE_JSON = 'json' 15 | CONST_EXPECTED_DATA_TYPE_FILE = 'file' 16 | 17 | CONST_API_AUTH_LEVEL_RESTRICTED = 1 18 | CONST_API_AUTH_LEVEL_DEFAULT = 100 19 | CONST_API_AUTH_LEVEL_ELEVATED = 500 20 | CONST_API_AUTH_LEVEL_SUPER = 1000 21 | 22 | api_key = '' 23 | 24 | server = '' 25 | endpoint_url = '' 26 | endpoint_auth_level = CONST_API_AUTH_LEVEL_RESTRICTED 27 | data = {} 28 | params = {} 29 | files = {} 30 | request_method_name = '' 31 | 32 | api_result_msg = '' 33 | api_unexpected_error_msg = 'Unexpected error has occurred (HTTP code: {}). Please try again later or contact with the support' 34 | api_unexpected_error_404_msg = 'Unexpected error has occurred (HTTP code: {}). This error is mostly occurring when called webservice is outdated and so does not support current action. If you believe it is an error, please contact with the support' 35 | api_success_msg = 'Your request was successfully processed by Falcon Sandbox' 36 | api_expected_error_msg = 'API error has occurred. HTTP code: {}, message: \'{}\'' 37 | 38 | api_response = None # TODO - rebuild the way how we set those values to use abstract methods instead 39 | api_expected_data_type = CONST_EXPECTED_DATA_TYPE_JSON 40 | api_response_json = {} 41 | 42 | def __init__(self, api_key: str, server: str): 43 | self.api_key = api_key 44 | self.server = server 45 | self.check_class_options() 46 | 47 | def reset_state(self): 48 | self.data = {} 49 | self.params = {} 50 | self.files = {} 51 | self.response_msg_success_nature = False 52 | self.api_response = None 53 | self.api_response_json = {} 54 | 55 | def check_class_options(self): 56 | requested_fields = ['request_method_name', 'endpoint_url'] 57 | for requested_field in requested_fields: 58 | if getattr(self, requested_field) == '': 59 | raise OptionNotDeclaredError('Value for \'{}\' should be declared in class \'{}\'.'.format(requested_field, self.__class__.__name__)) 60 | 61 | def call(self, request_handler, headers={'User-agent': 'VxApi Connector'}): 62 | if '$' in self.endpoint_url: 63 | raise UrlBuildError('Can\'t call API endpoint with url \'{}\', when some placeholders are still not filled.'.format(self.endpoint_url)) 64 | 65 | caller_function = getattr(request_handler, self.request_method_name) 66 | headers['api-key'] = self.api_key 67 | 68 | self.api_response = caller_function(self.get_full_endpoint_url(), data=self.data, params=self.params, files=self.files, headers=headers, verify=False, allow_redirects=False) 69 | if self.if_request_redirect(): 70 | raise ConfigError('Got redirect while trying to reach server URL. Please try to update it and pass the same URL base which is visible in the browser URL bar. ' 71 | 'For example: it should be \'https://www.hybrid-analysis.com\', instead of \'http://www.hybrid-analysis.com\' or \'https://hybrid-analysis.com\'') 72 | self.api_result_msg = self.prepare_response_msg() 73 | 74 | def attach_data(self, options): 75 | self.data = options 76 | self.build_url(self.data) 77 | 78 | def attach_params(self, params): 79 | self.params = params 80 | self.build_url(self.params) 81 | 82 | def attach_files(self, files): 83 | self.files = files 84 | 85 | def if_request_success(self): 86 | return str(self.api_response.status_code).startswith('2') # 20x status code 87 | 88 | def if_request_redirect(self): 89 | return str(self.api_response.status_code).startswith('3') # 30x status code 90 | 91 | def prepare_response_msg(self) -> str: 92 | if self.api_response is None: 93 | raise ResponseObjectNotExistError('It\'s not possible to get response message since API was not called.') 94 | 95 | if self.if_request_success() is True: 96 | if self.api_expected_data_type == self.CONST_EXPECTED_DATA_TYPE_JSON: 97 | self.api_response_json = self.get_response_json() 98 | 99 | self.api_result_msg = self.api_success_msg 100 | else: 101 | if self.api_response.headers['Content-Type'] == 'application/json': 102 | self.api_response_json = self.api_response.json() 103 | self.api_result_msg = self.api_expected_error_msg.format(self.api_response.status_code, self.api_response_json['message']) 104 | else: 105 | if self.api_response.status_code == 404: 106 | self.api_result_msg = self.api_unexpected_error_404_msg.format(self.api_response.status_code) 107 | else: 108 | self.api_result_msg = self.api_unexpected_error_msg.format(self.api_response.status_code) 109 | 110 | return self.api_result_msg 111 | 112 | def get_api_response(self): 113 | if self.api_response is None: 114 | raise ResponseObjectNotExistError('It\'s not possible to get api response before doing request.') 115 | 116 | return self.api_response 117 | 118 | def get_response_status_code(self): 119 | if self.api_response is None: 120 | raise ResponseObjectNotExistError('It\'s not possible to get response code since API was not called.') 121 | 122 | return self.api_response.status_code 123 | 124 | def get_prepared_response_msg(self): 125 | if self.api_result_msg == '': 126 | self.api_result_msg = self.prepare_response_msg() 127 | 128 | return self.api_result_msg 129 | 130 | def get_response_json(self): 131 | if self.api_response is None: 132 | raise ResponseObjectNotExistError('It\'s not possible to get response json since API was not called.') 133 | elif bool(self.api_response_json) is False: 134 | try: 135 | if self.api_response.headers['Content-Type'] == 'application/json': 136 | self.api_response_json = self.api_response.json() 137 | elif self.api_response.headers['Content-Type'].startswith('text/html'): 138 | # let's be more tolerant and accept situation when content type is not valid, but response has proper json 139 | self.api_response_json = json.loads(self.api_response.text) 140 | else: 141 | ''' 142 | Some of endpoints can return mixed content type - like file type(success) and json(controlled errors). 143 | Let's return there empty dictionary, as it's already properly handled by other project parts. 144 | ''' 145 | self.api_response_json = {} 146 | except json.decoder.JSONDecodeError: 147 | ''' 148 | When response has status code equal 200 and we're expecting json, there should be json always. 149 | Let's ignore other cases as for errors like 404, 500, we're getting html page instead. 150 | That case should be handled in some other place. 151 | ''' 152 | if self.if_request_success() and self.api_expected_data_type == self.CONST_EXPECTED_DATA_TYPE_JSON: 153 | raise JsonParseError('Failed to parse response: \'{}\''.format(self.api_response.text)) 154 | else: 155 | self.api_response_json = {} 156 | 157 | return self.api_response_json 158 | 159 | def get_headers(self): 160 | if self.api_response is None: 161 | raise ResponseObjectNotExistError('It\'s not possible to get response headers since API was not called.') 162 | 163 | return self.api_response.headers 164 | 165 | def build_url(self, params): 166 | if '$' in self.endpoint_url: 167 | url_data = params 168 | url_data_copy = url_data.copy() 169 | for key, value in url_data.items(): 170 | searched_key = '$' + key 171 | if searched_key in self.endpoint_url: 172 | self.endpoint_url = self.endpoint_url.replace('$' + key, str(value)) 173 | del url_data_copy[key] # Working on copy, since it's not possible to manipulate dict size, during iteration 174 | 175 | if self.request_method_name == self.CONST_REQUEST_METHOD_GET: 176 | self.params = url_data_copy 177 | else: 178 | self.data = url_data_copy 179 | 180 | def get_full_endpoint_url(self): 181 | return '{}/api/v2{}'.format(self.server, self.endpoint_url) 182 | -------------------------------------------------------------------------------- /api/callers/feed/__init__.py: -------------------------------------------------------------------------------- 1 | from api.callers.feed.api_feed import ApiFeed 2 | from api.callers.feed.api_feed_latest import ApiFeedLatest 3 | -------------------------------------------------------------------------------- /api/callers/feed/api_feed.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiFeed(ApiCaller): 5 | endpoint_url = '/feed/$days/days' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_ELEVATED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/feed/api_feed_latest.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiFeedLatest(ApiCaller): 5 | endpoint_url = '/feed/latest' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/key/__init__.py: -------------------------------------------------------------------------------- 1 | from api.callers.key.api_key_create import ApiKeyCreate 2 | from api.callers.key.api_key_current import ApiKeyCurrent 3 | -------------------------------------------------------------------------------- /api/callers/key/api_key_create.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiKeyCreate(ApiCaller): 5 | endpoint_url = '/key/create' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_ELEVATED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/key/api_key_current.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiKeyCurrent(ApiCaller): 5 | endpoint_url = '/key/current' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/overview/__init__.py: -------------------------------------------------------------------------------- 1 | from api.callers.overview.api_overview import ApiOverview 2 | from api.callers.overview.api_overview_refresh import ApiOverviewRefresh 3 | from api.callers.overview.api_overview_summary import ApiOverviewSummary 4 | from api.callers.overview.api_overview_sample import ApiOverviewSample 5 | -------------------------------------------------------------------------------- /api/callers/overview/api_overview.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiOverview(ApiCaller): 5 | endpoint_url = '/overview/$sha256' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/overview/api_overview_refresh.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiOverviewRefresh(ApiCaller): 5 | endpoint_url = '/overview/$sha256/refresh' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/overview/api_overview_sample.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiOverviewSample(ApiCaller): 5 | endpoint_url = '/overview/$sha256/sample' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | api_expected_data_type = ApiCaller.CONST_EXPECTED_DATA_TYPE_FILE 9 | -------------------------------------------------------------------------------- /api/callers/overview/api_overview_summary.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiOverviewSummary(ApiCaller): 5 | endpoint_url = '/overview/$sha256/summary' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/report/__init__.py: -------------------------------------------------------------------------------- 1 | from api.callers.report.api_report_bulk_summary import ApiReportBulkSummary 2 | from api.callers.report.api_report_demo_bulk import ApiReportDemoBulk 3 | from api.callers.report.api_report_dropped_files import ApiReportDroppedFiles 4 | from api.callers.report.api_report_dropped_file_raw import ApiReportDroppedFileRaw 5 | from api.callers.report.api_report_enhanced_summary import ApiReportEnhancedSummary 6 | from api.callers.report.api_report_file import ApiReportFile 7 | from api.callers.report.api_report_screenshots import ApiReportScreenshots 8 | from api.callers.report.api_report_state import ApiReportState 9 | from api.callers.report.api_report_summary import ApiReportSummary 10 | -------------------------------------------------------------------------------- /api/callers/report/api_report_bulk_summary.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportBulkSummary(ApiCaller): 5 | endpoint_url = '/report/summary' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/report/api_report_demo_bulk.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportDemoBulk(ApiCaller): 5 | endpoint_url = '/report/demo-bulk' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_ELEVATED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | api_expected_data_type = ApiCaller.CONST_EXPECTED_DATA_TYPE_FILE 9 | 10 | -------------------------------------------------------------------------------- /api/callers/report/api_report_dropped_file_raw.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportDroppedFileRaw(ApiCaller): 5 | endpoint_url = '/report/$id/dropped-file-raw/$hash' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | api_expected_data_type = ApiCaller.CONST_EXPECTED_DATA_TYPE_FILE 9 | -------------------------------------------------------------------------------- /api/callers/report/api_report_dropped_files.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportDroppedFiles(ApiCaller): 5 | endpoint_url = '/report/$id/dropped-files' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | api_expected_data_type = ApiCaller.CONST_EXPECTED_DATA_TYPE_FILE 9 | -------------------------------------------------------------------------------- /api/callers/report/api_report_enhanced_summary.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportEnhancedSummary(ApiCaller): 5 | endpoint_url = '/report/$id/enhanced-summary' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_ELEVATED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/report/api_report_file.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportFile(ApiCaller): 5 | endpoint_url = '/report/$id/file/$type' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | api_expected_data_type = ApiCaller.CONST_EXPECTED_DATA_TYPE_FILE 9 | -------------------------------------------------------------------------------- /api/callers/report/api_report_screenshots.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportScreenshots(ApiCaller): 5 | endpoint_url = '/report/$id/screenshots' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/report/api_report_state.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportState(ApiCaller): 5 | endpoint_url = '/report/$id/state' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/report/api_report_summary.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiReportSummary(ApiCaller): 5 | endpoint_url = '/report/$id/summary' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/scan/__init__.py: -------------------------------------------------------------------------------- 1 | from api.callers.scan.api_scan_convert_to_full import ApiScanConvertToFull 2 | from api.callers.scan.api_scan_file import ApiScanFile 3 | from api.callers.scan.api_scan_scan import ApiScanScan 4 | from api.callers.scan.api_scan_state import ApiScanState 5 | from api.callers.scan.api_scan_url_for_analysis import ApiScanUrlForAnalysis 6 | from api.callers.scan.api_scan_url_to_file import ApiScanUrlToFile 7 | -------------------------------------------------------------------------------- /api/callers/scan/api_scan_convert_to_full.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiScanConvertToFull(ApiCaller): 5 | endpoint_url = '/quick-scan/$id/convert-to-full' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/scan/api_scan_file.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiScanFile(ApiCaller): 5 | endpoint_url = '/quick-scan/file' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/scan/api_scan_scan.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiScanScan(ApiCaller): 5 | endpoint_url = '/quick-scan/$id' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/scan/api_scan_state.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiScanState(ApiCaller): 5 | endpoint_url = '/quick-scan/state' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/scan/api_scan_url_for_analysis.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiScanUrlForAnalysis(ApiCaller): 5 | endpoint_url = '/quick-scan/url-for-analysis' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/scan/api_scan_url_to_file.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiScanUrlToFile(ApiCaller): 5 | endpoint_url = '/quick-scan/url-to-file' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/search/__init__.py: -------------------------------------------------------------------------------- 1 | from api.callers.search.api_search_hash import ApiSearchHash 2 | from api.callers.search.api_search_hashes import ApiSearchHashes 3 | from api.callers.search.api_search_states import ApiSearchStates 4 | from api.callers.search.api_search_terms import ApiSearchTerms 5 | -------------------------------------------------------------------------------- /api/callers/search/api_search_hash.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSearchHash(ApiCaller): 5 | endpoint_url = '/search/hash' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/search/api_search_hashes.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSearchHashes(ApiCaller): 5 | endpoint_url = '/search/hashes' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/search/api_search_states.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSearchStates(ApiCaller): 5 | endpoint_url = '/search/states' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_ELEVATED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/search/api_search_terms.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSearchTerms(ApiCaller): 5 | endpoint_url = '/search/terms' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/submit/__init__.py: -------------------------------------------------------------------------------- 1 | from api.callers.submit.api_submit_dropped_file import ApiSubmitDroppedFile 2 | from api.callers.submit.api_submit_file import ApiSubmitFile 3 | from api.callers.submit.api_submit_hash_for_url import ApiSubmitHashForUrl 4 | from api.callers.submit.api_submit_reanalyze import ApiSubmitReanalyze 5 | from api.callers.submit.api_submit_url_for_analysis import ApiSubmitUrlForAnalysis 6 | from api.callers.submit.api_submit_url_to_file import ApiSubmitUrlToFile 7 | -------------------------------------------------------------------------------- /api/callers/submit/api_submit_dropped_file.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSubmitDroppedFile(ApiCaller): 5 | endpoint_url = '/submit/dropped-file' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/submit/api_submit_file.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSubmitFile(ApiCaller): 5 | endpoint_url = '/submit/file' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/submit/api_submit_hash_for_url.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSubmitHashForUrl(ApiCaller): 5 | endpoint_url = '/submit/hash-for-url' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/submit/api_submit_reanalyze.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSubmitReanalyze(ApiCaller): 5 | endpoint_url = '/submit/reanalyze' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_ELEVATED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/submit/api_submit_url_for_analysis.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSubmitUrlForAnalysis(ApiCaller): 5 | endpoint_url = '/submit/url-for-analysis' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/submit/api_submit_url_to_file.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSubmitUrlToFile(ApiCaller): 5 | endpoint_url = '/submit/url-to-file' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_POST 8 | -------------------------------------------------------------------------------- /api/callers/system/__init__.py: -------------------------------------------------------------------------------- 1 | from api.callers.system.api_system_backend import ApiSystemBackend 2 | from api.callers.system.api_system_environments import ApiSystemEnvironments 3 | from api.callers.system.api_system_heartbeat import ApiSystemHeartbeat 4 | from api.callers.system.api_system_in_progress import ApiSystemInProgress 5 | from api.callers.system.api_system_php import ApiSystemPhp 6 | from api.callers.system.api_system_queue_size import ApiSystemQueueSize 7 | from api.callers.system.api_system_state import ApiSystemState 8 | from api.callers.system.api_system_stats import ApiSystemStats 9 | from api.callers.system.api_system_version import ApiSystemVersion 10 | -------------------------------------------------------------------------------- /api/callers/system/api_system_backend.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemBackend(ApiCaller): 5 | endpoint_url = '/system/backend' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/system/api_system_environments.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemEnvironments(ApiCaller): 5 | endpoint_url = '/system/environments' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/system/api_system_heartbeat.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemHeartbeat(ApiCaller): 5 | endpoint_url = '/system/heartbeat' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/system/api_system_in_progress.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemInProgress(ApiCaller): 5 | endpoint_url = '/system/in-progress' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/system/api_system_php.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemPhp(ApiCaller): 5 | endpoint_url = '/system/php' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_ELEVATED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/system/api_system_queue_size.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemQueueSize(ApiCaller): 5 | endpoint_url = '/system/queue-size' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/system/api_system_state.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemState(ApiCaller): 5 | endpoint_url = '/system/state' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/system/api_system_stats.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemStats(ApiCaller): 5 | endpoint_url = '/system/stats' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_DEFAULT 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /api/callers/system/api_system_version.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | 3 | 4 | class ApiSystemVersion(ApiCaller): 5 | endpoint_url = '/system/version' 6 | endpoint_auth_level = ApiCaller.CONST_API_AUTH_LEVEL_RESTRICTED 7 | request_method_name = ApiCaller.CONST_REQUEST_METHOD_GET 8 | -------------------------------------------------------------------------------- /cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PayloadSecurity/VxAPI/55d852c7e07fb586acf5440204e3950ac43b04da/cli/__init__.py -------------------------------------------------------------------------------- /cli/arguments_builders/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.arguments_builders.default_cli_arguments import DefaultCliArguments 2 | from cli.arguments_builders.search_cli_arguments import SearchCliArguments 3 | from cli.arguments_builders.demo_bulk_cli_arguments import DemoBulkCliArguments 4 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 5 | -------------------------------------------------------------------------------- /cli/arguments_builders/default_cli_arguments.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from argparse import ArgumentParser 3 | from constants import ACTION_SYSTEM_ENVIRONMENTS 4 | from constants import ACTION_SCAN_STATE 5 | import os 6 | 7 | 8 | class DefaultCliArguments: 9 | 10 | def __init__(self, parser: ArgumentParser): 11 | self.parser = parser 12 | 13 | def add_sha256_arg(self, help='Sample sha256 hash'): 14 | self.parser.add_argument('sha256', type=str, help=help) 15 | 16 | return self 17 | 18 | def add_hash_arg(self, help='Md5, sha1 or sha256 hash'): 19 | self.parser.add_argument('hash', type=str, help=help) 20 | 21 | return self 22 | 23 | def add_scan_type_arg(self): 24 | self.parser.add_argument('scan-type', type=str, help='Type of scan (please use \'{}\' action to fetch all available scanners)'.format(ACTION_SCAN_STATE)) 25 | 26 | return self 27 | 28 | def add_url_arg(self, help): 29 | self.parser.add_argument('url', type=str, help=help) 30 | 31 | return self 32 | 33 | def add_id_arg(self, help='Id in one of format: \'jobId\' or \'sha256:environmentId\''): 34 | self.parser.add_argument('id', type=str, help=help) 35 | 36 | return self 37 | 38 | def add_feed_days_arg(self): 39 | self.parser.add_argument('days', type=int, help='Number of days') 40 | 41 | return self 42 | 43 | def add_key_uid_arg(self): 44 | self.parser.add_argument('uid', type=str, help='Any string to allow find this key later') 45 | 46 | return self 47 | 48 | def add_file_with_hash_list_arg(self, allowed_hashes): 49 | self.parser.add_argument('hashes-file', type=argparse.FileType('r'), help='Path to file containing list of sample hashes separated by new line (allowed: {})'.format(', '.join(allowed_hashes))) 50 | 51 | return self 52 | 53 | def add_file_with_ids_arg(self, allowed_ids): 54 | self.parser.add_argument('mixed-ids-file', type=argparse.FileType('r'), help='Path to file containing list of ids (allowed: {}'.format(', '.join(allowed_ids))) 55 | 56 | return self 57 | 58 | def add_report_file_type_opt_arg(self): 59 | self.parser.add_argument('type', type=str, choices=['bin', 'json', 'pdf', 'crt', 'maec', 'stix', 'misp', 'misp-json', 'openioc', 'html', 'pcap', 'memory', 'xml'], help='Type of requested content') 60 | 61 | return self 62 | 63 | def add_submit_files_arg(self): 64 | def validate_path(path): 65 | files = [path] 66 | if os.path.exists(path) is False: 67 | raise argparse.ArgumentTypeError('No such file or directory: \'{}\''.format(path)) 68 | 69 | if os.path.isdir(path): 70 | if path.startswith('/') is True: # Given path is absolute 71 | abs_path = path 72 | else: 73 | abs_path = '/'.join(os.path.dirname(os.path.realpath(__file__)).split('/')[:-2] + [path]) 74 | 75 | files = list(filter(lambda path: os.path.isfile(path), ['{}/{}'.format(abs_path, x) for x in os.listdir(path)])) 76 | 77 | return files 78 | 79 | self.parser.add_argument('file', type=validate_path, help='File to submit (when directory given, all files from it will be submitted - non recursively)') 80 | 81 | return self 82 | 83 | def add_file_output_path_opt(self): 84 | self.parser.add_argument('--output', '-o', type=str, default='output', help='File output path') 85 | 86 | def add_env_id_arg(self, required: bool = False): 87 | environment_id_help = 'Sample Environment ID (use \'{}\' action to fetch all available)'.format(ACTION_SYSTEM_ENVIRONMENTS) 88 | if required is False: 89 | self.parser.add_argument('--environment-id', '-env', type=int, help=environment_id_help) 90 | else: 91 | self.parser.add_argument('environment-id', type=int, help=environment_id_help) 92 | 93 | return self 94 | 95 | def add_report_file_type_opt(self): 96 | self.parser.add_argument('--type', '-t', type=str, choices=['bin', 'json', 'pdf', 'crt', 'maec', 'misp', 'misp-json', 'openioc', 'html', 'pcap', 'memory', 'xml'], help='File type to return') 97 | 98 | return self 99 | 100 | def add_verbose_arg(self): 101 | self.parser.add_argument('--verbose', '-v', help="Run command in verbose mode", action='store_true') 102 | 103 | return self 104 | 105 | def add_help_opt(self): 106 | self.parser.add_argument('--help', '-h', action='help', default=argparse.SUPPRESS, help='Show this help message and exit.') 107 | 108 | return self 109 | 110 | def add_quiet_opt(self): 111 | self.parser.add_argument('--quiet', '-q', action='store_true', default=False, help='Suppress all prompts and warnings') 112 | 113 | return self 114 | 115 | 116 | -------------------------------------------------------------------------------- /cli/arguments_builders/demo_bulk_cli_arguments.py: -------------------------------------------------------------------------------- 1 | from cli.arguments_builders.default_cli_arguments import DefaultCliArguments 2 | 3 | 4 | class DemoBulkCliArguments(DefaultCliArguments): 5 | 6 | def add_report_demo_bulk_modify_hash_opt(self): 7 | self.parser.add_argument('--modify-hash', '-mh', action='store_true', default=False, help='When set, will add null byte at the end of sample file') 8 | 9 | return self 10 | 11 | def add_report_demo_bulk_av_min_opt(self): 12 | self.parser.add_argument('--av-min', '-an', type=int, default=5, help='The minimum required AV detect') 13 | 14 | return self 15 | 16 | def add_report_demo_bulk_av_max_opt(self): 17 | self.parser.add_argument('--av-max', '-ax', type=int, default=15, help='The maximum required AV detect') 18 | 19 | return self 20 | 21 | def add_report_demo_bulk_look_back_size_opt(self): 22 | self.parser.add_argument('--look-back-size', '-lbs', type=int, default=400, help='Number of samples which will be fetched and filtered. Once you will get error message about problem with finding all samples, please increase that value') 23 | 24 | return self 25 | -------------------------------------------------------------------------------- /cli/arguments_builders/search_cli_arguments.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from cli.arguments_builders.default_cli_arguments import DefaultCliArguments 3 | 4 | 5 | class SearchCliArguments(DefaultCliArguments): 6 | 7 | def add_search_term_filename_opt(self): 8 | self.parser.add_argument('--filename', type=str, help='Filename e.g. invoice.exe') 9 | 10 | return self 11 | 12 | def add_search_term_filetype_opt(self): 13 | self.parser.add_argument('--filetype', type=str, help='Filetype e.g. docx') 14 | 15 | return self 16 | 17 | def add_search_term_filetype_desc_opt(self): 18 | self.parser.add_argument('--filetype-desc', type=str, help='Filetype description e.g. PE32 executable') 19 | 20 | return self 21 | 22 | def add_search_term_env_id_opt(self): 23 | self.parser.add_argument('--env-id', type=int, help='Environment Id') 24 | 25 | return self 26 | 27 | def add_search_term_country_opt(self): 28 | self.parser.add_argument('--country', type=str, help='Country (3 digit ISO) e.g. swe') 29 | 30 | return self 31 | 32 | def add_search_term_verdict_opt(self): 33 | self.parser.add_argument('--verdict', type=int, help='Verdict', choices={1: 'whitelisted', 2: 'no verdict', 3: 'no specific threat', 4: 'suspicious', 5: 'malicious'}) 34 | 35 | return self 36 | 37 | def add_search_term_av_detect_opt(self): 38 | def type_av_detect(value): 39 | if value.find('-'): 40 | values = value.split('-') 41 | else: 42 | values = [value] 43 | 44 | for iter_value in values: 45 | forced_int_value = int(iter_value) 46 | if forced_int_value < 0 or forced_int_value > 100: 47 | raise argparse.ArgumentTypeError('{} is not a value between {} and {}'.format(iter_value, 0, 100)) 48 | 49 | return value 50 | 51 | self.parser.add_argument('--av-detect', type=type_av_detect, help='AV Multiscan range e.g. 50-70 (min 0, max 100)') 52 | 53 | return self 54 | 55 | def add_search_term_vx_family_opt(self): 56 | self.parser.add_argument('--vx-family', type=str, help='AV Family Substring e.g. nemucod') 57 | 58 | return self 59 | 60 | def add_search_term_tag_opt(self): 61 | self.parser.add_argument('--tag', type=str, help='Hashtag e.g. ransomware') 62 | 63 | return self 64 | 65 | def add_search_term_date_from_opt(self): 66 | self.parser.add_argument('--date-to', type=str, help='Date from in format: ‘Y-m-d H:i’ e.g. 2018-09-28 15:30') # TODO - add some date validator here 67 | 68 | return self 69 | 70 | def add_search_term_date_to_opt(self): 71 | self.parser.add_argument('--date-from', type=str, help='Date to in format: ‘Y-m-d H:i’ e.g. 2018-09-28 15:30') 72 | 73 | return self 74 | 75 | def add_search_term_port_opt(self): 76 | self.parser.add_argument('--port', type=str, help='Port e.g. 8080') 77 | 78 | return self 79 | 80 | def add_search_term_host_opt(self): 81 | self.parser.add_argument('--host', type=str, help='Host e.g. 192.168.0.1') 82 | 83 | return self 84 | 85 | def add_search_term_domain_opt(self): 86 | self.parser.add_argument('--domain', type=str, help='Domain e.g. checkip.dyndns.org') 87 | 88 | return self 89 | 90 | def add_search_term_url_opt(self): 91 | self.parser.add_argument('--url', type=str, help='HTTP Request Substring e.g. example') 92 | 93 | return self 94 | 95 | def add_search_term_similar_to_opt(self): 96 | self.parser.add_argument('--similar-to', type=str, help='Similar Samples e.g. ') 97 | 98 | return self 99 | 100 | def add_search_term_context_opt(self): 101 | self.parser.add_argument('--context', type=str, help='Sample Context e.g. ') 102 | 103 | return self 104 | 105 | def add_search_term_imp_hash_opt(self): 106 | self.parser.add_argument('--imphash', type=str) 107 | 108 | return self 109 | 110 | def add_search_term_ssdeep_opt(self): 111 | self.parser.add_argument('--ssdeep', type=str) 112 | 113 | return self 114 | 115 | def add_search_term_authentihash_opt(self): 116 | self.parser.add_argument('--authentihash', type=str) 117 | 118 | return self 119 | -------------------------------------------------------------------------------- /cli/arguments_builders/submission_cli_arguments.py: -------------------------------------------------------------------------------- 1 | from cli.arguments_builders.default_cli_arguments import DefaultCliArguments 2 | from cli.types.values_in_between_action import ValuesInBetweenAction 3 | 4 | 5 | class SubmissionCliArguments(DefaultCliArguments): 6 | 7 | def add_submission_submit_name_opt(self): 8 | self.parser.add_argument('--submit-name', '-sn', type=str, help='Optional \'submission name\' field that will be used for file type detection and analysis') 9 | 10 | return self 11 | 12 | def add_submission_comment_opt(self): 13 | self.parser.add_argument('--comment', '-co', type=str, help='Add comment (e.g. #hashtag) to sample') 14 | 15 | return self 16 | 17 | def add_submission_no_share_third_party_opt(self): 18 | self.parser.add_argument('--no-share-third-party', '-nstp', help='When set to \'1\', the sample is never shared with any third party', type=int, choices=[1, 0], default=1) 19 | 20 | return self 21 | 22 | def add_submission_allow_community_access_opt(self): 23 | self.parser.add_argument('--allow-community-access', '-aca', choices=[1, 0], default=1, type=int, help='When set \'1\', the sample will be available for vetted users of the HA community or custom application server') 24 | 25 | return self 26 | 27 | def add_submission_no_hash_lookup(self): 28 | self.parser.add_argument('--no-hash-lookup', '-nhl', choices=[1, 0], type=int) 29 | 30 | return self 31 | 32 | def add_submission_action_script_opt(self): 33 | self.parser.add_argument('--action-script', '-ac', choices={1: 'default', 2: 'default_maxantievasion', 3: 'default_randomfiles', 4: 'default_randomtheme', 5: 'default_openie'}, type=int, help='Optional custom runtime action script') 34 | 35 | return self 36 | 37 | def add_submission_hybrid_analysis_opt(self): 38 | self.parser.add_argument('--hybrid-analysis', '-ha', choices=[1, 0], type=int, help='When set to \'0\', no memory dumps or memory dump analysis will take place') 39 | 40 | return self 41 | 42 | def add_submission_experimental_anti_evasion_opt(self): 43 | self.parser.add_argument('--experimental-anti-evasion', '-eae', choices=[1, 0], type=int, help='When set to \'1\', will set all experimental anti-evasion options of the Kernelmode Monitor') 44 | 45 | return self 46 | 47 | def add_submission_script_logging_opt(self): 48 | self.parser.add_argument('--script-logging', '-sl', choices=[1, 0], type=int, help='When set to \'1\', will set the in-depth script logging engine of the Kernelmode Monitor') 49 | 50 | return self 51 | 52 | def add_submission_input_sample_tampering_opt(self): 53 | self.parser.add_argument('--input-sample-tampering', '--ist', choices=[1, 0], type=int, help='When set to \'1\', will allow experimental anti-evasion options of the Kernelmode Monitor that tamper with the input sample') 54 | 55 | return self 56 | 57 | def add_submission_tor_enabled_analysis_opt(self): 58 | self.parser.add_argument('--tor-enabled-analysis', '-tea', choices=[1, 0], type=int, help='When set to \'1\', will route the network traffic for the analysis via TOR (if properly configured on the server)') 59 | 60 | return self 61 | 62 | def add_submission_offline_analysis_opt(self): 63 | self.parser.add_argument('--offline-analysis', '-oa', choices=[1, 0], type=int, help='When set to \'1\', will disable outbound network traffic for the guest VM (takes precedence over ‘tor-enabled-analysis’ if both are provided)') 64 | 65 | return self 66 | 67 | def add_submission_email_opt(self): 68 | self.parser.add_argument('--email', '-e', type=str, help='Optional E-Mail address that may be associated with the submission for notification') 69 | 70 | return self 71 | 72 | def add_submission_custom_date_time_opt(self): 73 | self.parser.add_argument('--custom-date-time', '-cdt', type=str, help='Optional custom date/time that can be set for the analysis system. Expected format: yyyy-MM-dd HH:mm') 74 | 75 | return self 76 | 77 | def add_submission_custom_cmd_line_opt(self): 78 | self.parser.add_argument('--custom-cmd-line', '-ccl', type=str, help='Optional commandline that should be passed to the analysis file') 79 | 80 | return self 81 | 82 | def add_submission_custom_run_time_opt(self): 83 | self.parser.add_argument('--custom-run-time', '-crt', type=int, help='Optional runtime duration (in seconds)',) 84 | 85 | return self 86 | 87 | def add_submission_client_opt(self): 88 | self.parser.add_argument('--client', '-cl', type=str, help='Optional ‘client’ field (see ‘vxClients’)') 89 | 90 | return self 91 | 92 | def add_submission_priority_opt(self): 93 | self.parser.add_argument('--priority', '-pr', type=ValuesInBetweenAction(), help='Optional priority value between 0 (default) and 100 (highest)') 94 | 95 | return self 96 | 97 | def add_submission_document_password_opt(self): 98 | self.parser.add_argument('--document-password', '-dp', type=str, help='Optional document password that will be used to fill-in Adobe/Office password prompts') 99 | 100 | return self 101 | 102 | def add_submission_environment_variable_opt(self): 103 | self.parser.add_argument('--environment-variable', '-ev', type=str, help='Optional system environment value. The value is provided in the format: name=value') 104 | 105 | return self 106 | -------------------------------------------------------------------------------- /cli/cli_file_writer.py: -------------------------------------------------------------------------------- 1 | import os 2 | from io import BytesIO 3 | import gzip 4 | import errno 5 | from exceptions import FailedFileSavingError 6 | 7 | 8 | class CliFileWriter: 9 | 10 | @staticmethod 11 | def write(dir_path, filename, content): 12 | CliFileWriter.create_dir_if_not_exists(dir_path) 13 | retrieved_filename_without_gz_ext, retrieved_file_extension = os.path.splitext(filename) 14 | 15 | mode = 'wb' if type(content).__name__ == 'bytes' else 'w' 16 | f_out_name = dir_path 17 | 18 | if retrieved_file_extension == '.gz': 19 | f_out_name += '/' + retrieved_filename_without_gz_ext # As we want to unpack it, put filename without '.gz. extension 20 | f_out = open(f_out_name, mode) 21 | try: 22 | gzip_file_handle = gzip.GzipFile(fileobj=BytesIO(content)) 23 | f_out.write(gzip_file_handle.read()) 24 | except Exception as e: 25 | f_out_name += retrieved_file_extension 26 | f_out = open(f_out_name, mode) 27 | f_out.write(content) 28 | f_out.close() 29 | f_out.close() 30 | else: 31 | f_out_name += '/' + filename 32 | f_out = open(f_out_name, mode) 33 | f_out.write(content) 34 | f_out.close() 35 | 36 | return f_out_name 37 | 38 | @staticmethod 39 | def create_dir_if_not_exists(dir_path): 40 | if os.path.exists(dir_path): 41 | if not os.path.isdir(dir_path): 42 | raise FailedFileSavingError('Given output path \'{}\' points a file instead of directory.'.format(dir_path)) 43 | else: 44 | try: 45 | os.makedirs(dir_path) 46 | except OSError as exc: 47 | if exc.errno == errno.EACCES: 48 | raise FailedFileSavingError('Failed to create directory in \'{}\'. Possibly it\'s connected with file rights.'.format(dir_path)) 49 | else: 50 | raise 51 | -------------------------------------------------------------------------------- /cli/cli_helper.py: -------------------------------------------------------------------------------- 1 | from colors import Color 2 | from constants import MINIMAL_SUPPORTED_INSTANCE_VERSION 3 | import pkg_resources 4 | 5 | class CliHelper: 6 | 7 | @staticmethod 8 | def check_if_version_is_supported(args, current_version, server): 9 | parsed_current_version = pkg_resources.parse_version(current_version.split('-')[0]) 10 | minimal_supported_version = pkg_resources.parse_version(MINIMAL_SUPPORTED_INSTANCE_VERSION) 11 | 12 | if args['quiet'] is False and 'hybrid-analysis.com' not in server and parsed_current_version < minimal_supported_version: 13 | print(Color.warning('This version of VxAPI works best on VxWebService version {} (or above). Consider upgrading to ensure the flawless performance.'.format(MINIMAL_SUPPORTED_INSTANCE_VERSION))) 14 | 15 | -------------------------------------------------------------------------------- /cli/cli_msg_printer.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import traceback 3 | from colors import Color 4 | from cli.formatter.cli_limits_formatter import CliLimitsFormatter 5 | import sys 6 | 7 | 8 | class CliMsgPrinter: 9 | 10 | date_form = '{:%Y-%m-%d %H:%M:%S}' 11 | 12 | @staticmethod 13 | def print_full_call_info(cli_object): 14 | print(Color.control('Request was sent at {}'.format(CliMsgPrinter.date_form.format(datetime.datetime.now())))) 15 | print('Endpoint URL: {}'.format(cli_object.api_object.get_full_endpoint_url())) 16 | print('HTTP Method: {}'.format(cli_object.api_object.request_method_name.upper())) 17 | print('Sent GET params: {}'.format(cli_object.api_object.params)) 18 | print('Sent POST params: {}'.format(cli_object.api_object.data)) 19 | print('Sent files: {}'.format(cli_object.api_object.files)) 20 | 21 | @staticmethod 22 | def print_shortest_call_info(cli_object, iteration): 23 | print(Color.control('Request was sent at {} - {}'.format(CliMsgPrinter.date_form.format(datetime.datetime.now()), iteration))) 24 | print('Sent files: {}'.format(cli_object.api_object.files)) 25 | 26 | @staticmethod 27 | def print_shorten_call_info(cli_object): 28 | print(Color.control('Endpoint data which will be reached:')) 29 | print('Endpoint URL: {}'.format(cli_object.api_object.get_full_endpoint_url())) 30 | print('HTTP Method: {}'.format(cli_object.api_object.request_method_name.upper())) 31 | print('Sent GET params: {}'.format(cli_object.api_object.params)) 32 | print('Sent POST params: {}'.format(cli_object.api_object.data)) 33 | 34 | @staticmethod 35 | def print_error_info(e): 36 | print(Color.control('During the code execution, error has occurred. Please try again or contact the support.'), file=sys.stderr) 37 | print(Color.error('Message: \'{}\'.').format(str(e)) + '\n', file=sys.stderr) 38 | print(traceback.format_exc(), file=sys.stderr) 39 | 40 | @staticmethod 41 | def print_limits_info(limits_data, limit_type): 42 | info_container = { 43 | 'query': { 44 | 'title': 'API query limits for used API Key', 45 | 'available': 'Webservice API usage limits: {}', 46 | 'used': 'Current API usage: {}', 47 | }, 48 | 'submission': { 49 | 'title': 'Submission limits for used API Key', 50 | 'available': 'Submission limits: {}', 51 | 'used': 'Used submission limits: {}', 52 | }, 53 | 'quick_scan': { 54 | 'title': 'Quick scan submission limits for used API Key', 55 | 'available': 'Submission limits: {}', 56 | 'used': 'Used submission limits: {}', 57 | }, 58 | } 59 | formatted_limits = CliLimitsFormatter.format(limits_data, limit_type) 60 | 61 | texts = info_container[limit_type] 62 | 63 | if formatted_limits: 64 | print(Color.control(texts['title'])) 65 | print(texts['available'].format(formatted_limits['available'])) 66 | print(texts['used'].format(formatted_limits['used'])) 67 | print('Is limit reached: {}'.format(Color.success('No') if formatted_limits['limit_reached'] is False else Color.error('Yes'))) 68 | 69 | @staticmethod 70 | def print_api_key_info(current_key_json): 71 | print(Color.control('Used API Key')) 72 | print('API Key: {}'.format(current_key_json['api_key'])) 73 | print('Auth Level: {}'.format(current_key_json['auth_level_name'])) 74 | if 'user' in current_key_json and current_key_json['user'] is not None: 75 | print('User: {} ({})'.format(current_key_json['user']['name'], current_key_json['user']['email'])) 76 | 77 | @staticmethod 78 | def print_response_summary(iter_cli_object, iteration=None): 79 | print(Color.control('Received response at {}{}'.format(CliMsgPrinter.date_form.format(datetime.datetime.now()), '- {}'.format(iteration) if iteration is not None else ''))) 80 | print('Response status code: {}'.format(iter_cli_object.get_colored_response_status_code())) 81 | print('Message: {}'.format(iter_cli_object.get_colored_prepared_response_msg())) 82 | 83 | @staticmethod 84 | def print_showing_response(arg_iter, iteration=None): 85 | show_response_msg = 'Showing response' 86 | if iteration is not None: 87 | show_response_msg = '{} for file \'{}\' - {}'.format(show_response_msg, arg_iter['file'] if isinstance(arg_iter['file'], str) else arg_iter['file'].name, iteration) 88 | print(Color.control(show_response_msg)) 89 | -------------------------------------------------------------------------------- /cli/cli_prompts.py: -------------------------------------------------------------------------------- 1 | class CliPrompts: 2 | @staticmethod 3 | def prompt_for_sharing_confirmation(args, instance_url): 4 | if 'nosharevt' in args: 5 | if args['nosharevt'] == 'no' and args['quiet'] is False: 6 | warning_msg = 'You are about to submit your file to all users of {} and the public.'.format(instance_url) 7 | if 'hybrid-analysis.com' in instance_url: 8 | warning_msg += ' Please make sure you consent to the Terms and Conditions of Use and Data Privacy Policy available at: {} and {}.'.format('https://www.hybrid-analysis.com/terms', 'https://www.hybrid-analysis.com/data-protection-policy') 9 | warning_msg += ' [y/n]' 10 | submit_warning = input(warning_msg) 11 | if not submit_warning or submit_warning[0].lower() != 'y': 12 | print('You did not indicate approval. Exiting ...') 13 | exit(1) 14 | 15 | @staticmethod 16 | def prompt_for_dir_content_submission(if_multiple_calls, args): 17 | if if_multiple_calls is True: 18 | number_of_files_to_submit = len(args['file']) 19 | if args['quiet'] is False and number_of_files_to_submit > 1: 20 | warning_msg = 'Are you sure that you want to submit all files in the specified directory? It contains {} files. [y/n]'.format(number_of_files_to_submit) 21 | submit_warning = input(warning_msg) 22 | if not submit_warning or submit_warning[0].lower() != 'y': 23 | print('You did not indicate approval. Exiting ...') 24 | exit(1) 25 | -------------------------------------------------------------------------------- /cli/formatter/cli_json_formatter.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class CliJsonFormatter: 5 | 6 | @staticmethod 7 | def format_to_pretty_string(given_json): 8 | return json.dumps(given_json, indent=4, sort_keys=True, ensure_ascii=False) 9 | -------------------------------------------------------------------------------- /cli/formatter/cli_limits_formatter.py: -------------------------------------------------------------------------------- 1 | class CliLimitsFormatter: 2 | 3 | @staticmethod 4 | def format(limits, limit_type): 5 | result = {} 6 | 7 | if not limits: 8 | return result 9 | 10 | if limit_type == 'query': 11 | result = { 12 | 'available': limits['limits'], 13 | 'used': limits['used'], 14 | 'limit_reached': limits['limit_reached'] 15 | } 16 | else: 17 | result = { 18 | 'available': limits['total']['quota'], 19 | 'used': limits['total']['used'], 20 | 'limit_reached': limits['total']['quota_reached'] 21 | } 22 | 23 | return result 24 | -------------------------------------------------------------------------------- /cli/types/values_in_between_action.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | 4 | class ValuesInBetweenAction(object): 5 | a = None 6 | b = None 7 | 8 | def __init__(self, a=0, b=100): 9 | self.a = a 10 | self.b = b 11 | 12 | def __call__(self, value): 13 | forced_int_value = int(value) 14 | if forced_int_value < self.a or forced_int_value > self.b: 15 | raise argparse.ArgumentTypeError('{} is not a value between {} and {}'.format(value, self.a, self.b)) 16 | 17 | return forced_int_value 18 | -------------------------------------------------------------------------------- /cli/wrappers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PayloadSecurity/VxAPI/55d852c7e07fb586acf5440204e3950ac43b04da/cli/wrappers/__init__.py -------------------------------------------------------------------------------- /cli/wrappers/cli_caller.py: -------------------------------------------------------------------------------- 1 | from api.callers.api_caller import ApiCaller 2 | from exceptions import ResponseTextContentTypeError 3 | from colors import Color 4 | import os 5 | from cli.arguments_builders.default_cli_arguments import DefaultCliArguments 6 | import datetime 7 | from cli.cli_file_writer import CliFileWriter 8 | from cli.formatter.cli_json_formatter import CliJsonFormatter 9 | from constants import CALLED_SCRIPT 10 | 11 | 12 | class CliCaller: 13 | 14 | api_object = None 15 | action_name = None 16 | help_description = '' 17 | given_args = {} 18 | result_msg_for_files = 'Response contains files. They were saved in the output folder ({}).' 19 | result_msg_for_json = '{}' 20 | cli_output_folder = '' 21 | args_to_prevent_from_being_send = ['chosen_action', 'verbose', 'quiet'] 22 | 23 | def __init__(self, api_object: ApiCaller, action_name: str): 24 | self.api_object = api_object 25 | self.action_name = action_name 26 | self.help_description = self.help_description.format(self.api_object.endpoint_url) 27 | 28 | def init_verbose_mode(self): 29 | self.result_msg_for_json = 'JSON:\n\n{}' 30 | 31 | def build_argument_builder(self, child_parser): 32 | return DefaultCliArguments(child_parser) 33 | 34 | def add_parser_args(self, child_parser): 35 | parser_argument_builder = self.build_argument_builder(child_parser) 36 | parser_argument_builder.add_verbose_arg() 37 | parser_argument_builder.add_help_opt() 38 | parser_argument_builder.add_quiet_opt() 39 | 40 | return parser_argument_builder 41 | 42 | def attach_args(self, args): 43 | self.given_args = args.copy() 44 | args_to_send = args.copy() 45 | for arg_to_remove in self.args_to_prevent_from_being_send: 46 | if arg_to_remove in args_to_send: 47 | del args_to_send[arg_to_remove] 48 | 49 | if 'output' in args: 50 | self.cli_output_folder = args['output'] 51 | del args_to_send['output'] 52 | 53 | args_to_send = {k: v for k, v in args_to_send.items() if v not in [None, '']} # Removing some 'empty' elements from dictionary 54 | 55 | if 'file' in args: 56 | del args_to_send['file'] # attaching file is handled by separated method 57 | 58 | if self.api_object.request_method_name == ApiCaller.CONST_REQUEST_METHOD_GET: 59 | self.api_object.attach_params(args_to_send) 60 | else: # POST 61 | self.api_object.attach_data(args_to_send) 62 | 63 | def attach_file(self, file): 64 | if isinstance(file, str): 65 | file = open(file, 'rb') 66 | 67 | self.api_object.attach_files({'file': file}) # it's already stored as file handler 68 | 69 | def get_colored_response_status_code(self): 70 | response_code = self.api_object.get_response_status_code() 71 | 72 | return Color.success(response_code) if self.api_object.if_request_success() is True else Color.error(response_code) 73 | 74 | def get_colored_prepared_response_msg(self): 75 | response_msg = self.api_object.get_prepared_response_msg() 76 | 77 | return Color.success(response_msg) if self.api_object.if_request_success() is True else Color.error(response_msg) 78 | 79 | def get_result_msg(self): 80 | if self.api_object.api_response.headers['Content-Type'] == 'text/html': 81 | raise ResponseTextContentTypeError('Can\'t print result, since it\'s \'text/html\' instead of expected content type with \'{}\' on board.'.format(self.api_object.api_expected_data_type)) 82 | 83 | if self.api_object.api_expected_data_type == ApiCaller.CONST_EXPECTED_DATA_TYPE_JSON: 84 | return self.result_msg_for_json.format(CliJsonFormatter.format_to_pretty_string(self.api_object.get_response_json())) 85 | elif self.api_object.api_expected_data_type == ApiCaller.CONST_EXPECTED_DATA_TYPE_FILE: 86 | if self.api_object.if_request_success() is True: 87 | return self.get_result_msg_for_files() 88 | else: 89 | error_msg = 'Error has occurred and your files were not saved.' 90 | if self.given_args['verbose'] is False: 91 | error_msg += ' To get more information, please run command in verbose mode. (add \'-v\')' 92 | 93 | return error_msg 94 | 95 | def get_processed_output_path(self): 96 | output_path = self.cli_output_folder 97 | if output_path.startswith('/') is True: # Given path is absolute 98 | final_output_path = output_path 99 | else: 100 | path_parts = os.path.dirname(os.path.realpath(__file__)).split('/')[:-2] 101 | called_script_dir = os.path.dirname(CALLED_SCRIPT) 102 | # It's about a case when user is calling script from not root directory.€ 103 | if called_script_dir != 'vxapi.py': 104 | new_path_parts = [] 105 | bad_parts = called_script_dir.split('/') 106 | for part in reversed(path_parts): 107 | if part in bad_parts: 108 | bad_parts.remove(part) 109 | continue 110 | new_path_parts.append(part) 111 | 112 | new_path_parts.reverse() 113 | path_parts = new_path_parts 114 | 115 | prepared_file_path = path_parts + [self.cli_output_folder] 116 | final_output_path = '/'.join(prepared_file_path) 117 | 118 | if not final_output_path.startswith('/'): 119 | final_output_path = '/' + final_output_path 120 | 121 | return final_output_path 122 | 123 | def get_result_msg_for_files(self): 124 | return self.result_msg_for_files.format(self.get_processed_output_path()) 125 | 126 | def do_post_processing(self): 127 | if self.api_object.api_expected_data_type == ApiCaller.CONST_EXPECTED_DATA_TYPE_FILE and self.api_object.if_request_success() is True: 128 | self.save_files() 129 | 130 | def get_date_string(self): 131 | now = datetime.datetime.now() 132 | return '{}_{}_{}_{}_{}_{}'.format(now.year, now.month, now.day, now.hour, now.minute, now.second) 133 | 134 | def convert_file_hashes_to_array(self, args, file_arg='hash_list', key_of_array_arg='hashes'): 135 | with args[file_arg] as file: 136 | hashes = file.read().splitlines() 137 | 138 | if not hashes: 139 | raise Exception('Given file does not contain any data.') 140 | 141 | for key, value in enumerate(hashes): 142 | args['{}[{}]'.format(key_of_array_arg, key)] = value 143 | 144 | del args[file_arg] 145 | 146 | return args 147 | 148 | def save_files(self): 149 | api_response = self.api_object.api_response 150 | identifier = None 151 | if 'id' in self.given_args: 152 | identifier = self.given_args['id'] 153 | elif 'sha256' in self.given_args: 154 | identifier = self.given_args['sha256'] 155 | 156 | filename = '{}-{}-{}'.format(self.action_name, identifier, api_response.headers['Vx-Filename']) if identifier is not None else '{}-{}'.format(self.action_name, api_response.headers['Vx-Filename']) 157 | 158 | return CliFileWriter.write(self.get_processed_output_path(), filename, api_response.content) 159 | -------------------------------------------------------------------------------- /cli/wrappers/feed/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.feed.cli_feed import CliFeed 2 | from cli.wrappers.feed.cli_feed_latest import CliFeedLatest 3 | -------------------------------------------------------------------------------- /cli/wrappers/feed/cli_feed.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliFeed(CliCaller): 5 | 6 | help_description = 'Access a JSON feed (summary information) of reports generated over the last X days by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliFeed, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_feed_days_arg() 11 | -------------------------------------------------------------------------------- /cli/wrappers/feed/cli_feed_latest.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliFeedLatest(CliCaller): 5 | 6 | help_description = 'Access a JSON feed (summary information) of last 250 reports from 24h by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/key/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.key.cli_key_create import CliKeyCreate 2 | from cli.wrappers.key.cli_key_current import CliKeyCurrent 3 | -------------------------------------------------------------------------------- /cli/wrappers/key/cli_key_create.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliKeyCreate(CliCaller): 5 | 6 | help_description = 'Create new API key with restricted auth level by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliKeyCreate, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_key_uid_arg() 11 | -------------------------------------------------------------------------------- /cli/wrappers/key/cli_key_current.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliKeyCurrent(CliCaller): 5 | 6 | help_description = 'Return information about the used API key and it limits \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/overview/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.overview.cli_overview import CliOverview 2 | from cli.wrappers.overview.cli_overview_refresh import CliOverviewRefresh 3 | from cli.wrappers.overview.cli_overview_summary import CliOverviewSummary 4 | from cli.wrappers.overview.cli_overview_sample import CliOverviewSample 5 | -------------------------------------------------------------------------------- /cli/wrappers/overview/cli_overview.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliOverview(CliCaller): 5 | 6 | help_description = 'Return overview for hash by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliOverview, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_sha256_arg() 11 | 12 | 13 | -------------------------------------------------------------------------------- /cli/wrappers/overview/cli_overview_refresh.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliOverviewRefresh(CliCaller): 5 | 6 | help_description = 'Refresh overview and download fresh data from external services by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliOverviewRefresh, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_sha256_arg() 11 | -------------------------------------------------------------------------------- /cli/wrappers/overview/cli_overview_sample.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliOverviewSample(CliCaller): 5 | 6 | help_description = 'Downloading sample file by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliOverviewSample, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_sha256_arg() 11 | parser_argument_builder.add_file_output_path_opt() 12 | 13 | 14 | -------------------------------------------------------------------------------- /cli/wrappers/overview/cli_overview_summary.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliOverviewSummary(CliCaller): 5 | 6 | help_description = 'Return overview for hash by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliOverviewSummary, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_sha256_arg() 11 | 12 | -------------------------------------------------------------------------------- /cli/wrappers/report/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.report.cli_report_bulk_summary import CliReportBulkSummary 2 | from cli.wrappers.report.cli_report_demo_bulk import CliReportDemoBulk 3 | from cli.wrappers.report.cli_report_dropped_files import CliReportDroppedFiles 4 | from cli.wrappers.report.cli_report_dropped_file_raw import CliReportDroppedFileRaw 5 | from cli.wrappers.report.cli_report_enhanced_summary import CliReportEnhancedSummary 6 | from cli.wrappers.report.cli_report_file import CliReportFile 7 | from cli.wrappers.report.cli_report_screenshots import CliReportScreenshots 8 | from cli.wrappers.report.cli_report_state import CliReportState 9 | from cli.wrappers.report.cli_report_summary import CliReportSummary 10 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_bulk_summary.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliReportBulkSummary(CliCaller): 5 | 6 | help_description = 'Return summary of multiple submissions (bulk query) by \'{}\'' 7 | 8 | def attach_args(self, args): 9 | super(CliReportBulkSummary, self).attach_args(self.convert_file_hashes_to_array(args, 'mixed_ids_file')) 10 | 11 | def add_parser_args(self, child_parser): 12 | parser_argument_builder = super(CliReportBulkSummary, self).add_parser_args(child_parser) 13 | parser_argument_builder.add_file_with_ids_arg(['jobId', 'md5:environmentId', 'sha1:environmentId', 'sha256:environmentId']) 14 | 15 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_demo_bulk.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.arguments_builders.demo_bulk_cli_arguments import DemoBulkCliArguments 3 | 4 | 5 | class CliReportDemoBulk(CliCaller): 6 | 7 | help_description = 'Return an archive with a collection of samples by \'{}\'' 8 | 9 | def build_argument_builder(self, child_parser): 10 | return DemoBulkCliArguments(child_parser) 11 | 12 | def add_parser_args(self, child_parser): 13 | parser_argument_builder = super(CliReportDemoBulk, self).add_parser_args(child_parser) 14 | parser_argument_builder.add_report_demo_bulk_modify_hash_opt() 15 | parser_argument_builder.add_report_demo_bulk_av_min_opt() 16 | parser_argument_builder.add_report_demo_bulk_av_max_opt() 17 | parser_argument_builder.add_report_demo_bulk_look_back_size_opt() 18 | parser_argument_builder.add_file_output_path_opt() 19 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_dropped_file_raw.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliReportDroppedFileRaw(CliCaller): 5 | 6 | help_description = 'Retrieve single extracted/dropped binaries files for a report by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliReportDroppedFileRaw, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_id_arg() 11 | parser_argument_builder.add_hash_arg('SHA256 of dropped file') 12 | parser_argument_builder.add_file_output_path_opt() 13 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_dropped_files.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliReportDroppedFiles(CliCaller): 5 | 6 | help_description = 'Retrieve all extracted/dropped binaries files for a report as zip by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliReportDroppedFiles, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_id_arg() 11 | parser_argument_builder.add_file_output_path_opt() 12 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_enhanced_summary.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliReportEnhancedSummary(CliCaller): 5 | 6 | help_description = 'Return enhanced summary of a submission by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliReportEnhancedSummary, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_id_arg() 11 | 12 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_file.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliReportFile(CliCaller): 5 | 6 | help_description = 'Download report data (e.g. JSON, XML, PCAP) by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliReportFile, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_id_arg() 11 | parser_argument_builder.add_report_file_type_opt_arg() 12 | parser_argument_builder.add_file_output_path_opt() 13 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_screenshots.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | import base64 3 | from cli.cli_file_writer import CliFileWriter 4 | 5 | 6 | class CliReportScreenshots(CliCaller): 7 | 8 | help_description = 'Retrieve an array of screenshots from a report in the Base64 format by \'{}\'' 9 | result_msg_for_files = '\n\nResponse from sample screenshots endpoint contain json with encoded strings. Screenshots were saved in the output folder ({}).' 10 | 11 | def add_parser_args(self, child_parser): 12 | parser_argument_builder = super(CliReportScreenshots, self).add_parser_args(child_parser) 13 | parser_argument_builder.add_id_arg() 14 | parser_argument_builder.add_file_output_path_opt() 15 | 16 | def get_result_msg(self): 17 | parent_result_msg = super(CliReportScreenshots, self).get_result_msg() 18 | 19 | if self.api_object.if_request_success() is True and self.given_args['verbose'] is True: 20 | return parent_result_msg + ' ' + self.get_result_msg_for_files() 21 | 22 | return parent_result_msg 23 | 24 | def get_processed_output_path(self): 25 | path = super(CliReportScreenshots, self).get_processed_output_path() 26 | 27 | return '{}/{}'.format(path, self.get_specific_output_dir_name()) 28 | 29 | def get_specific_output_dir_name(self): 30 | return '{}-{}'.format(self.action_name, self.given_args['id']) 31 | 32 | def do_post_processing(self): 33 | if self.api_object.if_request_success() is True: 34 | self.save_files() 35 | 36 | def save_files(self): 37 | output_path = '{}/{}'.format(self.cli_output_folder, self.get_specific_output_dir_name()) 38 | 39 | CliFileWriter.create_dir_if_not_exists(output_path) 40 | api_response_json = self.api_object.get_response_json() 41 | 42 | for screenshot_data in api_response_json: 43 | CliFileWriter.write(output_path, screenshot_data['name'], base64.b64decode(screenshot_data['image'])) 44 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_state.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliReportState(CliCaller): 5 | 6 | help_description = 'Return state of a submission by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliReportState, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_id_arg() 11 | 12 | -------------------------------------------------------------------------------- /cli/wrappers/report/cli_report_summary.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliReportSummary(CliCaller): 5 | 6 | help_description = 'Return summary of a submission by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliReportSummary, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_id_arg() 11 | 12 | -------------------------------------------------------------------------------- /cli/wrappers/scan/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.scan.cli_scan_convert_to_full import CliScanConvertToFull 2 | from cli.wrappers.scan.cli_scan_file import CliScanFile 3 | from cli.wrappers.scan.cli_scan_scan import CliScanScan 4 | from cli.wrappers.scan.cli_scan_state import CliScanState 5 | from cli.wrappers.scan.cli_scan_url_for_analysis import CliScanUrlForAnalysis 6 | from cli.wrappers.scan.cli_scan_url_to_file import CliScanUrlToFile 7 | -------------------------------------------------------------------------------- /cli/wrappers/scan/cli_scan_convert_to_full.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 3 | 4 | 5 | class CliScanConvertToFull(CliCaller): 6 | 7 | help_description = 'Convert quick scan to sandbox report by \'{}\'' 8 | 9 | def build_argument_builder(self, child_parser): 10 | return SubmissionCliArguments(child_parser) 11 | 12 | def add_parser_args(self, child_parser): 13 | parser_argument_builder = super(CliScanConvertToFull, self).add_parser_args(child_parser) 14 | parser_argument_builder.add_id_arg() 15 | parser_argument_builder.add_env_id_arg(True) 16 | parser_argument_builder.add_submission_no_share_third_party_opt() 17 | parser_argument_builder.add_submission_allow_community_access_opt() 18 | parser_argument_builder.add_submission_no_hash_lookup() 19 | parser_argument_builder.add_submission_action_script_opt() 20 | parser_argument_builder.add_submission_hybrid_analysis_opt() 21 | parser_argument_builder.add_submission_experimental_anti_evasion_opt() 22 | parser_argument_builder.add_submission_script_logging_opt() 23 | parser_argument_builder.add_submission_input_sample_tampering_opt() 24 | parser_argument_builder.add_submission_tor_enabled_analysis_opt() 25 | parser_argument_builder.add_submission_offline_analysis_opt() 26 | parser_argument_builder.add_submission_email_opt() 27 | parser_argument_builder.add_submission_comment_opt() 28 | parser_argument_builder.add_submission_custom_date_time_opt() 29 | parser_argument_builder.add_submission_custom_cmd_line_opt() 30 | parser_argument_builder.add_submission_custom_run_time_opt() 31 | parser_argument_builder.add_submission_client_opt() 32 | parser_argument_builder.add_submission_submit_name_opt() 33 | parser_argument_builder.add_submission_priority_opt() 34 | parser_argument_builder.add_submission_document_password_opt() 35 | parser_argument_builder.add_submission_environment_variable_opt() 36 | -------------------------------------------------------------------------------- /cli/wrappers/scan/cli_scan_file.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from constants import ACTION_OVERVIEW_GET 3 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 4 | 5 | 6 | class CliScanFile(CliCaller): 7 | 8 | help_description = 'Submit a file for quick scan (you can check results by \'' + ACTION_OVERVIEW_GET + '\' action) by \'{}\'' 9 | 10 | def build_argument_builder(self, child_parser): 11 | return SubmissionCliArguments(child_parser) 12 | 13 | def add_parser_args(self, child_parser): 14 | parser_argument_builder = super(CliScanFile, self).add_parser_args(child_parser) 15 | parser_argument_builder.add_submit_files_arg() 16 | parser_argument_builder.add_scan_type_arg() 17 | parser_argument_builder.add_submission_no_share_third_party_opt() 18 | parser_argument_builder.add_submission_allow_community_access_opt() 19 | parser_argument_builder.add_submission_comment_opt() 20 | parser_argument_builder.add_submission_submit_name_opt() 21 | 22 | -------------------------------------------------------------------------------- /cli/wrappers/scan/cli_scan_scan.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliScanScan(CliCaller): 5 | 6 | help_description = 'Get quick scan results (worth to use when submission endpoint didn\'t return full data) by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliScanScan, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_id_arg('ID of scan') 11 | -------------------------------------------------------------------------------- /cli/wrappers/scan/cli_scan_state.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.formatter.cli_json_formatter import CliJsonFormatter 3 | 4 | 5 | class CliScanState(CliCaller): 6 | 7 | help_description = 'Return list of available scanners by \'{}\'' 8 | 9 | def get_result_msg(self): 10 | parent_result_msg = super(CliScanState, self).get_result_msg() 11 | 12 | if self.api_object.if_request_success() is True and self.given_args['verbose'] is False: 13 | current_json = self.api_object.get_response_json() 14 | filtered_json = [] 15 | for scan in current_json: 16 | if scan['available']: 17 | filtered_json.append(scan) 18 | 19 | return CliJsonFormatter.format_to_pretty_string(filtered_json) 20 | 21 | return parent_result_msg 22 | -------------------------------------------------------------------------------- /cli/wrappers/scan/cli_scan_url_for_analysis.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from constants import ACTION_OVERVIEW_GET 3 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 4 | 5 | 6 | class CliScanUrlForAnalysis(CliCaller): 7 | 8 | help_description = 'Submit a url for quick scan (you can check results by \'' + ACTION_OVERVIEW_GET + '\' action) by \'{}\'' 9 | 10 | def build_argument_builder(self, child_parser): 11 | return SubmissionCliArguments(child_parser) 12 | 13 | def add_parser_args(self, child_parser): 14 | parser_argument_builder = super(CliScanUrlForAnalysis, self).add_parser_args(child_parser) 15 | parser_argument_builder.add_url_arg('Url for analyze') 16 | parser_argument_builder.add_scan_type_arg() 17 | parser_argument_builder.add_submission_no_share_third_party_opt() 18 | parser_argument_builder.add_submission_allow_community_access_opt() 19 | parser_argument_builder.add_submission_comment_opt() 20 | parser_argument_builder.add_submission_submit_name_opt() 21 | -------------------------------------------------------------------------------- /cli/wrappers/scan/cli_scan_url_to_file.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from constants import ACTION_OVERVIEW_GET 3 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 4 | 5 | 6 | class CliScanUrlToFile(CliCaller): 7 | 8 | help_description = 'Submit a file by url for quick scan (you can check results by \'' + ACTION_OVERVIEW_GET + '\' action) by \'{}\'' 9 | 10 | def build_argument_builder(self, child_parser): 11 | return SubmissionCliArguments(child_parser) 12 | 13 | def add_parser_args(self, child_parser): 14 | parser_argument_builder = super(CliScanUrlToFile, self).add_parser_args(child_parser) 15 | parser_argument_builder.add_url_arg('Url of file to submit') 16 | parser_argument_builder.add_scan_type_arg() 17 | parser_argument_builder.add_submission_no_share_third_party_opt() 18 | parser_argument_builder.add_submission_allow_community_access_opt() 19 | parser_argument_builder.add_submission_comment_opt() 20 | parser_argument_builder.add_submission_submit_name_opt() 21 | -------------------------------------------------------------------------------- /cli/wrappers/search/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.search.cli_search_hash import CliSearchHash 2 | from cli.wrappers.search.cli_search_hashes import CliSearchHashes 3 | from cli.wrappers.search.cli_search_states import CliSearchStates 4 | from cli.wrappers.search.cli_search_terms import CliSearchTerms 5 | -------------------------------------------------------------------------------- /cli/wrappers/search/cli_search_hash.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSearchHash(CliCaller): 5 | 6 | help_description = 'Get summary for given hash by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliSearchHash, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_hash_arg() 11 | -------------------------------------------------------------------------------- /cli/wrappers/search/cli_search_hashes.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSearchHashes(CliCaller): 5 | 6 | help_description = 'Get summary for given hashes by \'{}\'' 7 | 8 | def attach_args(self, args): 9 | super(CliSearchHashes, self).attach_args(self.convert_file_hashes_to_array(args, 'hashes_file')) 10 | 11 | def add_parser_args(self, child_parser): 12 | parser_argument_builder = super(CliSearchHashes, self).add_parser_args(child_parser) 13 | parser_argument_builder.add_file_with_hash_list_arg(['md5', 'sha1', 'sha256']) 14 | -------------------------------------------------------------------------------- /cli/wrappers/search/cli_search_states.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSearchStates(CliCaller): 5 | 6 | help_description = 'Get states for given ids by \'{}\'' 7 | 8 | def attach_args(self, args): 9 | super(CliSearchStates, self).attach_args(self.convert_file_hashes_to_array(args, 'mixed_ids_file', 'ids')) 10 | 11 | def add_parser_args(self, child_parser): 12 | parser_argument_builder = super(CliSearchStates, self).add_parser_args(child_parser) 13 | parser_argument_builder.add_file_with_ids_arg(['jobId', 'sha256:environmentId']) 14 | -------------------------------------------------------------------------------- /cli/wrappers/search/cli_search_terms.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.arguments_builders.search_cli_arguments import SearchCliArguments 3 | 4 | 5 | class CliSearchTerms(CliCaller): 6 | 7 | help_description = 'Search the database using given search terms by \'{}\'' 8 | 9 | def build_argument_builder(self, child_parser): 10 | return SearchCliArguments(child_parser) 11 | 12 | def add_parser_args(self, child_parser): 13 | parser_argument_builder = super(CliSearchTerms, self).add_parser_args(child_parser) 14 | parser_argument_builder.add_search_term_filename_opt() 15 | parser_argument_builder.add_search_term_filetype_opt() 16 | parser_argument_builder.add_search_term_filetype_desc_opt() 17 | parser_argument_builder.add_search_term_env_id_opt() 18 | parser_argument_builder.add_search_term_country_opt() 19 | parser_argument_builder.add_search_term_verdict_opt() 20 | parser_argument_builder.add_search_term_av_detect_opt() 21 | parser_argument_builder.add_search_term_vx_family_opt() 22 | parser_argument_builder.add_search_term_tag_opt() 23 | parser_argument_builder.add_search_term_port_opt() 24 | parser_argument_builder.add_search_term_host_opt() 25 | parser_argument_builder.add_search_term_domain_opt() 26 | parser_argument_builder.add_search_term_url_opt() 27 | parser_argument_builder.add_search_term_similar_to_opt() 28 | parser_argument_builder.add_search_term_context_opt() 29 | parser_argument_builder.add_search_term_imp_hash_opt() 30 | parser_argument_builder.add_search_term_ssdeep_opt() 31 | parser_argument_builder.add_search_term_authentihash_opt() 32 | -------------------------------------------------------------------------------- /cli/wrappers/submit/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.submit.cli_submit_dropped_file import CliSubmitDroppedFile 2 | from cli.wrappers.submit.cli_submit_file import CliSubmitFile 3 | from cli.wrappers.submit.cli_submit_hash_for_url import CliSubmitHashForUrl 4 | from cli.wrappers.submit.cli_submit_reanalyze import CliSubmitReanalyze 5 | from cli.wrappers.submit.cli_submit_url_for_analysis import CliSubmitUrlForAnalysis 6 | from cli.wrappers.submit.cli_submit_url_to_file import CliSubmitUrlToFile 7 | -------------------------------------------------------------------------------- /cli/wrappers/submit/cli_submit_dropped_file.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 3 | 4 | 5 | class CliSubmitDroppedFile(CliCaller): 6 | 7 | help_description = 'Submit dropped file for analysis by \'{}\'' 8 | 9 | def build_argument_builder(self, child_parser): 10 | return SubmissionCliArguments(child_parser) 11 | 12 | def add_parser_args(self, child_parser): 13 | parser_argument_builder = super(CliSubmitDroppedFile, self).add_parser_args(child_parser) 14 | parser_argument_builder.add_id_arg() 15 | parser_argument_builder.add_sha256_arg('Sha256 of dropped file for analyze') 16 | parser_argument_builder.add_submission_no_share_third_party_opt() 17 | 18 | -------------------------------------------------------------------------------- /cli/wrappers/submit/cli_submit_file.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 3 | 4 | 5 | class CliSubmitFile(CliCaller): 6 | 7 | help_description = 'Submit a file for analysis by \'{}\'' 8 | 9 | def build_argument_builder(self, child_parser): 10 | return SubmissionCliArguments(child_parser) 11 | 12 | def add_parser_args(self, child_parser): 13 | parser_argument_builder = super(CliSubmitFile, self).add_parser_args(child_parser) 14 | parser_argument_builder.add_submit_files_arg() 15 | parser_argument_builder.add_env_id_arg(True) 16 | parser_argument_builder.add_submission_no_share_third_party_opt() 17 | parser_argument_builder.add_submission_allow_community_access_opt() 18 | parser_argument_builder.add_submission_no_hash_lookup() 19 | parser_argument_builder.add_submission_action_script_opt() 20 | parser_argument_builder.add_submission_hybrid_analysis_opt() 21 | parser_argument_builder.add_submission_experimental_anti_evasion_opt() 22 | parser_argument_builder.add_submission_script_logging_opt() 23 | parser_argument_builder.add_submission_input_sample_tampering_opt() 24 | parser_argument_builder.add_submission_tor_enabled_analysis_opt() 25 | parser_argument_builder.add_submission_offline_analysis_opt() 26 | parser_argument_builder.add_submission_email_opt() 27 | parser_argument_builder.add_submission_comment_opt() 28 | parser_argument_builder.add_submission_custom_date_time_opt() 29 | parser_argument_builder.add_submission_custom_cmd_line_opt() 30 | parser_argument_builder.add_submission_custom_run_time_opt() 31 | parser_argument_builder.add_submission_client_opt() 32 | parser_argument_builder.add_submission_submit_name_opt() 33 | parser_argument_builder.add_submission_priority_opt() 34 | parser_argument_builder.add_submission_document_password_opt() 35 | parser_argument_builder.add_submission_environment_variable_opt() 36 | -------------------------------------------------------------------------------- /cli/wrappers/submit/cli_submit_hash_for_url.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSubmitHashForUrl(CliCaller): 5 | 6 | help_description = 'Determine a SHA256 that an online file or URL submission will have when being processed by the system by \'{}\'' 7 | 8 | def add_parser_args(self, child_parser): 9 | parser_argument_builder = super(CliSubmitHashForUrl, self).add_parser_args(child_parser) 10 | parser_argument_builder.add_url_arg('Url to check') 11 | -------------------------------------------------------------------------------- /cli/wrappers/submit/cli_submit_reanalyze.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 3 | 4 | 5 | class CliSubmitReanalyze(CliCaller): 6 | 7 | help_description = 'Reanalyze a generated report by \'{}\'' 8 | 9 | def build_argument_builder(self, child_parser): 10 | return SubmissionCliArguments(child_parser) 11 | 12 | def add_parser_args(self, child_parser): 13 | parser_argument_builder = super(CliSubmitReanalyze, self).add_parser_args(child_parser) 14 | parser_argument_builder.add_id_arg() 15 | parser_argument_builder.add_submission_no_share_third_party_opt() 16 | parser_argument_builder.add_submission_no_hash_lookup() 17 | 18 | -------------------------------------------------------------------------------- /cli/wrappers/submit/cli_submit_url_for_analysis.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 3 | 4 | 5 | class CliSubmitUrlForAnalysis(CliCaller): 6 | 7 | help_description = 'Submit an url for analysis by \'{}\'' 8 | 9 | def build_argument_builder(self, child_parser): 10 | return SubmissionCliArguments(child_parser) 11 | 12 | def add_parser_args(self, child_parser): 13 | parser_argument_builder = super(CliSubmitUrlForAnalysis, self).add_parser_args(child_parser) 14 | parser_argument_builder.add_url_arg('Url for analyze') 15 | parser_argument_builder.add_env_id_arg(True) 16 | parser_argument_builder.add_submission_no_share_third_party_opt() 17 | parser_argument_builder.add_submission_allow_community_access_opt() 18 | parser_argument_builder.add_submission_no_hash_lookup() 19 | parser_argument_builder.add_submission_action_script_opt() 20 | parser_argument_builder.add_submission_hybrid_analysis_opt() 21 | parser_argument_builder.add_submission_experimental_anti_evasion_opt() 22 | parser_argument_builder.add_submission_script_logging_opt() 23 | parser_argument_builder.add_submission_input_sample_tampering_opt() 24 | parser_argument_builder.add_submission_tor_enabled_analysis_opt() 25 | parser_argument_builder.add_submission_offline_analysis_opt() 26 | parser_argument_builder.add_submission_email_opt() 27 | parser_argument_builder.add_submission_comment_opt() 28 | parser_argument_builder.add_submission_custom_date_time_opt() 29 | parser_argument_builder.add_submission_custom_cmd_line_opt() 30 | parser_argument_builder.add_submission_custom_run_time_opt() 31 | parser_argument_builder.add_submission_client_opt() 32 | parser_argument_builder.add_submission_submit_name_opt() 33 | parser_argument_builder.add_submission_priority_opt() 34 | parser_argument_builder.add_submission_document_password_opt() 35 | parser_argument_builder.add_submission_environment_variable_opt() 36 | -------------------------------------------------------------------------------- /cli/wrappers/submit/cli_submit_url_to_file.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | from cli.arguments_builders.submission_cli_arguments import SubmissionCliArguments 3 | 4 | 5 | class CliSubmitUrlToFile(CliCaller): 6 | 7 | help_description = 'Submit a file by url for analysis by \'{}\'' 8 | 9 | def build_argument_builder(self, child_parser): 10 | return SubmissionCliArguments(child_parser) 11 | 12 | def add_parser_args(self, child_parser): 13 | parser_argument_builder = super(CliSubmitUrlToFile, self).add_parser_args(child_parser) 14 | parser_argument_builder.add_url_arg('Url for analyze') 15 | parser_argument_builder.add_env_id_arg(True) 16 | parser_argument_builder.add_submission_no_share_third_party_opt() 17 | parser_argument_builder.add_submission_allow_community_access_opt() 18 | parser_argument_builder.add_submission_no_hash_lookup() 19 | parser_argument_builder.add_submission_action_script_opt() 20 | parser_argument_builder.add_submission_hybrid_analysis_opt() 21 | parser_argument_builder.add_submission_experimental_anti_evasion_opt() 22 | parser_argument_builder.add_submission_script_logging_opt() 23 | parser_argument_builder.add_submission_input_sample_tampering_opt() 24 | parser_argument_builder.add_submission_tor_enabled_analysis_opt() 25 | parser_argument_builder.add_submission_offline_analysis_opt() 26 | parser_argument_builder.add_submission_email_opt() 27 | parser_argument_builder.add_submission_comment_opt() 28 | parser_argument_builder.add_submission_custom_date_time_opt() 29 | parser_argument_builder.add_submission_custom_cmd_line_opt() 30 | parser_argument_builder.add_submission_custom_run_time_opt() 31 | parser_argument_builder.add_submission_client_opt() 32 | parser_argument_builder.add_submission_submit_name_opt() 33 | parser_argument_builder.add_submission_priority_opt() 34 | parser_argument_builder.add_submission_document_password_opt() 35 | parser_argument_builder.add_submission_environment_variable_opt() 36 | -------------------------------------------------------------------------------- /cli/wrappers/system/__init__.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.system.cli_system_backend import CliSystemBackend 2 | from cli.wrappers.system.cli_system_environments import CliSystemEnvironments 3 | from cli.wrappers.system.cli_system_heartbeat import CliSystemHeartbeat 4 | from cli.wrappers.system.cli_system_in_progress import CliSystemInProgress 5 | from cli.wrappers.system.cli_system_php import CliSystemPhp 6 | from cli.wrappers.system.cli_system_queue_size import CliSystemQueueSize 7 | from cli.wrappers.system.cli_system_state import CliSystemState 8 | from cli.wrappers.system.cli_system_stats import CliSystemStats 9 | from cli.wrappers.system.cli_system_version import CliSystemVersion 10 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_backend.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemBackend(CliCaller): 5 | 6 | help_description = 'Returns information about configured backend nodes by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_environments.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemEnvironments(CliCaller): 5 | 6 | help_description = 'Return information about available execution environments by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_heartbeat.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemHeartbeat(CliCaller): 5 | 6 | help_description = 'Return heartbeat by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_in_progress.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemInProgress(CliCaller): 5 | 6 | help_description = 'Return information about processed samples by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_php.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemPhp(CliCaller): 5 | 6 | help_description = 'Return information about server PHP configuration by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_queue_size.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemQueueSize(CliCaller): 5 | 6 | help_description = 'Return information about queue size by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_state.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemState(CliCaller): 5 | 6 | help_description = 'Returns a full system state query, including all available action scripts, environments, files in progress etc. by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_stats.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemStats(CliCaller): 5 | 6 | help_description = 'Returns a package of webservice statistics, e.g. the total number of submissions, unique submissions, signature ID distribution, user comments etc. by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /cli/wrappers/system/cli_system_version.py: -------------------------------------------------------------------------------- 1 | from cli.wrappers.cli_caller import CliCaller 2 | 3 | 4 | class CliSystemVersion(CliCaller): 5 | 6 | help_description = 'Returns system elements versions by \'{}\'' 7 | 8 | -------------------------------------------------------------------------------- /colors.py: -------------------------------------------------------------------------------- 1 | from colorama import Fore, Back, Style 2 | import sys 3 | 4 | 5 | class Color: 6 | 7 | @staticmethod 8 | def is_atty(): 9 | if hasattr(sys.stderr, 'isatty') and not sys.stderr.isatty(): 10 | return False 11 | 12 | if hasattr(sys.stdout, 'isatty') and not sys.stdout.isatty(): 13 | return False 14 | 15 | return True 16 | 17 | @staticmethod 18 | def error(text): 19 | text = str(text) 20 | 21 | return Back.RED + text + Style.RESET_ALL if Color.is_atty() else text 22 | 23 | @staticmethod 24 | def control(text): 25 | text = '\n<<< ' + str(text) + ' >>>\n\r' 26 | 27 | return Fore.YELLOW + text + Style.RESET_ALL if Color.is_atty() else text 28 | 29 | @staticmethod 30 | def control_without_arrows(text): 31 | text = str(text) 32 | 33 | return Fore.YELLOW + text + Style.RESET_ALL if Color.is_atty() else text 34 | 35 | @staticmethod 36 | def warning(text): 37 | text = '\n\n<<< Warning: ' + str(text) + ' >>>\n' 38 | 39 | return Back.YELLOW + text + Style.RESET_ALL if Color.is_atty() else text 40 | 41 | @staticmethod 42 | def success(text): 43 | text = str(text) 44 | 45 | return Back.GREEN + text + Style.RESET_ALL if Color.is_atty() else text 46 | -------------------------------------------------------------------------------- /config_tpl.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Note: this is an example configuration file (the API key does not actually work). 3 | Before using VxApi, please copy this file, name it 'config.py` and then put in the same directory as the current one. 4 | ''' 5 | 6 | 7 | def get_config(): 8 | return { 9 | 'api_key': '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', 10 | 'server': 'https://www.hybrid-analysis.com' 11 | } 12 | -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | ACTION_SEARCH_HASH = 'search_hash' 5 | ACTION_SEARCH_HASHES = 'search_hashes' 6 | ACTION_SEARCH_STATES = 'search_states' 7 | ACTION_SEARCH_TERMS = 'search_terms' 8 | 9 | ACTION_OVERVIEW_GET = 'overview_get' 10 | ACTION_OVERVIEW_REFRESH = 'overview_refresh' 11 | ACTION_OVERVIEW_GET_SUMMARY = 'overview_get_summary' 12 | ACTION_OVERVIEW_GET_SAMPLE = 'overview_download_sample' 13 | 14 | ACTION_SUBMIT_DROPPED_FILE = 'submit_dropped_file' 15 | ACTION_SUBMIT_FILE = 'submit_file' 16 | ACTION_SUBMIT_HASH_FOR_URL = 'submit_hash_for_url' 17 | ACTION_SUBMIT_REANALYZE = 'submit_reanalyze' 18 | ACTION_SUBMIT_URL_FOR_ANALYSIS = 'submit_url_for_analysis' 19 | ACTION_SUBMIT_URL_TO_FILE = 'submit_url_to_file' 20 | 21 | ACTION_REPORT_GET_BULK_SUMMARY = 'report_get_bulk_summary' 22 | ACTION_REPORT_GET_BULK_DEMO = 'report_get_demo_bulk' 23 | ACTION_REPORT_GET_DROPPED_FILES = 'report_get_dropped_files' 24 | ACTION_REPORT_GET_DROPPED_FILE_RAW = 'report_get_raw_dropped_file' 25 | ACTION_REPORT_GET_ENHANCED_SUMMARY = 'report_get_enhanced_summary' 26 | ACTION_REPORT_GET_FILE = 'report_get_file' 27 | ACTION_REPORT_GET_SCREENSHOTS = 'report_get_screenshots' 28 | ACTION_REPORT_GET_STATE = 'report_get_state' 29 | ACTION_REPORT_GET_SUMMARY = 'report_get_summary' 30 | 31 | ACTION_SYSTEM_VERSION = 'system_get_version' 32 | ACTION_SYSTEM_ENVIRONMENTS = 'system_get_environments' 33 | ACTION_SYSTEM_STATS = 'system_get_stats' 34 | ACTION_SYSTEM_STATE = 'system_get_state' 35 | ACTION_SYSTEM_PHP = 'system_get_php' 36 | ACTION_SYSTEM_CONFIGURATION = 'system_get_configuration' 37 | ACTION_SYSTEM_BACKEND = 'system_get_backend' 38 | ACTION_SYSTEM_QUEUE_SIZE = 'system_get_queue_size' 39 | ACTION_SYSTEM_IN_PROGRESS = 'system_get_in_progress_stats' 40 | ACTION_SYSTEM_TOTAL_SUBMISSION = 'system_get_total_submissions_stats' 41 | ACTION_SYSTEM_HEARTBEAT = 'system_get_heartbeat' 42 | 43 | ACTION_KEY_CURRENT = 'key_get_current' 44 | ACTION_KEY_CREATE = 'key_create' 45 | 46 | ACTION_FEED = 'feed_get' 47 | ACTION_FEED_LATEST = 'feed_get_latest' 48 | 49 | ACTION_SCAN_CONVERT_TO_FULL = 'scan_convert_to_full' 50 | ACTION_SCAN_FILE = 'scan_file' 51 | ACTION_SCAN_SCAN = 'scan_get_result' 52 | ACTION_SCAN_STATE = 'scan_get_scanners' 53 | ACTION_SCAN_URL_FOR_ANALYSIS = 'scan_url_for_analysis' 54 | ACTION_SCAN_URL_TO_FILE = 'scan_url_to_file' 55 | 56 | MINIMAL_SUPPORTED_INSTANCE_VERSION = '8.2' 57 | CLI_BASE_PATH = os.path.dirname(os.path.realpath(__file__)) 58 | CALLED_SCRIPT = sys.argv[0] 59 | 60 | ACTION_WITH_MULTIPLE_CALL_SUPPORT = [ACTION_SUBMIT_FILE, ACTION_SCAN_FILE] 61 | -------------------------------------------------------------------------------- /exceptions.py: -------------------------------------------------------------------------------- 1 | class VxError(Exception): 2 | pass 3 | 4 | 5 | class OptionNotDeclaredError(VxError): 6 | pass 7 | 8 | 9 | class ResponseObjectNotExistError(VxError): 10 | pass 11 | 12 | 13 | class FilesSavingMethodNotDeclaredError(VxError): 14 | pass 15 | 16 | 17 | class FailedFileSavingError(VxError): 18 | pass 19 | 20 | 21 | class ResponseTextContentTypeError(VxError): 22 | pass 23 | 24 | 25 | class UrlBuildError(VxError): 26 | pass 27 | 28 | 29 | class ExceededApiLimitsError(VxError): 30 | pass 31 | 32 | 33 | class MissingConfigurationError(VxError): 34 | pass 35 | 36 | 37 | class RetrievingApiKeyDataError(VxError): 38 | pass 39 | 40 | 41 | class ReachedApiLimitError(VxError): 42 | pass 43 | 44 | 45 | class JsonParseError(VxError): 46 | pass 47 | 48 | 49 | class ConfigError(VxError): 50 | pass 51 | -------------------------------------------------------------------------------- /helper_classes/cli_helper.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import traceback 3 | from colors import Color 4 | from constants import * 5 | 6 | 7 | class CliHelper: 8 | 9 | @staticmethod 10 | def print_call_info(cli_object): 11 | print(Color.control('Request was sent at ' + '{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()))) 12 | print('Endpoint URL: {}'.format(cli_object.api_object.get_full_endpoint_url())) 13 | print('HTTP Method: {}'.format(cli_object.api_object.request_method_name.upper())) 14 | print('Sent GET params: {}'.format(cli_object.api_object.params)) 15 | print('Sent POST params: {}'.format(cli_object.api_object.data)) 16 | print('Sent files: {}'.format(cli_object.api_object.files)) 17 | 18 | @staticmethod 19 | def print_error_info(e): 20 | print(Color.control('During the code execution, error has occurred. Please try again or contact the support.')) 21 | print(Color.error('Message: \'{}\'.').format(str(e)) + '\n') 22 | print(traceback.format_exc()) 23 | 24 | @staticmethod 25 | def prompt_for_sharing_confirmation(args, instance_url): 26 | if 'nosharevt' in args: 27 | if args['nosharevt'] == 'no' and args['quiet'] is False: 28 | warning_msg = 'You are about to submit your file to all users of {} and the public.'.format(instance_url) 29 | if 'hybrid-analysis.com' in instance_url: 30 | warning_msg += ' Please make sure you consent to the Terms and Conditions of Use and Data Privacy Policy available at: {} and {}.'.format('https://www.hybrid-analysis.com/terms', 'https://www.hybrid-analysis.com/data-protection-policy') 31 | warning_msg += ' [y/n]' 32 | submit_warning = input(warning_msg) 33 | if not submit_warning or submit_warning[0].lower() != 'y': 34 | print('You did not indicate approval, exiting ...') 35 | exit(1) 36 | 37 | @staticmethod 38 | def prompt_for_dir_content_submission(args): 39 | if args['chosen_action'] == ACTION_SUBMIT_FILE: 40 | number_of_files_to_submit = len(args['file']) 41 | if args['quiet'] is False and number_of_files_to_submit > 1: 42 | warning_msg = 'Are you sure that you want to submit the content of selected directory? It contains {} of files. [y/n]'.format(number_of_files_to_submit) 43 | submit_warning = input(warning_msg) 44 | if not submit_warning or submit_warning[0].lower() != 'y': 45 | print('You did not indicate approval, exiting ...') 46 | exit(1) 47 | -------------------------------------------------------------------------------- /helper_classes/file_helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class FileHelper: 5 | 6 | @staticmethod 7 | def get_file_from_dir_recursively(path): 8 | retrieved_files = [] 9 | for folder, subs, files in os.walk(path): 10 | for filename in files: 11 | retrieved_files.append(os.path.join(folder, filename)) 12 | 13 | return retrieved_files 14 | 15 | -------------------------------------------------------------------------------- /img/cli_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PayloadSecurity/VxAPI/55d852c7e07fb586acf5440204e3950ac43b04da/img/cli_example.png -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PayloadSecurity/VxAPI/55d852c7e07fb586acf5440204e3950ac43b04da/img/icon.png -------------------------------------------------------------------------------- /img/scan_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PayloadSecurity/VxAPI/55d852c7e07fb586acf5440204e3950ac43b04da/img/scan_example.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | colorama 3 | -------------------------------------------------------------------------------- /tests/_data/archive-file.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PayloadSecurity/VxAPI/55d852c7e07fb586acf5440204e3950ac43b04da/tests/_data/archive-file.zip -------------------------------------------------------------------------------- /tests/_data/current_key_cache_file.json: -------------------------------------------------------------------------------- 1 | {"timestamp": 0, "response": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, "headers": {"content-type": "application/json", "webservice-version": "8.10", "api-version": "2.2.0", "api-limits": "{\"limits\": {\"minute\": 5, \"hour\": 200}, \"used\": {\"minute\": 0, \"hour\": 0}, \"limit_reached\": false}", "submission-limits": "{\"total\": {\"used\": {\"hour\": 0, \"hour_unique\": 0, \"day\": 0, \"day_unique\": 0, \"week\": 15, \"week_unique\": 0, \"month\": 25, \"month_unique\": 0, \"omega\": 302, \"omega_unique\": 125}, \"quota\": {\"hour\": 200, \"month\": 5000}, \"available\": {\"hour\": 200, \"month\": 4975}, \"quota_reached\": false}, \"quota_reached\": false}"}} 2 | -------------------------------------------------------------------------------- /tests/_data/hashes: -------------------------------------------------------------------------------- 1 | qwerty 2 | some_other 3 | hash 4 | -------------------------------------------------------------------------------- /tests/_data/ids: -------------------------------------------------------------------------------- 1 | qwerty:1 2 | some_other 3 | hash 4 | -------------------------------------------------------------------------------- /tests/_data/my-archive.bin.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PayloadSecurity/VxAPI/55d852c7e07fb586acf5440204e3950ac43b04da/tests/_data/my-archive.bin.gz -------------------------------------------------------------------------------- /tests/_data/screenshots/image1.png: -------------------------------------------------------------------------------- 1 | qwerty -------------------------------------------------------------------------------- /tests/_data/screenshots/image2.png: -------------------------------------------------------------------------------- 1 | tyuiop -------------------------------------------------------------------------------- /tests/_requests_scenarios/cache_1.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | ] 16 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/cache_2.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/feed/latest", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": {"pies": "to"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/feed/feed.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 500, "auth_level_name": "elevated"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/feed/5/days", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": {"pies": "to"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/feed/feed_latest.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/feed/latest", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": {"pies": "to"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/key/key_create.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 500, "auth_level_name": "elevated"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/key/create", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"pies": "to"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/key/key_current.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/overview/overview.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/overview/test", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": {"pies": "to"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/overview/overview_refresh.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/overview/test/refresh", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": {"pies": "to"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/overview/overview_sample.py: -------------------------------------------------------------------------------- 1 | file_handler = open('tests/_data/my-archive.bin.gz', 'rb') 2 | 3 | scenarios = [ 4 | { 5 | "url": "/key/current", 6 | "method": "get", 7 | "status_code": 200, 8 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 9 | "headers": { 10 | "content-type": "application/json", 11 | "webservice-version": "8.10", 12 | "api-version": "2.2.0", 13 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 14 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 15 | } 16 | }, 17 | { 18 | "url": "/overview/test/sample", 19 | "method": "get", 20 | "status_code": 200, 21 | "content": file_handler.read(), 22 | "headers": { 23 | "content-type": "application/octet-stream", 24 | "vx-filename": "my-archive.bin.gz", 25 | "webservice-version": "8.10", 26 | "api-version": "2.2.0", 27 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 28 | } 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/overview/overview_summary.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/overview/test/summary", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": {"pies": "to"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_bulk_summary.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/report/summary", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": [{"doc": "first"}, {"doc": "second"}], 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_demo_bulk.py: -------------------------------------------------------------------------------- 1 | file_handler = open('tests/_data/archive-file.zip', 'rb') 2 | 3 | scenarios = [ 4 | { 5 | "url": "/key/current", 6 | "method": "get", 7 | "status_code": 200, 8 | "json": {"api_key": "111", "auth_level": 500, "auth_level_name": "elevated"}, 9 | "headers": { 10 | "content-type": "application/json", 11 | "webservice-version": "8.10", 12 | "api-version": "2.2.0", 13 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 14 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 15 | } 16 | }, 17 | { 18 | "url": "/report/demo-bulk", 19 | "method": "get", 20 | "status_code": 200, 21 | "content": file_handler.read(), 22 | "headers": { 23 | "content-type": "application/octet-stream", 24 | "vx-filename": "archive-file.zip", 25 | "webservice-version": "8.10", 26 | "api-version": "2.2.0", 27 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 28 | } 29 | } 30 | ] 31 | 32 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_dropped_file_raw.py: -------------------------------------------------------------------------------- 1 | file_handler = open('tests/_data/my-archive.bin.gz', 'rb') 2 | 3 | scenarios = [ 4 | { 5 | "url": "/key/current", 6 | "method": "get", 7 | "status_code": 200, 8 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 9 | "headers": { 10 | "content-type": "application/json", 11 | "webservice-version": "8.10", 12 | "api-version": "2.2.0", 13 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 14 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 15 | } 16 | }, 17 | { 18 | "url": "/report/test/dropped-file-raw/hash_test", 19 | "method": "get", 20 | "status_code": 200, 21 | "content": file_handler.read(), 22 | "headers": { 23 | "content-type": "application/octet-stream", 24 | "vx-filename": "my-archive.bin.gz", 25 | "webservice-version": "8.10", 26 | "api-version": "2.2.0", 27 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 28 | } 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_dropped_files.py: -------------------------------------------------------------------------------- 1 | file_handler = open('tests/_data/archive-file.zip', 'rb') 2 | 3 | scenarios = [ 4 | { 5 | "url": "/key/current", 6 | "method": "get", 7 | "status_code": 200, 8 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 9 | "headers": { 10 | "content-type": "application/json", 11 | "webservice-version": "8.10", 12 | "api-version": "2.2.0", 13 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 14 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 15 | } 16 | }, 17 | { 18 | "url": "/report/test/dropped-files", 19 | "method": "get", 20 | "status_code": 200, 21 | "content": file_handler.read(), 22 | "headers": { 23 | "content-type": "application/octet-stream", 24 | "vx-filename": "archive-file.zip", 25 | "webservice-version": "8.10", 26 | "api-version": "2.2.0", 27 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 28 | } 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_enhanced_summary.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 500, "auth_level_name": "elevated"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/report/test/enhanced-summary", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": [{"doc": "first"}, {"doc": "second"}], 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_file.py: -------------------------------------------------------------------------------- 1 | file_handler = open('tests/_data/my-archive.bin.gz', 'rb') 2 | 3 | scenarios = [ 4 | { 5 | "url": "/key/current", 6 | "method": "get", 7 | "status_code": 200, 8 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 9 | "headers": { 10 | "content-type": "application/json", 11 | "webservice-version": "8.10", 12 | "api-version": "2.2.0", 13 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 14 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 15 | } 16 | }, 17 | { 18 | "url": "/report/test/file/bin", 19 | "method": "get", 20 | "status_code": 200, 21 | "content": file_handler.read(), 22 | "headers": { 23 | "content-type": "application/octet-stream", 24 | "vx-filename": "my-archive.bin.gz", 25 | "webservice-version": "8.10", 26 | "api-version": "2.2.0", 27 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 28 | } 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_screenshots.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | first_image = open('tests/_data/screenshots/image1.png', 'rb') 4 | second_image = open('tests/_data/screenshots/image2.png', 'rb') 5 | 6 | scenarios = [ 7 | { 8 | "url": "/key/current", 9 | "method": "get", 10 | "status_code": 200, 11 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 12 | "headers": { 13 | "content-type": "application/json", 14 | "webservice-version": "8.10", 15 | "api-version": "2.2.0", 16 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 17 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 18 | } 19 | }, 20 | { 21 | "url": "/report/test/screenshots", 22 | "method": "get", 23 | "status_code": 200, 24 | "json": [ 25 | {'name': 'image1.png', 'image': base64.b64encode(first_image.read()).decode(), 'date': 'd-m-y'}, 26 | {'name': 'image2.png', 'image': base64.b64encode(second_image.read()).decode(), 'date': 'd-m-y'} 27 | ], 28 | "headers": { 29 | "content-type": "application/json", 30 | "webservice-version": "8.10", 31 | "api-version": "2.2.0", 32 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 33 | } 34 | } 35 | ] 36 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_state.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/report/test/state", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": [{"doc": "first"}, {"doc": "second"}], 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/report/report_summary.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 500, "auth_level_name": "elevated"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/report/test/summary", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": [{"doc": "first"}, {"doc": "second"}], 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/scan/scan_convert_to_full.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/quick-scan/test/convert-to-full", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/scan/scan_file.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/quick-scan/file", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/scan/scan_scan.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/quick-scan/test", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/scan/scan_state.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/quick-scan/state", 17 | "method": "get", 18 | "status_code": 200, 19 | "json": [{"available": False, 'name': 'cat'}, {"available": True, 'name': 'parrot'}, {"available": False, 'name': 'qwerty'}], 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/scan/scan_url_for_analysis.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/quick-scan/url-for-analysis", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/scan/scan_url_to_file.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/quick-scan/url-to-file", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/search/search_hash.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/search/hash", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"pies": "to"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/search/search_hashes.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/search/hashes", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": [{"doc": "first"}, {"doc": "second"}], 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/search/search_states.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 500, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/search/states", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": [{"doc": "first"}, {"doc": "second"}], 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/search/search_terms.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/search/terms", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": [{"doc": "first"}, {"doc": "second"}], 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/submit/submit_dropped_file.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/submit/dropped-file", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/submit/submit_file.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/submit/file", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/submit/submit_hash_for_url.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/submit/hash-for-url", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/submit/submit_reanalyze.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 500, "auth_level_name": "elevated"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/submit/reanalyze", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/submit/submit_url_for_analysis.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/submit/url-for-analysis", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/_requests_scenarios/submit/submit_url_to_file.py: -------------------------------------------------------------------------------- 1 | scenarios = [ 2 | { 3 | "url": "/key/current", 4 | "method": "get", 5 | "status_code": 200, 6 | "json": {"api_key": "111", "auth_level": 100, "auth_level_name": "default"}, 7 | "headers": { 8 | "content-type": "application/json", 9 | "webservice-version": "8.10", 10 | "api-version": "2.2.0", 11 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False}, 12 | "submission-limits": {"total": {"used": {"hour": 0, "hour_unique": 0, "day": 0, "day_unique": 0, "week": 15, "week_unique": 0, "month": 25, "month_unique": 0, "omega": 302, "omega_unique": 125}, "quota": {"hour": 200, "month": 5000}, "available": {"hour": 200, "month": 4975}, "quota_reached": False}, "quota_reached": False} 13 | } 14 | }, 15 | { 16 | "url": "/submit/url-to-file", 17 | "method": "post", 18 | "status_code": 200, 19 | "json": {"there": "is"}, 20 | "headers": { 21 | "content-type": "application/json", 22 | "webservice-version": "8.10", 23 | "api-version": "2.2.0", 24 | "api-limits": {"limits": {"minute": 5, "hour": 200}, "used": {"minute": 0, "hour": 0}, "limit_reached": False} 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /tests/base_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | import abc 3 | import subprocess 4 | import pytest 5 | import os 6 | import hashlib 7 | import shutil 8 | 9 | 10 | class BaseTest(object): 11 | 12 | output = None 13 | code = None 14 | 15 | @abc.abstractmethod 16 | def get_action_name(self): 17 | return 18 | 19 | @pytest.fixture 20 | def run_command(self): 21 | def do_run(*args): 22 | args = ['python3', 'vxapi.py'] + list(args) 23 | p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 24 | (output, _) = p.communicate() 25 | output = output.decode('utf-8') 26 | self.output = output 27 | self.code = p.returncode 28 | print(output) 29 | 30 | return [p.returncode, output] 31 | 32 | return do_run 33 | 34 | def see_headers(self): 35 | assert 'Running \'VxWebService Python API Connector\'' in self.output 36 | assert 'API query limits for used API Key' in self.output 37 | assert 'Request was sent at' in self.output 38 | assert 'Received response at' in self.output 39 | assert 'Showing response' in self.output 40 | 41 | def see_successful_response_messages(self): 42 | assert 'Response status code: 200' in self.output 43 | assert 'Message: Your request was successfully processed by Falcon Sandbox' in self.output 44 | 45 | def see_response(self, dict): 46 | assert json.dumps(dict, indent=4, sort_keys=True, ensure_ascii=False) in self.output 47 | 48 | def see_sent_params(self, method, params): 49 | assert 'Sent {} params: {}'.format(method, params) in self.output 50 | 51 | def see_sent_files(self, filename): 52 | assert 'Sent files: {\'file\': <_io.BufferedReader name=\'' + filename + '\'>}' in self.output 53 | 54 | def see_reached_url(self, url_part): 55 | assert 'Endpoint URL: mock://my-webservice-instance/api/v2/{}'.format(url_part) in self.output 56 | 57 | def see_missing_file_command_state(self): 58 | assert self.code != 0 59 | assert 'No such file or directory:' in self.output 60 | 61 | def see_file_response(self, path, expected_file_hash, mode='rb'): 62 | prepared_file_path = os.path.dirname(os.path.realpath(__file__)).split('/')[:-1] + [path] 63 | final_output_path = '/'.join(prepared_file_path) 64 | 65 | assert 'Response contains files. They were saved in the output folder'.format(os.path.dirname(final_output_path)) in self.output 66 | 67 | file_handler = open(final_output_path, mode) 68 | 69 | assert hashlib.sha256(file_handler.read()).hexdigest() == expected_file_hash 70 | 71 | def remove_dir_content(self, dir_path): # TODO - move that logic to separated class, which is outside the test env 72 | for file in os.listdir(dir_path): 73 | file_path = os.path.join(dir_path, file) 74 | if os.path.isfile(file_path): 75 | os.unlink(file_path) 76 | elif os.path.isdir(file_path): 77 | shutil.rmtree(file_path) 78 | 79 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | os.environ['VX_APP_ENV'] = 'test' 5 | os.environ['VX_DISABLE_CACHING'] = '1' 6 | os.environ['VX_TEST_CONFIG'] = json.dumps({ 7 | 'api_key': '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', 8 | 'server': 'mock://my-webservice-instance' 9 | }) 10 | -------------------------------------------------------------------------------- /tests/feed/test_feed.py: -------------------------------------------------------------------------------- 1 | 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ['pytester'] 9 | 10 | 11 | class TestFeed(BaseTest): 12 | 13 | expected_response = {'pies': 'to'} 14 | 15 | def get_action_name(self): 16 | return 'feed_get' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'feed.feed' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name(), '5') 25 | self.see_response(self.expected_response) 26 | 27 | def test_verbose_query(self, run_command): 28 | self.init_request_scenario() 29 | 30 | run_command(self.get_action_name(), '5', '-v') 31 | self.see_headers() 32 | self.see_sent_params('GET', {}) 33 | self.see_response(self.expected_response) 34 | -------------------------------------------------------------------------------- /tests/feed/test_feed_latest.py: -------------------------------------------------------------------------------- 1 | 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ['pytester'] 9 | 10 | 11 | class TestFeed(BaseTest): 12 | 13 | expected_response = {'pies': 'to'} 14 | 15 | def get_action_name(self): 16 | return 'feed_get_latest' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'feed.feed_latest' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name()) 25 | self.see_response(self.expected_response) 26 | 27 | def test_verbose_query(self, run_command): 28 | self.init_request_scenario() 29 | 30 | run_command(self.get_action_name(), '-v') 31 | self.see_headers() 32 | self.see_sent_params('GET', {}) 33 | self.see_response(self.expected_response) 34 | -------------------------------------------------------------------------------- /tests/key/test_key_create.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | pytest_plugins = ["pytester"] 6 | 7 | 8 | class TestKeyCreate(BaseTest): 9 | 10 | expected_response = {'pies': 'to'} 11 | 12 | def get_action_name(self): 13 | return 'key_create' 14 | 15 | def init_request_scenario(self): 16 | os.environ['VX_TEST_SCENARIO'] = 'key.key_create' 17 | 18 | def test_base_query(self, run_command): 19 | self.init_request_scenario() 20 | 21 | run_command(self.get_action_name(), 'test') 22 | self.see_response(self.expected_response) 23 | 24 | def test_verbose_query(self, run_command): 25 | self.init_request_scenario() 26 | 27 | run_command(self.get_action_name(), 'test', '-v') 28 | self.see_headers() 29 | self.see_sent_params('POST', {'uid': 'test'}) 30 | self.see_response(self.expected_response) 31 | -------------------------------------------------------------------------------- /tests/key/test_key_current.py: -------------------------------------------------------------------------------- 1 | 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ['pytester'] 9 | 10 | 11 | class TestKeyCurrent(BaseTest): 12 | 13 | expected_response = {'api_key': '111', 'auth_level': 100, 'auth_level_name': 'default'} 14 | 15 | def get_action_name(self): 16 | return 'key_get_current' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'key.key_current' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name()) 25 | self.see_response(self.expected_response) 26 | 27 | def test_verbose_query(self, run_command): 28 | self.init_request_scenario() 29 | 30 | run_command(self.get_action_name(), '-v') 31 | self.see_headers() 32 | self.see_sent_params('GET', {}) 33 | self.see_response(self.expected_response) 34 | -------------------------------------------------------------------------------- /tests/overview/test_get_overview.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestGetOverview(BaseTest): 11 | 12 | expected_response = {'pies': 'to'} 13 | 14 | def get_action_name(self): 15 | return 'overview_get' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'overview.overview' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'test') 24 | self.see_response(self.expected_response) 25 | 26 | def test_verbose_query(self, run_command): 27 | self.init_request_scenario() 28 | 29 | run_command(self.get_action_name(), 'test', '-v') 30 | self.see_headers() 31 | self.see_response(self.expected_response) 32 | -------------------------------------------------------------------------------- /tests/overview/test_get_overview_sample.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestGetOverviewSample(BaseTest): 11 | 12 | expected_hash = '98bdbd3fb298c89cc8ad98fa42c6ea1b819701cd3e5869bc09d1498d333d587c' 13 | 14 | def get_action_name(self): 15 | return 'overview_download_sample' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'overview.overview_sample' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'test') 24 | self.see_file_response('output/overview_download_sample-test-my-archive.bin', self.expected_hash) 25 | 26 | def test_verbose_query(self, run_command): 27 | self.init_request_scenario() 28 | 29 | run_command(self.get_action_name(), 'test', '-v') 30 | self.see_headers() 31 | self.see_file_response('output/overview_download_sample-test-my-archive.bin', self.expected_hash) 32 | -------------------------------------------------------------------------------- /tests/overview/test_get_overview_summary.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestGetOverviewSummary(BaseTest): 11 | 12 | expected_response = {'pies': 'to'} 13 | 14 | def get_action_name(self): 15 | return 'overview_get_summary' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'overview.overview_summary' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'test') 24 | self.see_response(self.expected_response) 25 | 26 | def test_verbose_query(self, run_command): 27 | self.init_request_scenario() 28 | 29 | run_command(self.get_action_name(), 'test', '-v') 30 | self.see_headers() 31 | self.see_response(self.expected_response) 32 | -------------------------------------------------------------------------------- /tests/overview/test_get_refreshed_overview.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestGetRefreshedOverview(BaseTest): 11 | 12 | expected_response = {'pies': 'to'} 13 | 14 | def get_action_name(self): 15 | return 'overview_refresh' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'overview.overview_refresh' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'test') 24 | self.see_response(self.expected_response) 25 | 26 | def test_verbose_query(self, run_command): 27 | self.init_request_scenario() 28 | 29 | run_command(self.get_action_name(), 'test', '-v') 30 | self.see_headers() 31 | self.see_response(self.expected_response) 32 | -------------------------------------------------------------------------------- /tests/report/test_report_bulk_summary.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ["pytester"] 9 | 10 | 11 | class TestReportBulkSummary(BaseTest): 12 | 13 | expected_response = [{'doc': 'first'}, {'doc': 'second'}] 14 | 15 | def get_action_name(self): 16 | return 'report_get_bulk_summary' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'report.report_bulk_summary' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name(), 'tests/_data/hashes') 25 | self.see_response(self.expected_response) 26 | 27 | def test_base_query_with_not_existing_file(self, run_command): 28 | self.init_request_scenario() 29 | run_command(self.get_action_name(), 'not_existing_file') 30 | self.see_missing_file_command_state() 31 | 32 | def test_verbose_query(self, run_command): 33 | self.init_request_scenario() 34 | 35 | run_command(self.get_action_name(), 'tests/_data/hashes', '-v') 36 | self.see_sent_params('POST', {'hashes[0]': 'qwerty', 'hashes[1]': 'some_other', 'hashes[2]': 'hash'}) 37 | self.see_headers() 38 | self.see_response(self.expected_response) 39 | 40 | def test_verbose_query_with_not_existing_file(self, run_command): 41 | self.init_request_scenario() 42 | run_command(self.get_action_name(), 'not_existing_file', '-v') 43 | self.see_missing_file_command_state() 44 | -------------------------------------------------------------------------------- /tests/report/test_report_demo_bulk.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ["pytester"] 9 | 10 | 11 | class TestReportDemoBulk(BaseTest): 12 | 13 | expected_hash = '8b585baa0b8f4bea3af85f93eda4c164d3c577b34856e6ca9d923a676e95a8e9' 14 | 15 | def get_action_name(self): 16 | return 'report_get_demo_bulk' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'report.report_demo_bulk' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name()) 25 | self.see_file_response('output/report_get_demo_bulk-archive-file.zip', self.expected_hash) 26 | 27 | @pytest.mark.parametrize("command_args,expected_sent_params", [ 28 | ([], {'modify_hash': False, 'av_min': 5, 'av_max': 15, 'look_back_size': 400}), 29 | (['--modify-hash', '--av-min', '0', '--av-max', '50', '--look-back-size', '500'], {'modify_hash': True, 'av_min': 0, 'av_max': 50, 'look_back_size': 500}), 30 | ]) 31 | def test_verbose_query(self, run_command, command_args, expected_sent_params): 32 | self.init_request_scenario() 33 | 34 | final_command_args = [self.get_action_name(), '-v'] + command_args 35 | run_command(*final_command_args) 36 | self.see_sent_params('GET', expected_sent_params) 37 | self.see_headers() 38 | self.see_file_response('output/report_get_demo_bulk-archive-file.zip', self.expected_hash) 39 | -------------------------------------------------------------------------------- /tests/report/test_report_dropped_file_raw.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestReportDroppedFileRaw(BaseTest): 11 | 12 | expected_hash = '98bdbd3fb298c89cc8ad98fa42c6ea1b819701cd3e5869bc09d1498d333d587c' 13 | 14 | def get_action_name(self): 15 | return 'report_get_raw_dropped_file' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'report.report_dropped_file_raw' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'test', 'hash_test') 24 | print(self.output) 25 | self.see_file_response('output/report_get_raw_dropped_file-test-my-archive.bin', self.expected_hash) 26 | 27 | def test_verbose_query(self, run_command): 28 | self.init_request_scenario() 29 | 30 | run_command(self.get_action_name(), 'test', 'hash_test', '-v') 31 | self.see_headers() 32 | self.see_file_response('output/report_get_raw_dropped_file-test-my-archive.bin', self.expected_hash) 33 | -------------------------------------------------------------------------------- /tests/report/test_report_dropped_files.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestReportDroppedFiles(BaseTest): 11 | 12 | expected_hash = '8b585baa0b8f4bea3af85f93eda4c164d3c577b34856e6ca9d923a676e95a8e9' 13 | 14 | def get_action_name(self): 15 | return 'report_get_dropped_files' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'report.report_dropped_files' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'test') 24 | print(self.output) 25 | self.see_file_response('output/report_get_dropped_files-test-archive-file.zip', self.expected_hash) 26 | 27 | def test_verbose_query(self, run_command): 28 | self.init_request_scenario() 29 | 30 | run_command(self.get_action_name(), 'test', '-v') 31 | self.see_headers() 32 | self.see_file_response('output/report_get_dropped_files-test-archive-file.zip', self.expected_hash) 33 | -------------------------------------------------------------------------------- /tests/report/test_report_enhanced_summary.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ["pytester"] 9 | 10 | 11 | class TestReportEnhancedSummary(BaseTest): 12 | 13 | expected_response = [{'doc': 'first'}, {'doc': 'second'}] 14 | 15 | def get_action_name(self): 16 | return 'report_get_enhanced_summary' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'report.report_enhanced_summary' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name(), 'test') 25 | self.see_response(self.expected_response) 26 | 27 | def test_verbose_query(self, run_command): 28 | self.init_request_scenario() 29 | 30 | run_command(self.get_action_name(), 'test', '-v') 31 | self.see_sent_params('GET', {}) 32 | self.see_headers() 33 | self.see_response(self.expected_response) 34 | -------------------------------------------------------------------------------- /tests/report/test_report_file.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestReportFile(BaseTest): 11 | 12 | expected_hash = '98bdbd3fb298c89cc8ad98fa42c6ea1b819701cd3e5869bc09d1498d333d587c' 13 | 14 | def get_action_name(self): 15 | return 'report_get_file' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'report.report_file' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'test', 'bin') 24 | self.see_file_response('output/report_get_file-test-my-archive.bin', self.expected_hash) 25 | 26 | def test_verbose_query(self, run_command): 27 | self.init_request_scenario() 28 | 29 | run_command(self.get_action_name(), 'test', 'bin', '-v') 30 | self.see_headers() 31 | self.see_file_response('output/report_get_file-test-my-archive.bin', self.expected_hash) 32 | -------------------------------------------------------------------------------- /tests/report/test_report_screenshots.py: -------------------------------------------------------------------------------- 1 | import os 2 | import hashlib 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestReportScreenshots(BaseTest): 11 | 12 | expected_response = [ 13 | {'name': 'image1.png', 'image': 'cXdlcnR5', 'date': 'd-m-y'}, 14 | {'name': 'image2.png', 'image': 'dHl1aW9w', 'date': 'd-m-y'} 15 | ] 16 | 17 | def get_action_name(self): 18 | return 'report_get_screenshots' 19 | 20 | def init_request_scenario(self): 21 | os.environ['VX_TEST_SCENARIO'] = 'report.report_screenshots' 22 | 23 | def test_base_query(self, run_command): 24 | self.init_request_scenario() 25 | 26 | run_command(self.get_action_name(), 'test') 27 | self.see_response(self.expected_response) 28 | self.check_saved_screenshots() 29 | 30 | def test_verbose_query(self, run_command): 31 | self.init_request_scenario() 32 | 33 | run_command(self.get_action_name(), 'test', '-v') 34 | self.see_sent_params('GET', {}) 35 | self.see_headers() 36 | self.see_response(self.expected_response) 37 | self.check_saved_screenshots() 38 | 39 | 40 | def check_saved_screenshots(self): 41 | output_path = 'output/{}-test'.format(self.get_action_name()) 42 | input_path = 'tests/_data/screenshots' 43 | input_screenshots = sorted(os.listdir(input_path)) 44 | output_screenshots = sorted(os.listdir(output_path)) 45 | assert os.path.exists(output_path) 46 | assert input_screenshots == output_screenshots 47 | for name in input_screenshots: 48 | first_handler = open('{}/{}'.format(input_path, name), 'rb') 49 | second_handler = open('{}/{}'.format(output_path, name), 'rb') 50 | 51 | assert hashlib.sha256(first_handler.read()).hexdigest() == hashlib.sha256(second_handler.read()).hexdigest() 52 | 53 | 54 | -------------------------------------------------------------------------------- /tests/report/test_report_state.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ["pytester"] 9 | 10 | 11 | class TestReportState(BaseTest): 12 | 13 | expected_response = [{'doc': 'first'}, {'doc': 'second'}] 14 | 15 | def get_action_name(self): 16 | return 'report_get_state' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'report.report_state' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name(), 'test') 25 | self.see_response(self.expected_response) 26 | 27 | def test_verbose_query(self, run_command): 28 | self.init_request_scenario() 29 | 30 | run_command(self.get_action_name(), 'test', '-v') 31 | self.see_sent_params('GET', {}) 32 | self.see_headers() 33 | self.see_response(self.expected_response) 34 | -------------------------------------------------------------------------------- /tests/report/test_report_summary.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ["pytester"] 9 | 10 | 11 | class TestReportSummary(BaseTest): 12 | 13 | expected_response = [{'doc': 'first'}, {'doc': 'second'}] 14 | 15 | def get_action_name(self): 16 | return 'report_get_summary' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'report.report_summary' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name(), 'test') 25 | self.see_response(self.expected_response) 26 | 27 | def test_verbose_query(self, run_command): 28 | self.init_request_scenario() 29 | 30 | run_command(self.get_action_name(), 'test', '-v') 31 | self.see_sent_params('GET', {}) 32 | self.see_headers() 33 | self.see_response(self.expected_response) 34 | -------------------------------------------------------------------------------- /tests/scan/test_scan_convert_to_full.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestScanConvertToFull(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'scan_convert_to_full' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'scan.scan_convert_to_full' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'test', '5') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'test', '5', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | self.see_sent_params('POST', {'environment_id': 5, 'no_share_third_party': 1, 'allow_community_access': 1}) 32 | -------------------------------------------------------------------------------- /tests/scan/test_scan_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestScanFile(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'scan_file' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'scan.scan_file' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'tests/_data/hashes', 'all') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'tests/_data/hashes', 'all', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | self.see_sent_files('tests/_data/hashes') 32 | self.see_sent_params('POST', {'scan_type': 'all', 'no_share_third_party': 1, 'allow_community_access': 1}) 33 | -------------------------------------------------------------------------------- /tests/scan/test_scan_scan.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestScanScan(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'scan_get_result' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'scan.scan_scan' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'test') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'test', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | self.see_sent_params('GET', {}) 32 | -------------------------------------------------------------------------------- /tests/scan/test_scan_state.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestScanScan(BaseTest): 10 | 11 | def get_action_name(self): 12 | return 'scan_get_scanners' 13 | 14 | def init_request_scenario(self): 15 | os.environ['VX_TEST_SCENARIO'] = 'scan.scan_state' 16 | 17 | def test_base_query(self, run_command): 18 | self.init_request_scenario() 19 | 20 | run_command(self.get_action_name()) 21 | self.see_response([{"available": True, 'name': 'parrot'}]) 22 | 23 | def test_verbose_query(self, run_command): 24 | self.init_request_scenario() 25 | 26 | run_command(self.get_action_name(), '-v') 27 | self.see_headers() 28 | self.see_response([{"available": False, 'name': 'cat'}, {"available": True, 'name': 'parrot'}, {"available": False, 'name': 'qwerty'}]) 29 | self.see_sent_params('GET', {}) 30 | -------------------------------------------------------------------------------- /tests/scan/test_scan_url_for_analysis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestScanUrlForAnalysis(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'scan_url_for_analysis' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'scan.scan_url_for_analysis' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'example.com', 'all') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'example.com', 'all', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | self.see_sent_params('POST', {'url': 'example.com', 'scan_type': 'all', 'no_share_third_party': 1, 'allow_community_access': 1}) 32 | -------------------------------------------------------------------------------- /tests/scan/test_scan_url_to_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestScanUrlToFile(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'scan_url_to_file' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'scan.scan_url_to_file' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'example.com', 'all') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'example.com', 'all', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | self.see_sent_params('POST', {'url': 'example.com', 'scan_type': 'all', 'no_share_third_party': 1, 'allow_community_access': 1}) 32 | -------------------------------------------------------------------------------- /tests/search/test_search_hash.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestSearchHash(BaseTest): 11 | 12 | expected_response = {'pies': 'to'} 13 | 14 | def get_action_name(self): 15 | return 'search_hash' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'search.search_hash' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'test') 24 | self.see_response(self.expected_response) 25 | 26 | def test_verbose_query(self, run_command): 27 | self.init_request_scenario() 28 | 29 | run_command(self.get_action_name(), 'test', '-v') 30 | self.see_headers() 31 | self.see_response(self.expected_response) 32 | -------------------------------------------------------------------------------- /tests/search/test_search_hashes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ["pytester"] 9 | 10 | 11 | class TestSearchHashes(BaseTest): 12 | 13 | expected_response = [{'doc': 'first'}, {'doc': 'second'}] 14 | 15 | def get_action_name(self): 16 | return 'search_hashes' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'search.search_hashes' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name(), 'tests/_data/hashes') 25 | self.see_response(self.expected_response) 26 | 27 | def test_base_query_with_not_existing_file(self, run_command): 28 | self.init_request_scenario() 29 | run_command(self.get_action_name(), 'not_existing_file') 30 | self.see_missing_file_command_state() 31 | 32 | def test_verbose_query(self, run_command): 33 | self.init_request_scenario() 34 | 35 | run_command(self.get_action_name(), 'tests/_data/hashes', '-v') 36 | self.see_sent_params('POST', {'hashes[0]': 'qwerty', 'hashes[1]': 'some_other', 'hashes[2]': 'hash'}) 37 | self.see_headers() 38 | self.see_response(self.expected_response) 39 | 40 | def test_verbose_query_with_not_existing_file(self, run_command): 41 | self.init_request_scenario() 42 | run_command(self.get_action_name(), 'not_existing_file', '-v') 43 | self.see_missing_file_command_state() 44 | -------------------------------------------------------------------------------- /tests/search/test_search_states.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from base_test import BaseTest 5 | 6 | 7 | pytest_plugins = ["pytester"] 8 | 9 | 10 | class TestSearchStates(BaseTest): 11 | 12 | expected_response = [{'doc': 'first'}, {'doc': 'second'}] 13 | 14 | def get_action_name(self): 15 | return 'search_states' 16 | 17 | def init_request_scenario(self): 18 | os.environ['VX_TEST_SCENARIO'] = 'search.search_states' 19 | 20 | def test_base_query(self, run_command): 21 | self.init_request_scenario() 22 | 23 | run_command(self.get_action_name(), 'tests/_data/ids') 24 | self.see_response(self.expected_response) 25 | 26 | def test_base_query_with_not_existing_file(self, run_command): 27 | self.init_request_scenario() 28 | run_command(self.get_action_name(), 'not_existing_file') 29 | self.see_missing_file_command_state() 30 | 31 | def test_verbose_query(self, run_command): 32 | self.init_request_scenario() 33 | 34 | run_command(self.get_action_name(), 'tests/_data/ids', '-v') 35 | self.see_sent_params('POST', {'ids[0]': 'qwerty:1', 'ids[1]': 'some_other', 'ids[2]': 'hash'}) 36 | self.see_headers() 37 | self.see_response(self.expected_response) 38 | 39 | def test_verbose_query_with_not_existing_file(self, run_command): 40 | self.init_request_scenario() 41 | run_command(self.get_action_name(), 'not_existing_file', '-v') 42 | self.see_missing_file_command_state() 43 | -------------------------------------------------------------------------------- /tests/search/test_search_terms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import os 4 | 5 | from base_test import BaseTest 6 | 7 | 8 | pytest_plugins = ["pytester"] 9 | 10 | 11 | class TestSearchTerms(BaseTest): 12 | 13 | expected_response = [{"doc": "first"}, {"doc": "second"}] 14 | 15 | def get_action_name(self): 16 | return 'search_terms' 17 | 18 | def init_request_scenario(self): 19 | os.environ['VX_TEST_SCENARIO'] = 'search.search_terms' 20 | 21 | def test_base_query(self, run_command): 22 | self.init_request_scenario() 23 | 24 | run_command(self.get_action_name(), '--filename', 'exe') 25 | self.see_response(self.expected_response) 26 | 27 | @pytest.mark.parametrize("command_args,expected_sent_params", [ 28 | (['--filename', 'exe'], {'filename': 'exe'}), 29 | (['--av-detect', '70-80', '--filename', 'exe', '--similar-to', 'qwerty'], {'filename': 'exe', 'av_detect': '70-80', 'similar_to': 'qwerty'}), 30 | (['--av-detect', '70', '--filename', 'exe', '--similar-to', 'qwerty'], {'filename': 'exe', 'av_detect': '70', 'similar_to': 'qwerty'}), 31 | ]) 32 | def test_verbose_query(self, run_command, command_args, expected_sent_params): 33 | self.init_request_scenario() 34 | 35 | final_command_args = [self.get_action_name(), '-v'] + command_args 36 | run_command(*final_command_args) 37 | self.see_sent_params('POST', expected_sent_params) 38 | self.see_headers() 39 | self.see_response(self.expected_response) 40 | -------------------------------------------------------------------------------- /tests/submit/test_submit_dropped_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestSubmitDroppedFile(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'submit_dropped_file' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'submit.submit_dropped_file' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'sha256:envId', 'sha256') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'sha256:envId', 'sha256', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | -------------------------------------------------------------------------------- /tests/submit/test_submit_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestSubmitFile(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'submit_file' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'submit.submit_file' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'tests/_data/hashes', '5') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'tests/_data/hashes', '5', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | self.see_sent_files('tests/_data/hashes') 32 | self.see_sent_params('POST', {'environment_id': 5, 'no_share_third_party': 1, 'allow_community_access': 1}) 33 | -------------------------------------------------------------------------------- /tests/submit/test_submit_hash_for_url.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestSubmitHashForUrl(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'submit_hash_for_url' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'submit.submit_hash_for_url' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'example.com') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'example.com', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | self.see_sent_params('POST', {'url': 'example.com'}) 32 | -------------------------------------------------------------------------------- /tests/submit/test_submit_reanalyze.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | 6 | pytest_plugins = ["pytester"] 7 | 8 | 9 | class TestSubmitReanalyze(BaseTest): 10 | 11 | expected_response = {'there': 'is'} 12 | 13 | def get_action_name(self): 14 | return 'submit_reanalyze' 15 | 16 | def init_request_scenario(self): 17 | os.environ['VX_TEST_SCENARIO'] = 'submit.submit_reanalyze' 18 | 19 | def test_base_query(self, run_command): 20 | self.init_request_scenario() 21 | 22 | run_command(self.get_action_name(), 'some_tmp_id') 23 | self.see_response(self.expected_response) 24 | 25 | def test_verbose_query(self, run_command): 26 | self.init_request_scenario() 27 | 28 | run_command(self.get_action_name(), 'some_tmp_id', '-v') 29 | self.see_headers() 30 | self.see_response(self.expected_response) 31 | self.see_sent_params('POST', {'id': 'some_tmp_id', 'no_share_third_party': 1}) 32 | -------------------------------------------------------------------------------- /tests/submit/test_submit_url_for_analysis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | pytest_plugins = ["pytester"] 6 | 7 | 8 | class TestSubmitUrlForAnalysis(BaseTest): 9 | 10 | expected_response = {'there': 'is'} 11 | 12 | def get_action_name(self): 13 | return 'submit_url_for_analysis' 14 | 15 | def init_request_scenario(self): 16 | os.environ['VX_TEST_SCENARIO'] = 'submit.submit_url_for_analysis' 17 | 18 | def test_base_query(self, run_command): 19 | self.init_request_scenario() 20 | 21 | run_command(self.get_action_name(), 'example.com', '5') 22 | self.see_response(self.expected_response) 23 | 24 | def test_verbose_query(self, run_command): 25 | self.init_request_scenario() 26 | 27 | run_command(self.get_action_name(), 'example.com', '5', '-v') 28 | self.see_headers() 29 | self.see_response(self.expected_response) 30 | self.see_sent_params('POST', {'url': 'example.com', 'environment_id': 5, 'no_share_third_party': 1, 'allow_community_access': 1}) 31 | -------------------------------------------------------------------------------- /tests/submit/test_submit_url_to_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from base_test import BaseTest 4 | 5 | pytest_plugins = ["pytester"] 6 | 7 | 8 | class TestSubmitUrlToFile(BaseTest): 9 | 10 | expected_response = {'there': 'is'} 11 | 12 | def get_action_name(self): 13 | return 'submit_url_to_file' 14 | 15 | def init_request_scenario(self): 16 | os.environ['VX_TEST_SCENARIO'] = 'submit.submit_url_to_file' 17 | 18 | def test_base_query(self, run_command): 19 | self.init_request_scenario() 20 | 21 | run_command(self.get_action_name(), 'some_tmp_id', '5') 22 | self.see_response(self.expected_response) 23 | 24 | def test_verbose_query(self, run_command): 25 | self.init_request_scenario() 26 | 27 | run_command(self.get_action_name(), 'example.com', '5', '-v') 28 | self.see_headers() 29 | self.see_response(self.expected_response) 30 | self.see_sent_params('POST', {'url': 'example.com', 'environment_id': 5, 'no_share_third_party': 1, 'allow_community_access': 1}) 31 | -------------------------------------------------------------------------------- /tests/test_cache.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import json 4 | import hashlib 5 | import pytest 6 | 7 | from base_test import BaseTest 8 | 9 | pytest_plugins = ["pytester"] 10 | 11 | 12 | class TestCache(BaseTest): 13 | 14 | cache_file_path = 'cache/current_key_6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b.json' 15 | 16 | def clear_cache(self): 17 | self.remove_dir_content('cache') 18 | 19 | def load_cache_file_example(self): 20 | file_handler = open('tests/_data/current_key_cache_file.json', 'r') 21 | 22 | return json.load(file_handler) 23 | 24 | def init_request_scenario(self, name): 25 | os.environ['VX_TEST_SCENARIO'] = name 26 | 27 | def save_default_cache_file(self, secs_to_subtract): 28 | example_cache_file_content = self.load_cache_file_example() 29 | 30 | example_cache_file_content['timestamp'] = int(time.time()) - secs_to_subtract 31 | fake_cache_file_handler = open(self.cache_file_path, 'w') 32 | fake_cache_file_handler.write(json.dumps(example_cache_file_content)) 33 | fake_cache_file_handler.close() 34 | 35 | def get_current_hash_of_cache_file(self): 36 | fake_cache_file_handler = open(self.cache_file_path, 'rb') 37 | fake_cache_file_hash = hashlib.sha256(fake_cache_file_handler.read()).hexdigest() 38 | fake_cache_file_handler.close() 39 | 40 | return fake_cache_file_hash 41 | 42 | def test_help_call_without_cache(self, run_command): 43 | self.clear_cache() 44 | self.init_request_scenario('cache_1') 45 | 46 | run_command('-h') 47 | assert 'positional arguments:' in self.output 48 | 49 | assert not os.listdir('cache') 50 | 51 | def test_help_call_with_cache_1(self, run_command): 52 | self.clear_cache() 53 | os.environ['VX_DISABLE_CACHING'] = '0' 54 | self.init_request_scenario('cache_1') 55 | 56 | run_command('-h') 57 | assert len(os.listdir('cache')) == 1 58 | assert os.path.exists('cache/current_key_6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b.json') 59 | assert 'positional arguments:' in self.output 60 | self.clear_cache() 61 | os.environ['VX_DISABLE_CACHING'] = '1' 62 | 63 | @pytest.mark.parametrize("is_exhausted", [(False),(True)]) 64 | def test_help_call_with_cache_2(self, run_command, is_exhausted): 65 | self.clear_cache() 66 | os.environ['VX_DISABLE_CACHING'] = '0' 67 | self.init_request_scenario('cache_1') 68 | 69 | self.save_default_cache_file(999999 if is_exhausted is True else 5) 70 | fake_cache_file_hash = self.get_current_hash_of_cache_file() 71 | 72 | run_command('-h') 73 | 74 | assert 'positional arguments:' in self.output 75 | assert len(os.listdir('cache')) == 1 76 | assert os.path.exists(self.cache_file_path) 77 | 78 | if is_exhausted is True: 79 | assert fake_cache_file_hash != self.get_current_hash_of_cache_file() 80 | else: 81 | assert fake_cache_file_hash == self.get_current_hash_of_cache_file() 82 | 83 | self.clear_cache() 84 | os.environ['VX_DISABLE_CACHING'] = '1' 85 | 86 | def test_cache_refresh(self, run_command): 87 | self.clear_cache() 88 | os.environ['VX_DISABLE_CACHING'] = '0' 89 | self.init_request_scenario('cache_1') 90 | 91 | self.save_default_cache_file(5) 92 | fake_cache_file_hash = self.get_current_hash_of_cache_file() 93 | 94 | run_command('key_get_current') 95 | 96 | self.see_response({'api_key': '111', 'auth_level': 100, 'auth_level_name': 'default'}) 97 | assert len(os.listdir('cache')) == 1 98 | assert os.path.exists(self.cache_file_path) 99 | 100 | assert fake_cache_file_hash != self.get_current_hash_of_cache_file() 101 | self.clear_cache() 102 | os.environ['VX_DISABLE_CACHING'] = '1' 103 | 104 | def test_cache_not_refresh(self, run_command): 105 | self.clear_cache() 106 | os.environ['VX_DISABLE_CACHING'] = '0' 107 | self.init_request_scenario('cache_2') 108 | 109 | self.save_default_cache_file(5) 110 | fake_cache_file_hash = self.get_current_hash_of_cache_file() 111 | 112 | run_command('feed_get_latest') 113 | 114 | self.see_response({'pies': 'to'}) 115 | assert len(os.listdir('cache')) == 1 116 | assert os.path.exists(self.cache_file_path) 117 | 118 | assert fake_cache_file_hash == self.get_current_hash_of_cache_file() 119 | self.clear_cache() 120 | os.environ['VX_DISABLE_CACHING'] = '1' # TODO - try to implement py.test unittester, which supports teardown classes. Then that part should be moved there 121 | 122 | --------------------------------------------------------------------------------