Intel471Credentials
Fetches leaked credentials from the Intel471 Credentials API and produces an indicator per credential. While building each indicator the integration also creates an associated incident.
Data Enrichment & Threat Intelligence · Intel471 Feed · Feed
Details
| ID | Intel471Credentials |
|---|---|
| Provider | Intel471 |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.8.0 |
| Docker Image | demisto/py3-tools:1.0.0.11597437 |
| Supported Modules | Agentix XSIAM |
README
Intel471 Credentials
Fetches leaked credentials from the Intel471 Credentials API (/credentials/stream).
The primary command is fetch-indicators: each credential is converted into one Cortex indicator (Email if the login contains @, otherwise Account). While iterating the indicator-creation loop, the integration also creates an associated Cortex incident for the same credential and links it back via the indicator’s relatedIncidents field.
Notes
- On the first run, the integration fetches credentials with
last_updated_tsnewer than the configured “First fetch timestamp” (default: 7 days). - Subsequent runs continue from the stream cursor returned by the API.
Prerequisites
The integration authenticates to the Intel471 Credentials API with HTTP Basic auth (Username = API username, Password = API key). To obtain these credentials:
- Sign in to the Intel471 Developer Portal using your organization SSO account (or sign up if this is your first visit).
- Confirm that your organization has an active subscription that grants access to the Credentials Intelligence product. If it does not, contact your Intel471 account manager to enable it.
- In the portal, open API Keys (under your account menu) and click Create new API key.
- Copy the generated username and API key — the API key is shown only once.
- Use these values in the Username and Password fields of the configuration below.
Configure Intel471 Credentials in Cortex
| Parameter | Description | Required |
|---|---|---|
| Username | HTTP Basic auth credentials for the Intel471 Credentials API — enter your API username and API key. | True |
| Password | HTTP Basic auth credentials for the Intel471 Credentials API — enter your API username and API key. | True |
| Use system proxy settings | When enabled, requests are routed through the system proxy configured on the Cortex engine. | False |
| Trust any certificate (not secure) | When enabled, SSL certificate verification is skipped. Not recommended for production use. | False |
| Fetch indicators | False | |
| First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days) | The time to go back when performing the first fetch. | False |
| Maximum items per fetch | The maximum number of credentials to pull per fetch (each one becomes one indicator and one incident). | False |
| Feed Fetch Interval | How often (in minutes) the integration polls the Intel471 API for new credentials. | False |
| Indicator Reputation | The reputation to apply to indicators from this integration instance. | False |
| Source Reliability | The reliability of the source providing the intelligence data. | True |
| Tags | A comma-separated list of tags. | False |
| Bypass exclusion list | Whether to ignore the exclusion list for indicators from this feed. | False |
| Credential set name | The credential set name to filter results by. | False |
| Credential set id | The credential set ID to filter results by. | False |
| Domain | The credential detection domain to filter results by. | False |
| Affiliation group | The affiliation group to filter results by. Possible values: my_employees, my_customers, third_parties, vip_emails. | False |
| Password strength | The password strength to filter results by. | False |
| Detected malware | The detected info stealer malware family to filter results by (e.g., agent_tesla, Lumma, VIDAR). | False |
| GIRs | A comma-separated list of custom GIRs (General Intelligence Requirements), my_girs or company_pirs, to filter results by. | False |
Commands
You can execute these commands from the 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.
intel471-credentials-get-indicators
Gets a preview of indicators that the feed would pull on the next run (no state is persisted).
Base Command
intel471-credentials-get-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of indicators to return. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Intel471Credentials.Indicators.value | String | The credential login value (email address or account username). |
| Intel471Credentials.Indicators.type | String | The indicator type — Email or Account. |
| Intel471Credentials.Indicators.fields.firstseenbysource | Date | Timestamp when the credential was first observed by Intel471. |
| Intel471Credentials.Indicators.fields.lastseenbysource | Date | Timestamp when the credential was last observed by Intel471. |
| Intel471Credentials.Indicators.fields.tags | Unknown | Aggregated tags — malware families, affiliations, and configured feed tags. |
| Intel471Credentials.Indicators.fields.intel471infostealerantivirussoftware | String | Antivirus software detected on the machine infected by the info stealer. |
| Intel471Credentials.Indicators.fields.intel471infostealercomputerusername | String | Operating-system username logged in on the infected machine. |
| Intel471Credentials.Indicators.fields.intel471infostealerinfectiontimestamp | Date | Timestamp when the info stealer infection was recorded. |
| Intel471Credentials.Indicators.fields.intel471infostealerip | String | IP address of the machine infected by the info stealer. |
| Intel471Credentials.Indicators.fields.intel471infostealerisp | String | Internet service provider associated with the infected machine. |
| Intel471Credentials.Indicators.fields.intel471infostealermachineid | String | Unique identifier fingerprinted by the info stealer for the infected host. |
| Intel471Credentials.Indicators.fields.intel471infostealermalwarefamily | String | Family of info stealer malware that captured the credential. |
| Intel471Credentials.Indicators.fields.intel471infostealermalwareinstallpath | String | Filesystem path where the info stealer malware was installed. |
| Intel471Credentials.Indicators.fields.intel471infostealeros | String | Operating system reported for the machine infected by the info stealer. |
| Intel471Credentials.Indicators.fields.intel471infostealerpcname | String | Hostname (PC name) of the machine infected by the info stealer. |
| Intel471Credentials.Indicators.fields.intel471infostealerscreenshotpath | String | Path to the desktop screenshot captured by the info stealer. |
| Intel471Credentials.Indicators.fields.intel471infostealerversion | String | Version identifier reported by the info stealer malware. |
Command example
!intel471-credentials-get-indicators limit=5
Context Example
{
"Intel471Credentials": {
"Indicators": [
{
"value": "victim@example.com",
"type": "Email",
"fields": {
"firstseenbysource": "2026-06-01T00:00:00Z",
"lastseenbysource": "2026-06-20T00:00:00Z",
"tags": ["lumma", "my_employees"],
"intel471infostealerantivirussoftware": "Defender",
"intel471infostealercomputerusername": "jdoe",
"intel471infostealerinfectiontimestamp": "2026-06-19T12:00:00Z",
"intel471infostealerip": "1.2.3.4, 5.6.7.8",
"intel471infostealerisp": "ExampleISP",
"intel471infostealermachineid": "m-1",
"intel471infostealermalwarefamily": "lumma",
"intel471infostealermalwareinstallpath": "C:/Users/jdoe/AppData/Roaming",
"intel471infostealeros": "Windows 11",
"intel471infostealerpcname": "DESKTOP-XYZ",
"intel471infostealerscreenshotpath": "screens/abc.png",
"intel471infostealerversion": "1.2.3"
}
}
]
}
}
Human Readable Output
Indicators from Intel471 Credentials
Value Type fields victim@example.com firstseenbysource: 2026-06-01T00:00:00Z
lastseenbysource: 2026-06-20T00:00:00Z
tags: lumma, my_employees
intel471infostealermalwarefamily: lumma
intel471infostealerip: 1.2.3.4, 5.6.7.8
intel471infostealeros: Windows 11
intel471infostealerpcname: DESKTOP-XYZ
intel471infostealerversion: 1.2.3
Configuration parameters
credentials— Username (required)proxy— Use system proxy settingsinsecure— Trust any certificate (not secure)feed— Fetch indicatorsfetch_time— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)max_fetch— Maximum items per fetchfeedFetchInterval— Feed Fetch IntervalfeedReputation— Indicator ReputationfeedReliability— Source Reliability (required)tlp_color— Traffic Light Protocol ColorfeedExpirationPolicy—feedExpirationInterval—feedTags— TagsfeedBypassExclusionList— Bypass exclusion listcredential_set_name— Credential set namecredential_set_id— Credential set iddomain— Domainaffiliation_group— Affiliation grouppassword_strength— Password strengthdetected_malware— Detected malwaregirs— GIRs
Commands (1)
-
intel471-credentials-get-indicatorsGets a preview of indicators that the feed would pull on the next run (no state is persisted).
import urllib.parse from typing import Any import urllib3 from CommonServerPython import * urllib3.disable_warnings() INTEGRATION_NAME = "Intel471 Credentials" API_BASE_URL = "https://api.intel471.cloud" FEED_CREDENTIALS_PATH = "/integrations/creds/v1/credentials/stream" FEED_URL_CREDENTIALS = f"{API_BASE_URL}{FEED_CREDENTIALS_PATH}" DEMISTO_VERSION = demisto.demistoVersion() CONTENT_PACK = f"Intel471 Feed/{get_pack_version()!s}" USER_AGENT = f'XSOAR/{DEMISTO_VERSION["version"]}.{DEMISTO_VERSION["buildNumber"]} - {CONTENT_PACK} - {INTEGRATION_NAME}' MAX_PAGE_SIZE = 1000 DEFAULT_MAX_INCIDENTS = 200 REQUEST_TIMEOUT = 60 INCIDENT_TYPE = "Intel471 Leaked Credential" INFO_STEALER_FIELDS = ( "antivirus_software", "computer_username", "infection_ts", "ip", "isp", "machine_id", "malware_family", "malware_install_path", "os", "pc_name", "screenshot_path", "version", ) # Override for keys whose XSOAR cliName can't be derived by stripping underscores. CLI_NAME_OVERRIDES = {"infection_ts": "intel471infostealerinfectiontimestamp"} class Client(BaseClient): """Client for the Intel471 Credentials API.""" def __init__( self, auth: tuple[str, str], verify: bool = True, proxy: bool = False, credential_set_name: str | None = None, credential_set_id: str | None = None, domain: str | None = None, affiliation_group: str | None = None, password_strength: str | None = None, detected_malware: str | None = None, girs: str | None = None, fetch_time: str | None = None, ): super().__init__( base_url=API_BASE_URL, verify=verify, proxy=proxy, headers={"user-agent": USER_AGENT}, auth=auth, timeout=REQUEST_TIMEOUT, ) self.credential_set_name = credential_set_name self.credential_set_id = credential_set_id self.domain = domain self.affiliation_group = affiliation_group self.password_strength = password_strength self.detected_malware = detected_malware self.girs = girs self.fetch_time = fetch_time def fetch_credentials(self, from_ts: str, cursor: str, limit: int) -> tuple[list, str]: """Pull leaked credentials from /credentials/stream. Args: from_ts: ``last_updated_from`` watermark (UNIX millis as str or period like ``7days``). cursor: stream cursor returned by previous call. Empty on the first run. limit: maximum number of credentials to return. Returns: A tuple of (credentials, next_cursor). """ result: list = [] next_cursor: str = cursor params: dict[str, Any] = {} if self.credential_set_name: params["credential_set_name"] = self.credential_set_name if self.credential_set_id: params["credential_set_id"] = self.credential_set_id if self.domain: params["domain"] = self.domain if self.affiliation_group: params["affiliation_group"] = self.affiliation_group if self.password_strength: params["password_strength"] = self.password_strength if self.detected_malware: params["detected_malware"] = self.detected_malware if self.girs: params["girs"] = self.girs params["last_updated_from"] = from_ts if cursor: params["cursor"] = cursor should_continue = True while should_continue: remaining = limit - len(result) if remaining <= 0: break # Cap each request to what's still needed so cursor_next never advances # past items we don't keep — otherwise the next fetch would skip them. params["size"] = str(min(MAX_PAGE_SIZE, remaining)) try: data = self._http_request( method="GET", url_suffix=FEED_CREDENTIALS_PATH, params=params, params_parser=urllib.parse.quote, ) credentials: list = data.get("credentials", []) if credentials: result.extend(credentials) else: should_continue = False returned_cursor = data.get("cursor_next", "") if returned_cursor: next_cursor = returned_cursor params["cursor"] = returned_cursor else: should_continue = False except Exception as err: if result: demisto.error(f"Partial fetch failure: {err}\n{traceback.format_exc()}") break raise return result, next_cursor def test_module(client: Client, *_) -> str: """Verifies API connectivity by issuing a small /credentials/stream request.""" start_date, _end = parse_date_range(client.fetch_time or "1 day", utc=True, to_timestamp=True) client.fetch_credentials(str(start_date), "", 1) return "ok" def _indicator_type_for_login(login: str) -> str: if login and "@" in login: return FeedIndicatorType.Email return FeedIndicatorType.Account def build_indicator(credential: dict[str, Any], feed_tags: list[str] | None = None) -> dict[str, Any]: """Builds an XSOAR indicator dict from a single /credentials/stream record. Returns an empty dict when no usable login value is present. """ data = credential.get("data", {}) or {} login = data.get("credential_login", "") or "" if not login: return {} indicator_type = _indicator_type_for_login(login) activity = credential.get("activity", {}) or {} info_stealer = data.get("info_stealer", {}) or {} malware_families = info_stealer.get("malware_family", []) or [] fields: dict[str, Any] = { "firstseenbysource": activity.get("first_seen_ts", ""), "lastseenbysource": activity.get("last_seen_ts", ""), "tags": [], } if malware_families: fields["tags"].extend(malware_families) affiliations = data.get("affiliations", []) or [] if affiliations: fields["tags"].extend(affiliations) if feed_tags: fields["tags"].extend(feed_tags) for key in INFO_STEALER_FIELDS: raw = info_stealer.get(key) # infection_ts maps to a date-typed indicator field, so keep it as a single ISO string # rather than the comma-joined form used for the multi-value shortText fields. if key == "infection_ts": value = _first_info_stealer_value(raw) else: value = _format_info_stealer_value(raw) if value: cli_name = CLI_NAME_OVERRIDES.get(key, f"intel471infostealer{key.replace('_', '')}") fields[cli_name] = value return { "value": login, "type": indicator_type, "rawJSON": credential, "fields": fields, } def _flatten_info_stealer(info_stealer: dict[str, Any]) -> list[dict[str, str]]: """Builds XSOAR incident labels from the credential's info_stealer payload. The /credentials/stream payload uses ``InfoStealerResponse_Set`` (array per field) — values are joined with commas so they render as a single label string per attribute. """ labels: list[dict[str, str]] = [] for key in INFO_STEALER_FIELDS: raw = info_stealer.get(key) if raw is None or raw == "": continue if isinstance(raw, list): values = [str(v) for v in raw if v not in (None, "")] if not values: continue value = ", ".join(values) else: value = str(raw) labels.append({"type": f"info_stealer.{key}", "value": value}) return labels def _format_info_stealer_value(raw: Any) -> str: if raw is None or raw == "": return "" if isinstance(raw, list): values = [str(v) for v in raw if v not in (None, "")] return ", ".join(values) return str(raw) def _first_info_stealer_value(raw: Any) -> str: if raw is None or raw == "": return "" if isinstance(raw, list): for v in raw: if v not in (None, ""): return str(v) return "" return str(raw) def compose_incident_details(credential: dict[str, Any]) -> str: """Builds a human-readable ``details`` blob for an Intel471 leaked-credential incident. Mirrors ``compose_incident_details`` in the Intel471WatcherAlerts integration so leaked-credential incidents render the same shape of summary as the watcher-alert credential adapter — but extended with the info_stealer + activity fields the ``/credentials/stream`` payload carries. """ data = credential.get("data", {}) or {} activity = credential.get("activity", {}) or {} info_stealer = data.get("info_stealer", {}) or {} password = data.get("password", {}) or {} lines: list[str] = ["Source Object: CREDENTIAL"] login = data.get("credential_login", "") if login: lines.append(f"Credential Login: {login}") detection_domain = data.get("detection_domain", "") if detection_domain: lines.append(f"Detection Domain: {detection_domain}") credential_domain = data.get("credential_domain", "") if credential_domain and credential_domain != detection_domain: lines.append(f"Credential Domain: {credential_domain}") strength = password.get("strength", "") if strength: lines.append(f"Password Strength: {strength}") affiliations = data.get("affiliations", []) or [] if affiliations: lines.append(f"Affiliations: {', '.join(str(a) for a in affiliations)}") first_seen = activity.get("first_seen_ts", "") if first_seen: lines.append(f"First Seen: {first_seen}") last_seen = activity.get("last_seen_ts", "") if last_seen: lines.append(f"Last Seen: {last_seen}") info_stealer_lines: list[str] = [] for key in INFO_STEALER_FIELDS: value = _format_info_stealer_value(info_stealer.get(key)) if value: info_stealer_lines.append(f" {key}: {value}") if info_stealer_lines: lines.append("") lines.append("Info Stealer:") lines.extend(info_stealer_lines) return "\n".join(lines) def build_incident(credential: dict[str, Any]) -> dict[str, Any]: """Builds an XSOAR incident dict from a single /credentials/stream record.""" data = credential.get("data", {}) or {} login = data.get("credential_login", "") or "(unknown login)" domain = data.get("detection_domain", "") or data.get("credential_domain", "") occurred = credential.get("last_updated_ts", "") info_stealer = data.get("info_stealer", {}) or {} name = f"Intel471 Leaked Credential: {login}" if domain: name = f"{name} @ {domain}" incident: dict[str, Any] = { "name": name, "type": INCIDENT_TYPE, "details": compose_incident_details(credential), "rawJSON": json.dumps(credential), } if occurred: incident["occurred"] = occurred labels = _flatten_info_stealer(info_stealer) if labels: incident["labels"] = labels return incident def fetch_indicators_command(client: Client, max_items: int, feed_tags: list[str] | None = None) -> tuple[list, list, dict]: """Fetches leaked credentials and turns each one into an indicator and an incident. Indicators are the primary output; incidents are produced inline within the indicator-creation loop so each credential yields exactly one of each, linked together via the indicator's ``relatedIncidents`` field. Returns: A tuple of (indicators, incidents, next_run). """ last_run = demisto.getLastRun() or {} cursor = last_run.get("cursor", "") from_ts = last_run.get("from_ts", "") if not from_ts: start_date, _end = parse_date_range(client.fetch_time or "7 days", utc=True, to_timestamp=True) from_ts = str(start_date) credentials, next_cursor = client.fetch_credentials(from_ts, cursor, max_items) indicators: list = [] incidents: list = [] for credential in credentials: indicator = build_indicator(credential, feed_tags) if not indicator: continue incident = build_incident(credential) indicator["relatedIncidents"] = [incident["name"]] indicators.append(indicator) incidents.append(incident) next_run = {"cursor": next_cursor or cursor, "from_ts": from_ts} return indicators, incidents, next_run def get_indicators_command(client: Client, args: dict[str, str], feed_tags: list[str] | None = None) -> CommandResults: """War-room wrapper that returns the pending indicators without persisting state.""" limit = arg_to_number(args.get("limit")) or 50 start_date, _end = parse_date_range(client.fetch_time or "7 days", utc=True, to_timestamp=True) credentials, _cursor = client.fetch_credentials(str(start_date), "", limit) indicators: list = [] for credential in credentials: indicator = build_indicator(credential, feed_tags) if indicator: indicators.append(indicator) hr = [{"Value": i.get("value"), "Type": i.get("type"), "fields": i.get("fields")} for i in indicators] human_readable = tableToMarkdown( "Indicators from Intel471 Credentials:", hr, headers=["Value", "Type", "fields"], removeNull=True ) return CommandResults( readable_output=human_readable, outputs_prefix="Intel471Credentials.Indicators", outputs_key_field="value", outputs=indicators, raw_response=indicators, ) def main(): args = demisto.args() params = demisto.params() verify = not params.get("insecure", False) proxy = params.get("proxy", False) fetch_time = params.get("fetch_time") credential_set_name = params.get("credential_set_name") credential_set_id = params.get("credential_set_id") domain = params.get("domain") affiliation_group = params.get("affiliation_group") password_strength = params.get("password_strength") detected_malware = params.get("detected_malware") girs = params.get("girs") feed_tags = argToList(params.get("feedTags")) max_items = arg_to_number(params.get("max_fetch")) or DEFAULT_MAX_INCIDENTS credentials = params.get("credentials", {}) if not credentials: raise DemistoException("Integration credentials not entered.") auth = (credentials.get("identifier", ""), credentials.get("password", "")) command = demisto.command() demisto.info(f"Command being called is {command}") try: client = Client( auth=auth, verify=verify, proxy=proxy, credential_set_name=credential_set_name, credential_set_id=credential_set_id, domain=domain, affiliation_group=affiliation_group, password_strength=password_strength, detected_malware=detected_malware, girs=girs, fetch_time=fetch_time, ) if command == "test-module": return_results(test_module(client, params)) elif command == "fetch-indicators": indicators, incidents, next_run = fetch_indicators_command(client, max_items, feed_tags) for iter_ in batch(indicators, batch_size=2000): demisto.createIndicators(iter_) for iter_ in batch(incidents, batch_size=2000): demisto.createIncidents(iter_) demisto.setLastRun(next_run) elif command == "intel471-credentials-get-indicators": return_results(get_indicators_command(client, args, feed_tags)) else: raise NotImplementedError(f"Command {command} is not implemented.") except Exception as err: err_msg = f"Error in {INTEGRATION_NAME} Integration. [{err}]" return_error(err_msg, error=err) if __name__ in ["__main__", "builtin", "builtins"]: main()