urlscan.io
Use urlscan.io integration to perform scans on suspected URLs and see their reputation.
Data Enrichment & Threat Intelligence · URLScan.io
Details
| ID | urlscan.io |
|---|---|
| Provider | Urlscan io |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.8.3720084 |
| Supported Modules | Agentix XSIAM |
README
Use urlscan.io integration to perform scans on suspected urls and see their reputation.
Configure urlscan.io on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for urlscan.io.
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- Server URL (e.g. https://urlscan.io/api/v1/ )
- API Key (needed only for submitting URLs for scanning)
- Scan Visibility: Determines the visibility level of the scan. This will override the 'public submissions' setting.
- Source Reliability. Reliability of the source providing the intelligence data. (The default value is C - Fairly reliable)
- Scan Country. Specify which country the scan should be performed from. If you omit this value, urlscan will try to do automatic country detection based on the TLD of the URL, GeoIP information of the server and of the user.
- Trust any certificate (not secure)
- Use system proxy settings
- URL Threshold. Minimum number of positive results from urlscan.io to consider the URL malicious.
- User Agent: User Agent used during scans with this integration.
- Click Test to validate the URLs, token, and connection.
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.
- Search for indicators: urlscan-search
- (Deprecated) Submit a URL: urlscan-submit
- Submit a URL (specify the "using" argument): url
1. Search for indicators
Search for an indicator that is related to previous urlscan.io scans.
Notice: Submitting indicators using this command might make the indicator data publicly available. See the vendor’s documentation for more details.
Base Command
urlscan-search
Input
| Argument Name | Description | Required |
|---|---|---|
| searchParameter | Enter a parameter to search as a string (IP, File name, sha256, url, domain) | Required |
| searchType | Allows querying multiple search parameters | Optional |
Context Output
| Path | Description |
|---|---|
| URLScan.URL | Bad URLs found |
| URLScan.Domain | Domain of the URL scanned |
| URLScan.ASN | ASN of the URL scanned |
| URLScan.IP | IP of the url scanned |
| URLScan.ScanID | Scan ID for the URL scanned |
| URLScan.ScanDate | Latest scan date for the URL |
| URLScan.Hash | SHA-256 of file scanned |
| URLScan.FileName | Filename of the file scanned |
| URLScan.FileSize | File size of the file scanned |
| URLScan.FileType | File type of the file scanned |
Command Example
!urlscan-search searchParameter=8.8.8.8
!urlscan-search searchType=advanced searchParameter="filename:logo.png AND date:>now-24h"
!urlscan-search searchType=raw searchParameter="q=meta%3Asearchhit.search.04eb755f-468d-4421-ab86-210a01ee1bdd&datasource=hostnames&search_after="
2. (Deprecated) Submit a URL directly to urlscan.io
Submits a URL to urlscan.io.
This command is deprecated, but will still work if it is used in a playbook.
Base Command
urlscan-submit
Input
| Argument Name | Description | Required |
|---|---|---|
| url | URL to scan | Required |
| timeout | How many seconds to wait to the scan id result. Default is 30 seconds. | Optional |
| public | Will the submission be public or private | Optional |
| useragent | User Agent used to perform scans | Optional |
| scan_visibility | The submission visibility. If specified, overrides the 'public' parameter | Optional |
Context Output
| Path | Description |
|---|---|
| URLScan.URLs | URLs related to the scanned URL |
| URLScan.RelatedIPs | IPs related to the scanned URL |
| URLScan.RelatedASNs | ASNs related to the scanned URL |
| URLScan.Countries | Countries associated with the scanned URL |
| URLScan.relatedhashes | IOCs found for the scanned URL |
| URLScan.Subdomains | Associated subdomains for the url scanned |
| URLScan.ASN | ASN of the URL scanned |
| URLScan.Data | URL of the file found |
| URLScan.Malicious.Vendor | Vendor reporting the malicious indicator for the file |
| URLScan.Malicious.Description | Description of the malicious indicator |
| URLScan.File.Hash | SHA256 of file found |
| URLScan.File.FileName | File name of file found |
| URLScan.File.FileType | File type of the file found |
| URLScan.File.Hostname | URL where the file was found |
| URLScan.Certificates | Certificates found for the scanned URL |
Command Example
!urlscan-submit url=http://www.github.com/
3. Submit a URL (specify using urlscan.io)
Submit a URL to scan and specify the using argument as urlscan.io.
Base Command
url
Input
| Argument Name | Description | Required |
|---|---|---|
| url | URL to scan | Required |
| timeout | How many seconds to wait for the scan ID result. Default is 30 seconds. | Optional |
| public | Whether the submission will be public or private | Optional |
| retries | Number of retries if the API rate limit is reached. This argument is optional, but if you specify this argument, you need to specify the wait argument. | Optional |
| wait | Time interval (in seconds) between retries, if the API rate limit is reached. This argument is optional, but if you specify the retries argument, you need to specify this argument. | Optional |
| useragent | User Agent used to perform scans | Optional |
| scan_visibility | The submission visibility. If specified, overrides the 'public' parameter | Optional |
| use_url_as_name | Whether to use the URL as the screenshot name. Default is false which sets screenshot name to screenshot.png | Optional |
Context Output
| Path | Description |
|---|---|
| URLScan.URLs | URLs related to the scanned URL |
| URLScan.RelatedIPs | IPs related to the URL scanned |
| URLScan.RelatedASNs | ASNs related to the scanned URL |
| URLScan.Countries | Countries associated with the scanned URL |
| URLScan.relatedhashes | IOCs found for the scanned URL |
| URLScan.Subdomains | Associated sub-domains for the scanned URL |
| URLScan.ASN | ASN of the scanned URL |
| URLScan.Data | URL of the file found |
| URLScan.Malicious.Vendor | Vendor reporting the malicious indicator for the file |
| URLScan.Malicious.Description | Description of the malicious indicator |
| URLScan.File.Hash | SHA-256 of file found |
| URLScan.File.FileName | File name of file found |
| URLScan.File.FileType | File type of the file found |
| URLScan.File.Hostname | URL where the file was found |
| URLScan.Certificates | Certificates found for the scanned URL |
| URLScan.RedirectedURLS | Redirected URLs from the URL scanned |
| URLScan.EffectiveURL | Effective URL of the original URL |
| URL.ASN | The URL ASN. |
| URL.FeedRelatedIndicators.value | Indicators that are associated with the URL. |
| URL.FeedRelatedIndicators.type | The type of the indicators that are associated with the URL. |
| URL.Geo.Country | The URL country. |
| URL.ASOwner | The URL AS owner. |
| URL.Tags | Tags that are associated with the URL. |
Command Example
!url url=http://www.github.com/ using="urlscan.io"
Configuration parameters
creds_apikey—scan_visibility— Scan Visibility (required)integrationReliability— Source Reliability (required)country— Scan Countryurl_threshold— URL Threshold. Minimum number of positive results from urlscan.io to consider the URL malicious.useragent— User Agentcreate_relationships— Create relationshipsinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsis_public— Enable public submissions by default.apikey— API Key (only required for scanning URLs)
Commands (7)
-
urlSubmits a URL to scan.
-
urlscan-get-http-transaction-listDeprecatedReturns the HTTP transaction list for the specified URL. Do not use this command in conjunction with the urlscan-get-http-transactions script.
-
urlscan-get-result-pageDeprecatedReturns the results page for the specified UUID.
-
urlscan-poll-uriDeprecatedPolls the urlscan service regarding the results of the specified URI.
-
urlscan-searchSearch for an indicator that is related to former urlscan.io scans.
-
urlscan-submitDeprecatedDeprecated. Use the url command instead.
-
urlscan-submit-url-commandDeprecatedSubmits a URL to retrieve its UUID.
import json import time from threading import Thread import demistomock as demisto # noqa: F401 import pytest from CommonServerPython import * # noqa: F401 from pytest_mock import MockerFixture RETURN_ERROR_TARGET = "UrlScan.return_error" SCAN_URL = "https://urlscan.io/api/v1/scan/" RESULT_URL = "https://urlscan.io/api/v1/result/" @pytest.mark.parametrize("continue_on_blacklisted_urls", [(True), (False)]) def test_continue_on_blacklisted_error_arg(mocker, requests_mock, continue_on_blacklisted_urls): from UrlScan import BLACKLISTED_URL_ERROR_MESSAGES, Client, http_request return_error_mock = mocker.patch(RETURN_ERROR_TARGET) response_json = { "status": 400, "message": "Scan prevented ...", "description": BLACKLISTED_URL_ERROR_MESSAGES[0], } args = {"continue_on_blacklisted_urls": continue_on_blacklisted_urls} data = {"url": "www.test.com"} requests_mock.post(SCAN_URL, status_code=400, json=response_json) mocker.patch.object(demisto, "args", return_value=args) client = Client() response = http_request(client, "POST", "scan/", json=json.dumps(data)) if continue_on_blacklisted_urls: assert return_error_mock.call_count == 0 else: assert response[0].get("is_error") is True assert ("The submitted domain is on our blacklist. For your own safety we did not perform this scan...") in response[ 0 ].get("error_string") def thread_target(): from UrlScan import Client, format_results client = Client() try: format_results(client, "uuid", "") except Exception: pass def test_endless_loop_on_failed_response(requests_mock, mocker): """ Given - Some uuid When - Running format results on it Then - Assert it does not enter an endless loop """ mocker.patch(RETURN_ERROR_TARGET) with open("./test_data/capitalne.json") as f: response_data = json.loads(f.read()) requests_mock.get(RESULT_URL + "uuid", status_code=200, json=response_data) thread = Thread(target=thread_target) thread.start() time.sleep(10) assert not thread.is_alive(), "format_results method have probably entered an endless loop" def test_urlscan_submit_url(requests_mock, mocker): """ Given - Two URLs which are rate limited When - running the !url command Then - Assert the items are scheduled and the metrics are correct. """ import CommonServerPython from UrlScan import Client, urlscan_submit_command response_json = {"is_error": True} args = {"url": "https://something.com,https://somethingelse.com"} requests_mock.post(SCAN_URL, status_code=429, json=response_json, headers={"X-Rate-Limit-Reset-After": "123"}) mocker.patch.object(demisto, "args", return_value=args) mocker.patch.object(CommonServerPython, "is_demisto_version_ge", return_value=True) mocker.patch.object(ScheduledCommand, "raise_error_if_not_supported") client = Client() response = urlscan_submit_command(client=client) scheduled_command = response[0].scheduled_command scheduled_command_args = scheduled_command._args assert scheduled_command_args["polling"] is True assert scheduled_command_args["url"] == ["https://something.com", "https://somethingelse.com"] assert scheduled_command._next_run == "123" assert scheduled_command._items_remaining == 2 metrics = response[1] assert metrics.execution_metrics == [{"Type": "QuotaError", "APICallsCount": 2}] def test_urlscan_search_only_found(mocker: MockerFixture): """ Given: Client, execution metrics and empty command results When: urlscan_search_only is called with a url that has a result Then: - Execution metrics success is incremented by 1 - The command_results is empty """ from UrlScan import Client, urlscan_search_only client = Client() command_results = [] execution_metrics = ExecutionMetrics() url = "http://example.com" mocker.patch( "UrlScan.urlscan_search", return_value={"results": [{"task": {"uuid": "123"}, "page": {"url": "http://example.com"}}]}, ) mocker.patch("UrlScan.format_results") urlscan_search_only(client, url, command_results, execution_metrics) assert execution_metrics.success == 1 assert len(command_results) == 0 def test_urlscan_search_only_not_found(mocker: MockerFixture): """ Given: Client, execution metrics and empty command results When: urlscan_search_only is called with a url that has no result Then: - No results message is added to command_results - Execution metrics is unchanged """ from UrlScan import Client, urlscan_search_only client = Client() command_results = [] execution_metrics = ExecutionMetrics() url = "http://example.com" mocker.patch("UrlScan.urlscan_search", return_value={"results": []}) urlscan_search_only(client, url, command_results, execution_metrics) assert execution_metrics.success == 0 assert len(command_results) == 1 assert command_results[0].readable_output.startswith("No results found for") def test_urlscan_search_only_error(mocker: MockerFixture): """ Given: Client, execution metrics and empty command results When: urlscan_search_only is called with a url that return an error Then: - Error message is added to command_results - Execution metrics general error is incremented by 1 """ from UrlScan import Client, urlscan_search_only client = Client() command_results = [] execution_metrics = ExecutionMetrics() url = "http://example.com" mocker.patch( "UrlScan.urlscan_search", return_value={"is_error": True, "error_string": "Test error"}, ) urlscan_search_only(client, url, command_results, execution_metrics) assert execution_metrics.general_error == 1 assert "Test error" in command_results[0].readable_output def test_format_results_check_lists(mocker): from UrlScan import Client, format_results client = Client() with open("./test_data/capitalne.json") as f: response_data = json.loads(f.read()) mocker.patch("UrlScan.urlscan_submit_request", return_value=(response_data, "", "")) mocker.patch.object(demisto, "results", return_value="") command_results_inputs = mocker.patch("UrlScan.CommandResults") format_results(client, "uuid", "") outputs = command_results_inputs.call_args[1]["outputs"]["URLScan(val.URL && val.URL == obj.URL)"] assert outputs.get("links") == ["http://capitalne.com/home", "http://capitalne.com/about", "https://urlscan.io/"]