WildFire-v2

Perform malware dynamic analysis.

Forensics & Malware Analysis · WildFire by Palo Alto Networks

Details

IDWildFire-v2
ProviderPalo Alto Networks
CategoryForensics & Malware Analysis
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10404775
Supported ModulesAgentix Cortex Cloud Cloud Runtime Security XSIAM EDR

README

Use the Palo Alto Networks Wildfire integration to automatically identify unknown threats and stop attackers in their tracks by performing malware dynamic analysis.

Palo Alto Networks WildFire v2 Playbooks

  1. WildFire - Detonate File
  2. Detonate URL - WildFire v2.1

Use Cases

  1. Send a file sample to WildFire.
  2. Upload a file hosted on a website to WildFire.
  3. Submit a webpage to WildFire.
  4. Get a report regarding the sent samples using file hash.
  5. Get sample file from WildFire.
  6. Get verdict regarding multiple hashes (up to 500) using the wildfire-get-verdicts command.

Supported File Types

For a list of the supported file types, see here.

Configure WildFire v2 on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for WildFire-v2.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    Server base URL (see WildFire Server URLs below)   True
    API Key   True
    API Key Type API Key product name False
    Source Reliability Reliability of the source providing the intelligence data. True
    Trust any certificate (not secure)   False
    Use system proxy settings   False
    Return warning entry for unsupported file types   False
    Create relationships Create relationships between indicators as part of Enrichment. False
  4. Click Test to validate the URLs, token, and connection.

WildFire Server URLs

Use the appropriate server URL in the Server base URL parameter based on your region or cloud environment:

Region Server URL
Global (default) https://wildfire.paloaltonetworks.com
US Gov Cloud / FedRAMP Moderate https://pubsec-cloud.wildfire.paloaltonetworks.com
US Gov Cloud / FedRAMP High https://gov-cloud.wildfire.paloaltonetworks.com
EU https://eu.wildfire.paloaltonetworks.com
Japan https://jp.wildfire.paloaltonetworks.com

For on-premise WildFire appliances, use the appliance IP or hostname with the /publicapi path (e.g., https://192.168.0.1/publicapi).

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.

file


Retrieve results for a file hash using WildFire

Base Command

file

Input

Argument Name Description Required
file File hash to check. Optional
md5 MD5 hash to check. Optional
sha256 SHA256 hash to check. Optional

Context Output

Path Type Description
File.Name string Name of the file.
File.Type string File type, for example: “PE”.
File.Size string Size of the file.
File.MD5 string MD5 hash of the file.
File.SHA1 string SHA1 hash of the file.
File.SHA256 string SHA256 hash of the file.
File.Malicious.Vendor string For malicious files, the vendor that made the decision.
File.DigitalSignature.Publisher string  
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string The vendor used to calculate the score.
DBotScore.Score number The actual score.
WildFire.Report.Status string The status of the submission.
WildFire.Report.SHA256 string SHA256 hash of the submission.
InfoFile.EntryID Unknown The EntryID of the report file.
InfoFile.Extension string Extension of the report file.
InfoFile.Name string Name of the report file.
InfoFile.Info string Details of the report file.
InfoFile.Size number Size of the report file.
InfoFile.Type string The report file type.
File.FeedRelatedIndicators.value String Indicators that are associated with the file.
File.FeedRelatedIndicators.type String The type of the indicators that are associated with the file.
File.Tags String Tags that are associated with the file.
File.Behavior.details String File behavior details.
File.Behavior.action String File behavior action.

Command Example

!file file=735bcfa56930d824f9091188eeaac2a1d68bc64a21f90a49c5ff836ed6ea723f

Human Readable Output

WildFire File Report

FileType MD5 SHA256 Size Status
JScript ccdb1053f56a2d297906746bc720ef2a 735bcfa56930d824f9091188eeaac2a1d68bc64a21f90a49c5ff836ed6ea723f 12 Completed

wildfire-upload


Uploads a file to WildFire for analysis.

Base Command

wildfire-upload

Input

Argument Name Description Required
upload ID of the entry containing the file to upload. Optional
polling Whether to use Cortex XSOAR’s built-in polling to retrieve the result when it’s ready. Possible values are: true, false. Optional
interval_in_seconds Interval in seconds between each poll. Default is 60. Optional
md5 Used for the inner polling flow. For uploading a file, use the ‘upload’ argument instead. Optional
format The type of structured report (XML or PDF) to request. Only relevant when polling=true. Possible values are: xml, pdf. Default is pdf. Optional
verbose Whether to receive extended information from WildFire. Only relevant when polling=true. Possible values are: true, false. Default is false. Optional
extended_data If set to “true”, the report will return extended data which includes the additional outputs. Possible values are: true, false. Optional

Context Output

Path Type Description
WildFire.Report.MD5 string MD5 hash of the submission.
WildFire.Report.SHA256 string SHA256 hash of the submission.
WildFire.Report.FileType string The submission type.
WildFire.Report.Size number The size of the submission.
WildFire.Report.Status string The status of the submission.
File.Name string Name of the file.
File.Type string File type, for example: “PE”.
File.Size number Size of the file.
File.MD5 string MD5 hash of the file.
File.SHA1 string SHA1 hash of the file.
File.SHA256 string SHA256 hash of the file.
File.Malicious.Vendor string For malicious files, the vendor that made the decision.
File.DigitalSignature.Publisher string The entity that signed the file for authenticity purposes.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string Vendor used to calculate the score.
DBotScore.Score number The actual score.
InfoFile.EntryID string The EntryID of the report file.
InfoFile.Extension string The extension of the report file.
InfoFile.Name string The name of the report file.
InfoFile.Info string Details of the report file.
InfoFile.Size number The size of the report file.
InfoFile.Type string The report file type.
WildFire.Report.NetworkInfo.URL.Host string Submission related hosts
WildFire.Report.NetworkInfo.URL.Method string Submission related method
WildFire.Report.NetworkInfo.URL.URI string Submission related uri
WildFire.Report.NetworkInfo.URL.UserAgent string Submission related user agent
WildFire.Report.NetworkInfo.UDP.IP string Submission related IPs, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.Port string Submission related ports, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.JA3 string Submission related JA3s, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.JA3S string Submission related JA3Ss, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.Country string Submission related Countries, in UDP protocol.
WildFire.Report.NetworkInfo.TCP.IP string Submission related IPs, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.JA3 string Submission related JA3s, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.JA3S string Submission related JA3Ss, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.Country string Submission related Countries, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.Port string Submission related ports, in TCP protocol.
WildFire.Report.NetworkInfo.DNS.Query string Submission DNS queries.
WildFire.Report.NetworkInfo.DNS.Response string Submission DNS responses.
WildFire.Report.NetworkInfo.DNS.Type string Submission DNS Types.
WildFire.Report.Evidence.md5 string Submission evidence MD5 hash.
WildFire.Report.Evidence.Text string Submission evidence text.
WildFire.Report.detection_reasons.description string Reason for the detection verdict.
WildFire.Report.detection_reasons.name string Name of the detection.
WildFire.Report.detection_reasons.type string Type of the detection.
WildFire.Report.detection_reasons.verdict string Verdict of the detection.
WildFire.Report.detection_reasons.artifacts unknown Artifacts of the detection reasons.
WildFire.Report.iocs unknown Associated IOCs.
WildFire.Report.verdict string The verdict of the report.
WildFire.Report.Platform string The Platform of the report
WildFire.Report.Software string The Software of the report
WildFire.Report.ProcessList.Service string The process service
WildFire.Report.ProcessList.ProcessCommand string The process command
WildFire.Report.ProcessList.ProcessName string The process name
WildFire.Report.ProcessList.ProcessPid string The process pid
WildFire.Report.ProcessList.ProcessFile string Lists files that started a child processes, the process name, and the action the process performed.
WildFire.Report.ProcessTree.ProcessName string The process name
WildFire.Report.ProcessTree.ProcessPid string The process pid
WildFire.Report.ProcessTree.ProcessText string The action the process performed.
WildFire.Report.ProcessTree.Process.ChildName string The child process name
WildFire.Report.ProcessTree.Process.ChildPid string The child process pid
WildFire.Report.ProcessTree.Process.ChildText string The action the child process performed.
WildFire.Report.ExtractedURL.URL string The extracted url
WildFire.Report.ExtractedURL.Verdict string The extracted verdict
WildFire.Report.Summary.Text string The summary of the report
WildFire.Report.Summary.Details string The details summary of the report
WildFire.Report.Summary.Behavior string The behavior summary of the report
WildFire.Report.ELF.ShellCommands string The shell commands

Command Example

!wildfire-upload upload=294@675f238c-ed75-4cae-83d2-02b6b820168b

Human Readable Output

WildFire Upload File

FileType MD5 SHA256 Size Status
Jscript for WSH ccdb1053f56a2d297906746bc720ef2a 735bcfa56930d824f9091188eeaac2a1d68bc64a21f90a49c5ff836ed6ea723f 12 Pending

wildfire-upload-file-url


Uploads the URL of a remote file to WildFire for analysis.

Base Command

wildfire-upload-file-url

Input

Argument Name Description Required
upload URL of the remote file to upload. Optional
url Used for the inner polling flow. For uploading a URL, use the ‘upload’ argument instead. Optional
polling Whether to use Cortex XSOAR’s built-in polling to retrieve the result when it’s ready. Possible values are: true, false. Optional
interval_in_seconds Interval in seconds between each poll. Default is 60. Optional
format The type of structured report (XML or PDF) to request. Only relevant when polling=true. Possible values are: xml, pdf. Default is pdf. Optional
verbose Whether to receive extended information from WildFire. Only relevant when polling=true. Possible values are: true, false. Default is false. Optional
extended_data If set to “true”, the report will return extended data which includes the additional outputs. Possible values are: true, false. Optional

Context Output

Path Type Description
WildFire.Report.MD5 string MD5 hash of the submission.
WildFire.Report.SHA256 string SHA256 hash of the submission.
WildFire.Report.Status string The status of the submission.
WildFire.Report.URL string URL of the submission.
File.Name string Name of the file.
File.Type string File type, for example: “PE”.
File.Size number Size of the file.
File.MD5 string MD5 hash of the file.
File.SHA1 string SHA1 hash of the file.
File.SHA256 string SHA256 hash of the file.
File.Malicious.Vendor string For malicious files, the vendor that made the decision.
File.DigitalSignature.Publisher string The entity that signed the file for authenticity purposes.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string Vendor used to calculate the score.
DBotScore.Score number The actual score.
InfoFile.EntryID string The EntryID of the report file.
InfoFile.Extension string The extension of the report file.
InfoFile.Name string The name of the report file.
InfoFile.Info string Details of the report file.
InfoFile.Size number The size of the report file.
InfoFile.Type string The report file type.
WildFire.Report.NetworkInfo.URL.Host string Submission related hosts
WildFire.Report.NetworkInfo.URL.Method string Submission related method
WildFire.Report.NetworkInfo.URL.URI string Submission related uri
WildFire.Report.NetworkInfo.URL.UserAgent string Submission related user agent
WildFire.Report.NetworkInfo.UDP.IP string Submission related IPs, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.Port string Submission related ports, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.JA3 string Submission related JA3s, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.JA3S string Submission related JA3Ss, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.Country string Submission related Countries, in UDP protocol.
WildFire.Report.NetworkInfo.TCP.IP string Submission related IPs, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.JA3 string Submission related JA3s, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.JA3S string Submission related JA3Ss, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.Country string Submission related Countries, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.Port string Submission related ports, in TCP protocol.
WildFire.Report.NetworkInfo.DNS.Query string Submission DNS queries.
WildFire.Report.NetworkInfo.DNS.Response string Submission DNS responses.
WildFire.Report.NetworkInfo.DNS.Type string Submission DNS Types.
WildFire.Report.Evidence.md5 string Submission evidence MD5 hash.
WildFire.Report.Evidence.Text string Submission evidence text.
WildFire.Report.detection_reasons.description string Reason for the detection verdict.
WildFire.Report.detection_reasons.name string Name of the detection.
WildFire.Report.detection_reasons.type string Type of the detection.
WildFire.Report.detection_reasons.verdict string Verdict of the detection.
WildFire.Report.detection_reasons.artifacts unknown Artifacts of the detection reasons.
WildFire.Report.iocs unknown Associated IOCs.
WildFire.Report.verdict string The verdict of the report.
WildFire.Report.Platform string The Platform of the report
WildFire.Report.Software string The Software of the report
WildFire.Report.ProcessList.Service string The process service
WildFire.Report.ProcessList.ProcessCommand string The process command
WildFire.Report.ProcessList.ProcessName string The process name
WildFire.Report.ProcessList.ProcessPid string The process pid
WildFire.Report.ProcessList.ProcessFile string Lists files that started a child processes, the process name, and the action the process performed.
WildFire.Report.ProcessTree.ProcessName string The process name
WildFire.Report.ProcessTree.ProcessPid string The process pid
WildFire.Report.ProcessTree.ProcessText string The action the process performed.
WildFire.Report.ProcessTree.Process.ChildName string The child process name
WildFire.Report.ProcessTree.Process.ChildPid string The child process pid
WildFire.Report.ProcessTree.Process.ChildText string The action the child process performed.
WildFire.Report.ExtractedURL.URL string The extracted url
WildFire.Report.ExtractedURL.Verdict string The extracted verdict
WildFire.Report.Summary.Text string The summary of the report
WildFire.Report.Summary.Details string The details summary of the report
WildFire.Report.Summary.Behavior string The behavior summary of the report
WildFire.Report.ELF.ShellCommands string The shell commands

Command Example

!wildfire-upload-file-url upload=http://www.software995.net/bin/pdf995s.exe

Human Readable Output

WildFire Upload File URL

FileType MD5 SHA256 Size Status URL
PE32 executable 891b77e864c88881ea98be867e74177f 555092d994b8838b8fa18d59df4fdb26289d146e071e831fcf0c6851b5fb04f8 5958304 Pending http://www.software995.net/bin/pdf995s.exe

wildfire-report


Retrieves results for a file hash using WildFire.

Base Command

wildfire-report

Input

Argument Name Description Required
md5 MD5 hash to check. Optional
sha256 SHA256 hash to check. Optional
hash Deprecated. Use the sha256 argument instead. Optional
format The type of structured report (MAEC, XML or PDF) to request. Possible values are: maec, xml, pdf. Default is pdf. Optional
verbose Receive extended information from WildFire. Possible values are: true, false. Default is false. Optional
url Retrieves results for a URL using WildFire. The report format is in JSON. Optional
extended_data If set to “true”, the report will return extended data which includes the additional outputs. Possible values are: true, false. Optional

Context Output

Path Type Description
File.Name string Name of the file.
File.Type string File type, for example: “PE”
File.Size number Size of the file.
File.MD5 string MD5 hash of the file.
File.SHA1 string SHA1 hash of the file.
File.SHA256 string SHA256 hash of the file.
File.Malicious.Vendor string For malicious files, the vendor that made the decision.
File.DigitalSignature.Publisher string The entity that signed the file for authenticity purposes.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string Vendor used to calculate the score.
DBotScore.Score number The actual score.
WildFire.Report.Status string The status of the submission.
WildFire.Report.SHA256 string SHA256 hash of the submission.
InfoFile.EntryID string The EntryID of the report file.
InfoFile.Extension string The extension of the report file.
InfoFile.Name string The name of the report file.
InfoFile.Info string Details of the report file.
InfoFile.Size number The size of the report file.
InfoFile.Type string The report file type.
WildFire.Report.NetworkInfo.URL.Host string Submission related hosts
WildFire.Report.NetworkInfo.URL.Method string Submission related method
WildFire.Report.NetworkInfo.URL.URI string Submission related uri
WildFire.Report.NetworkInfo.URL.UserAgent string Submission related user agent
WildFire.Report.NetworkInfo.UDP.IP string Submission related IPs, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.Port string Submission related ports, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.JA3 string Submission related JA3s, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.JA3S string Submission related JA3Ss, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.Country string Submission related Countries, in UDP protocol.
WildFire.Report.NetworkInfo.TCP.IP string Submission related IPs, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.JA3 string Submission related JA3s, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.JA3S string Submission related JA3Ss, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.Country string Submission related Countries, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.Port string Submission related ports, in TCP protocol.
WildFire.Report.NetworkInfo.DNS.Query string Submission DNS queries.
WildFire.Report.NetworkInfo.DNS.Response string Submission DNS responses.
WildFire.Report.NetworkInfo.DNS.Type string Submission DNS Types.
WildFire.Report.Evidence.md5 string Submission evidence MD5 hash.
WildFire.Report.Evidence.Text string Submission evidence text.
WildFire.Report.detection_reasons.description string Reason for the detection verdict.
WildFire.Report.detection_reasons.name string Name of the detection.
WildFire.Report.detection_reasons.type string Type of the detection.
WildFire.Report.detection_reasons.verdict string Verdict of the detection.
WildFire.Report.detection_reasons.artifacts unknown Artifacts of the detection reasons.
WildFire.Report.iocs unknown Associated IOCs.
WildFire.Report.verdict string The verdict of the report.
WildFire.Report.Platform string The Platform of the report
WildFire.Report.Software string The Software of the report
WildFire.Report.ProcessList.Service string The process service
WildFire.Report.ProcessList.ProcessCommand string The process command
WildFire.Report.ProcessList.ProcessName string The process name
WildFire.Report.ProcessList.ProcessPid string The process pid
WildFire.Report.ProcessList.ProcessFile string Lists files that started a child processes, the process name, and the action the process performed.
WildFire.Report.ProcessTree.ProcessName string The process name
WildFire.Report.ProcessTree.ProcessPid string The process pid
WildFire.Report.ProcessTree.ProcessText string The action the process performed.
WildFire.Report.ProcessTree.Process.ChildName string The child process name
WildFire.Report.ProcessTree.Process.ChildPid string The child process pid
WildFire.Report.ProcessTree.Process.ChildText string The action the child process performed.
WildFire.Report.ExtractedURL.URL string The extracted url
WildFire.Report.ExtractedURL.Verdict string The extracted verdict
WildFire.Report.Summary.Text string The summary of the report
WildFire.Report.Summary.Details string The details summary of the report
WildFire.Report.Summary.Behavior string The behavior summary of the report
WildFire.Report.ELF.ShellCommands string The shell commands
WildFire.Report.maec_report string MAEC report output

Command Example

!wildfire-report url=https://www.paloaltonetworks.com

Human Readable Output

Wildfire URL report for https://www.paloaltonetworks.com

sha256 type verdict
288cd35401e334a2defc0b428d709f58d4ea28c8e9c6e47fdba88da2d6bc88a7 wf-report benign

wildfire-get-verdict


Returns a verdict for a hash.

Base Command

wildfire-get-verdict

Input

Argument Name Description Required
hash Comma-separated list of hashes to get the verdict for. Optional
url The URL to get the verdict for. Optional

Context Output

Path Type Description
WildFire.Verdicts.MD5 string MD5 hash of the file.
WildFire.Verdicts.SHA256 string SHA256 hash of the file.
WildFire.Verdicts.Verdict number Verdict of the file.
WildFire.Verdicts.VerdictDescription string Description of the file verdict.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string Vendor used to calculate the score.
DBotScore.Score number The actual score.
WildFire.Verdicts.AnalysisTime date Verdict analysis time.
WildFire.Verdicts.URL string The URL of the web page.
WildFire.Verdicts.Valid string Is the URL valid.

Command Example

!wildfire-get-verdict hash=afe6b95ad95bc689c356f34ec8d9094c495e4af57c932ac413b65ef132063acc

Human Readable Output

WildFire Verdict

MD5 SHA256 Verdict VerdictDescription
0e4e3c2d84a9bc726a50b3c91346fbb1 afe6b95ad95bc689c356f34ec8d9094c495e4af57c932ac413b65ef132063acc 1 malware

wildfire-get-verdicts


Returns a verdict regarding multiple hashes, stored in a TXT file or given as list.

Base Command

wildfire-get-verdicts

Input

Argument Name Description Required
EntryID EntryID of the text file that contains multiple hashes. Limit is 500 hashes. Optional
hash_list A comma-separated list of hashes to get verdicts for. Optional

Context Output

Path Type Description
WildFire.Verdicts.MD5 string MD5 hash of the file.
WildFire.Verdicts.SHA256 string SHA256 hash of the file.
WildFire.Verdicts.Verdict number Verdict of the file.
WildFire.Verdicts.VerdictDescription string Description of the file verdict.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string Vendor used to calculate the score.
DBotScore.Score number The actual score.

wildfire-upload-url


Uploads a URL of a webpage to WildFire for analysis.

Notice: Submitting indicators using this command might make the indicator data publicly available. See the vendor’s documentation for more details.

Base Command

wildfire-upload-url

Input

Argument Name Description Required
upload URL to submit to WildFire. Optional
url Used for the inner polling flow. For uploading a URL, use the ‘upload’ argument instead. Optional
polling Whether to use Cortex XSOAR’s built-in polling to retrieve the result when it’s ready. Possible values are: true, false. Optional
interval_in_seconds Interval in seconds between each poll. Default is 60. Optional
format The type of structured report (XML or PDF) to request. Only relevant when polling=true. Possible values are: xml, pdf. Default is pdf. Optional
verbose Whether to receive extended information from WildFire. Only relevant when polling=true. Possible values are: true, false. Default is false. Optional
extended_data If set to “true”, the report will return extended data which includes the additional outputs. Possible values are: true, false. Optional

Context Output

Path Type Description
WildFire.Report.MD5 string MD5 of the submission.
WildFire.Report.SHA256 string SHA256 of the submission.
WildFire.Report.Status string The status of the submission.
WildFire.Report.URL string URL of the submission.
File.Name string Name of the file.
File.Type string File type, for example: “PE”.
File.Size number Size of the file.
File.MD5 string MD5 hash of the file.
File.SHA1 string SHA1 hash of the file.
File.SHA256 string SHA256 hash of the file.
File.Malicious.Vendor string For malicious files, the vendor that made the decision.
File.DigitalSignature.Publisher string The entity that signed the file for authenticity purposes.
DBotScore.Indicator string The indicator that was tested.
DBotScore.Type string The indicator type.
DBotScore.Vendor string Vendor used to calculate the score.
DBotScore.Score number The actual score.
InfoFile.EntryID string The EntryID of the report file.
InfoFile.Extension string The extension of the report file.
InfoFile.Name string The name of the report file.
InfoFile.Info string Details of the report file.
InfoFile.Size number The size of the report file.
InfoFile.Type string The report file type.
WildFire.Report.NetworkInfo.URL.Host string Submission related hosts
WildFire.Report.NetworkInfo.URL.Method string Submission related method
WildFire.Report.NetworkInfo.URL.URI string Submission related uri
WildFire.Report.NetworkInfo.URL.UserAgent string Submission related user agent
WildFire.Report.NetworkInfo.UDP.IP string Submission related IPs, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.Port string Submission related ports, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.JA3 string Submission related JA3s, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.JA3S string Submission related JA3Ss, in UDP protocol.
WildFire.Report.NetworkInfo.UDP.Country string Submission related Countries, in UDP protocol.
WildFire.Report.NetworkInfo.TCP.IP string Submission related IPs, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.JA3 string Submission related JA3s, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.JA3S string Submission related JA3Ss, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.Country string Submission related Countries, in TCP protocol.
WildFire.Report.NetworkInfo.TCP.Port string Submission related ports, in TCP protocol.
WildFire.Report.NetworkInfo.DNS.Query string Submission DNS queries.
WildFire.Report.NetworkInfo.DNS.Response string Submission DNS responses.
WildFire.Report.NetworkInfo.DNS.Type string Submission DNS Types.
WildFire.Report.Evidence.md5 string Submission evidence MD5 hash.
WildFire.Report.Evidence.Text string Submission evidence text.
WildFire.Report.detection_reasons.description string Reason for the detection verdict.
WildFire.Report.detection_reasons.name string Name of the detection.
WildFire.Report.detection_reasons.type string Type of the detection.
WildFire.Report.detection_reasons.verdict string Verdict of the detection.
WildFire.Report.detection_reasons.artifacts unknown Artifacts of the detection reasons.
WildFire.Report.iocs unknown Associated IOCs.
WildFire.Report.verdict string The verdict of the report.
WildFire.Report.Platform string The Platform of the report
WildFire.Report.Software string The Software of the report
WildFire.Report.ProcessList.Service string The process service
WildFire.Report.ProcessList.ProcessCommand string The process command
WildFire.Report.ProcessList.ProcessName string The process name
WildFire.Report.ProcessList.ProcessPid string The process pid
WildFire.Report.ProcessList.ProcessFile string Lists files that started a child processes, the process name, and the action the process performed.
WildFire.Report.ProcessTree.ProcessName string The process name
WildFire.Report.ProcessTree.ProcessPid string The process pid
WildFire.Report.ProcessTree.ProcessText string The action the process performed.
WildFire.Report.ProcessTree.Process.ChildName string The child process name
WildFire.Report.ProcessTree.Process.ChildPid string The child process pid
WildFire.Report.ProcessTree.Process.ChildText string The action the child process performed.
WildFire.Report.ExtractedURL.URL string The extracted url
WildFire.Report.ExtractedURL.Verdict string The extracted verdict
WildFire.Report.Summary.Text string The summary of the report
WildFire.Report.Summary.Details string The details summary of the report
WildFire.Report.Summary.Behavior string The behavior summary of the report
WildFire.Report.ELF.ShellCommands string The shell commands

Command Example

!wildfire-upload-url upload=https://www.paloaltonetworks.com

Human Readable Output

WildFire Upload URL

MD5 SHA256 Status URL
67632f32e6af123aa8ffd1fe8765a783 c51a8231d1be07a2545ac99e86a25c5d68f88380b7ebf7ac91501661e6d678bb Pending https://www.paloaltonetworks.com

wildfire-get-sample


Retrieves a sample.

Base Command

wildfire-get-sample

Input

Argument Name Description Required
md5 MD5 hash of the sample. Optional
sha256 SHA256 hash of the sample. Optional

Context Output

There is no context output for this command.

Command Example

!wildfire-get-sample sha256=afe6b95ad95bc689c356f34ec8d9094c495e4af57c932ac413b65ef132063acc

Human Readable Output

There is no human-readable output for this command.

wildfire-get-url-webartifacts


Get web artifacts for a URL webpage. An empty tgz will be returned, no matter what the verdict, or even if the URL is malformed.

Notice: Submitting indicators using this command might make the indicator data publicly available. See the vendor’s documentation for more details.

Base Command

wildfire-get-url-webartifacts

Input

Argument Name Description Required
url URL of the webpage. Required
types Whether to download as screenshots or as downloadable files. If not specified, both will be downloaded. Possible values are: download_files, screenshot. Optional
screenshot_inline Whether to extract screenshot image from tgz to warroom. Only applies to types=screenshot. Possible values are: true, false. Optional

Context Output

Path Type Description
InfoFile.EntryID String The EntryID of the web artifacts.
InfoFile.Extension string Extension of the web artifacts.
InfoFile.Name string Name of the web artifacts.
InfoFile.Info string Details of the web artifacts.
InfoFile.Size number Size of the web artifacts.
InfoFile.Type string The web artifacts file type.

Command Example

!wildfire-get-url-webartifacts url=http://royalmail-login.com

Human Readable Output

There is no human-readable output for this command.

Configuration parameters

  • server — Server base URL (e.g., https://192.168.0.1/publicapi) (required)
  • credentials
  • credentials_source — API Key Type
  • integrationReliability — Source Reliability
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • suppress_file_type_error — Return warning entry for unsupported file types
  • token — API Key (Deprecated)
  • create_relationships — Create relationships

Commands (9)

  • file

    Retrieve results for a file hash using WildFire.

  • wildfire-get-sample

    Retrieves a sample.

  • wildfire-get-url-webartifacts

    Get web artifacts for a URL webpage. An empty tgz will be returned, no matter what the verdict is, or even if the URL is malformed.

  • wildfire-get-verdict

    Returns a verdict for a hash.

  • wildfire-get-verdicts

    Returns a verdict regarding multiple hashes, stored in a TXT file or given as a list.

  • wildfire-report

    Retrieves results for a file hash using WildFire.

  • wildfire-upload

    Uploads a file to WildFire for analysis.

  • wildfire-upload-file-url

    Uploads the URL of a remote file to WildFire for analysis.

  • wildfire-upload-url

    Uploads a URL of a webpage to WildFire for analysis.

import contextlib
import io
import os
import shutil
import tarfile
from collections.abc import Callable
from traceback import format_exc

import demistomock as demisto  # noqa: F401
import urllib3
from CommonServerPython import *  # noqa: F401

# Disable insecure warnings
urllib3.disable_warnings()

""" GLOBALS/PARAMS """
BRAND = "WildFire-v2"
INTEGRATION_NAME = "Wildfire"

PARAMS = demisto.params()
URL = PARAMS.get("server")
USE_SSL = not PARAMS.get("insecure", False)
FILE_TYPE_SUPPRESS_ERROR = PARAMS.get("suppress_file_type_error")
RELIABILITY = PARAMS.get("integrationReliability", DBotScoreReliability.B) or DBotScoreReliability.B
CREATE_RELATIONSHIPS = argToBoolean(PARAMS.get("create_relationships", "true"))
DEFAULT_HEADERS = {"Content-Type": "application/x-www-form-urlencoded"}
WILDFIRE_REPORT_DT_FILE = (
    "WildFire.Report(val.SHA256 && val.SHA256 == obj.SHA256 || val.MD5 && val.MD5 == obj.MD5 || val.URL && val.URL == obj.URL)"
)

# update the default headers with the correct agent version based on the selection in the instance config
API_KEY_SOURCE = PARAMS.get("credentials_source")
AGENT_VALUE = None
BODY_DICT: dict = {}
PARAMS_DICT: dict = {}
TOKEN = None

if URL and not URL.endswith("/publicapi"):
    if URL[-1] != "/":
        URL += "/"
    URL += "publicapi"

URL_DICT = {
    "verdict": "/get/verdict",
    "verdicts": "/get/verdicts",
    "change_verdict": "/submit/local-verdict-change",
    "upload_file": "/submit/file",
    "upload_url": "/submit/link",
    "upload_file_url": "/submit/url",
    "report": "/get/report",
    "sample": "/get/sample",
    "webartifacts": "/get/webartifacts",
}

ERROR_DICT = {
    "401": "Unauthorized, API key invalid",
    "404": "Not Found, The report was not found",
    "405": "Method Not Allowed, Method other than POST used",
    "413": "Request Entity Too Large, Sample file size over max limit",
    "415": "Unsupported Media Type",
    "418": "Unsupported File Type Sample, file type is not supported",
    "419": "Request quota exceeded",
    "420": "Insufficient arguments",
    "421": "Invalid arguments",
    "500": "Internal error",
    "502": "Bad Gateway",
    "513": "File upload failed. This may happen for unsupported files such as empty files.",
}

VERDICTS_DICT = {
    "0": "benign",
    "1": "malware",
    "2": "grayware",
    "4": "phishing",
    "5": "c2",
    "-100": "pending, the sample exists, but there is currently no verdict",
    "-101": "error",
    "-102": "unknown, cannot find sample record in the database",
    "-103": "invalid hash value",
    "-104": "flawed submission, please re-submit the file",
}

VERDICTS_TO_DBOTSCORE = {
    "0": 1,
    "1": 3,
    "2": 2,
    "4": 3,
    "5": 3,
    "-100": 0,
    "-101": 0,
    "-102": 0,
    "-103": 0,
    "-104": 0,
}

VERDICTS_TO_CHANGE_DICT = {"benign": "0", "malware": "1", "grayware": "2", "phishing": "3"}

RELATIONSHIPS_TYPE = {
    "file": FeedIndicatorType.File,
    "url": FeedIndicatorType.URL,
    "domain": FeedIndicatorType.Domain,
    "ip": FeedIndicatorType.IP,
}

""" HELPER FUNCTIONS """


class NotFoundError(Exception):
    """Report or File not found."""

    def __init__(self, *args):  # real signature unknown
        pass


def http_request(
    url: str,
    method: str,
    headers: dict = None,
    body=None,
    params=None,
    files=None,
    resp_type: str = "xml",
    return_raw: bool = False,
    ok_codes: list = None,
):
    LOG(f"running request with url={url}")
    result = requests.request(method, url, headers=headers, data=body, verify=USE_SSL, params=params, files=files)
    if str(result.reason) == "Not Found":
        raise NotFoundError("Not Found.")

    # invalid argument
    if result.status_code == 421:
        try:
            error_message = json.loads(xml2json(result.text))
            error_message = error_message.get("error", {}).get("error-message")
        except Exception:
            raise Exception(f"Failed to parse response to json. response: {result.text}")

        demisto.results({"Type": entryTypes["error"], "Contents": error_message, "ContentsFormat": formats["text"]})

    # Check if status code is in ok_codes before treating it as an error
    if ok_codes and result.status_code in ok_codes:
        return result

    if result.status_code < 200 or result.status_code >= 300:
        if str(result.status_code) in ERROR_DICT:
            if result.status_code == 418 and FILE_TYPE_SUPPRESS_ERROR:
                demisto.results(
                    {
                        "Type": 11,
                        "Contents": f"Request Failed with status: {result.status_code}"
                        f" Reason is: {ERROR_DICT[str(result.status_code)]}",
                        "ContentsFormat": formats["text"],
                    }
                )
                sys.exit(0)
            else:
                raise Exception(
                    f"Request Failed with status: {result.status_code} Reason is: {ERROR_DICT[str(result.status_code)]}"
                )
        else:
            raise Exception(f"Request Failed with status: {result.status_code} Reason is: {result.reason}")
    if result.text.find("Forbidden. (403)") != -1:
        raise Exception("Request Forbidden - 403, check SERVER URL and API Key")

    if (
        ("Content-Type" in result.headers and result.headers["Content-Type"] == "application/octet-stream")
        or ("Transfer-Encoding" in result.headers and result.headers["Transfer-Encoding"] == "chunked")
    ) and return_raw:
        return result

    if resp_type == "json":
        return result.json()
    try:
        json_res = json.loads(xml2json(result.text))
        return json_res
    except Exception as exc:
        demisto.error(f"Failed to parse response to json. Error: {exc}")
        raise Exception(f"Failed to parse response to json. response: {result.text}")


def prettify_upload(upload_body):
    pretty_upload = {"MD5": upload_body["md5"], "SHA256": upload_body["sha256"], "Status": "Pending"}
    if "filetype" in upload_body:
        pretty_upload["FileType"] = upload_body["filetype"]
    if "size" in upload_body:
        pretty_upload["Size"] = upload_body["size"]
    if "url" in upload_body:
        pretty_upload["URL"] = upload_body["url"]

    return pretty_upload


def prettify_report_entry(file_info):
    pretty_report = {"MD5": file_info["md5"], "SHA256": file_info["sha256"], "Status": "Completed"}
    if "filetype" in file_info:
        pretty_report["FileType"] = file_info["filetype"]
    if "size" in file_info:
        pretty_report["Size"] = file_info["size"]
    if "url" in file_info:
        pretty_report["URL"] = file_info["url"]

    return pretty_report


def prettify_verdict(verdict_data):
    pretty_verdict = {}

    if "md5" in verdict_data:
        pretty_verdict["MD5"] = verdict_data["md5"]
    if "sha256" in verdict_data:
        pretty_verdict["SHA256"] = verdict_data["sha256"]

    pretty_verdict["Verdict"] = verdict_data["verdict"]
    pretty_verdict["VerdictDescription"] = VERDICTS_DICT[verdict_data["verdict"]]

    return pretty_verdict


def prettify_url_verdict(verdict_data: Dict) -> Dict:
    pretty_verdict = {
        "URL": verdict_data.get("url"),
        "Verdict": verdict_data.get("verdict"),
        "VerdictDescription": VERDICTS_DICT[verdict_data.get("verdict", "")],
        "Valid": verdict_data.get("valid"),
        "AnalysisTime": verdict_data.get("analysis_time"),
    }

    return pretty_verdict


def create_dbot_score_from_verdict(pretty_verdict):
    if "SHA256" not in pretty_verdict and "MD5" not in pretty_verdict:
        raise Exception("Hash is missing in WildFire verdict.")

    if pretty_verdict["Verdict"] not in VERDICTS_TO_DBOTSCORE:
        raise Exception("This hash verdict is not mapped to a DBotScore. Contact Demisto support for more information.")

    dbot_score = [
        {
            "Indicator": pretty_verdict["SHA256"] if "SHA256" in pretty_verdict else pretty_verdict["MD5"],
            "Type": "hash",
            "Vendor": "WildFire",
            "Score": VERDICTS_TO_DBOTSCORE[pretty_verdict["Verdict"]],
            "Reliability": RELIABILITY,
        },
        {
            "Indicator": pretty_verdict["SHA256"] if "SHA256" in pretty_verdict else pretty_verdict["MD5"],
            "Type": "file",
            "Vendor": "WildFire",
            "Score": VERDICTS_TO_DBOTSCORE[pretty_verdict["Verdict"]],
            "Reliability": RELIABILITY,
        },
    ]
    return dbot_score


def create_dbot_score_from_url_verdict(pretty_verdict: Dict) -> list:
    if pretty_verdict.get("Verdict") not in VERDICTS_TO_DBOTSCORE:
        dbot_score = [
            {"Indicator": pretty_verdict.get("URL"), "Type": "url", "Vendor": "WildFire", "Score": 0, "Reliability": RELIABILITY}
        ]
    else:
        dbot_score = [
            {
                "Indicator": pretty_verdict.get("URL"),
                "Type": "url",
                "Vendor": "WildFire",
                "Score": VERDICTS_TO_DBOTSCORE[pretty_verdict["Verdict"]],
                "Reliability": RELIABILITY,
            }
        ]
    return dbot_score


def prettify_verdicts(verdicts_data):
    pretty_verdicts_arr = []

    for verdict_data in verdicts_data:
        pretty_verdict = {}
        if "md5" in verdict_data:
            pretty_verdict["MD5"] = verdict_data["md5"]
        if "sha256" in verdict_data:
            pretty_verdict["SHA256"] = verdict_data["sha256"]

        pretty_verdict["Verdict"] = verdict_data["verdict"]
        pretty_verdict["VerdictDescription"] = VERDICTS_DICT[verdict_data["verdict"]]

        pretty_verdicts_arr.append(pretty_verdict)

    return pretty_verdicts_arr


def create_dbot_score_from_verdicts(pretty_verdicts):
    dbot_score_arr = []

    for pretty_verdict in pretty_verdicts:
        if "SHA256" not in pretty_verdict and "MD5" not in pretty_verdict:
            raise Exception("Hash is missing in WildFire verdict.")
        if pretty_verdict["Verdict"] not in VERDICTS_TO_DBOTSCORE:
            raise Exception("This hash verdict is not mapped to a DBotScore. Contact Demisto support for more information.")

        dbot_score_type_hash = {
            "Indicator": pretty_verdict["SHA256"] if "SHA256" in pretty_verdict else pretty_verdict["MD5"],
            "Type": "hash",
            "Vendor": "WildFire",
            "Score": VERDICTS_TO_DBOTSCORE[pretty_verdict["Verdict"]],
            "Reliability": RELIABILITY,
        }
        dbot_score_type_file = {
            "Indicator": pretty_verdict["SHA256"] if "SHA256" in pretty_verdict else pretty_verdict["MD5"],
            "Type": "file",
            "Vendor": "WildFire",
            "Score": VERDICTS_TO_DBOTSCORE[pretty_verdict["Verdict"]],
            "Reliability": RELIABILITY,
        }
        dbot_score_arr.append(dbot_score_type_hash)
        dbot_score_arr.append(dbot_score_type_file)

    return dbot_score_arr


def hash_args_handler(sha256=None, md5=None):
    # hash argument used in wildfire-report, wildfire-verdict commands
    inputs = argToList(sha256) if sha256 else argToList(md5)
    for element in inputs:
        if sha256Regex.match(element) or md5Regex.match(element):
            continue
        raise Exception("Invalid hash. Only SHA256 and MD5 are supported.")

    return inputs


def file_args_handler(file=None, sha256=None, md5=None):
    # file/md5/sha256 are used in file command
    if (file and not md5 and not sha256) or (not file and md5 and not sha256) or (not file and md5 and not sha256):
        if file:
            inputs = argToList(file)
        elif md5:
            inputs = argToList(md5)
        else:
            inputs = argToList(sha256)

        for element in inputs:
            if sha256Regex.match(element) or md5Regex.match(element) or sha1Regex.match(element):
                continue
            raise Exception("Invalid hash. Only SHA256 and MD5 are supported.")

        return inputs
    raise Exception("Specify exactly 1 of the following arguments: file, sha256, md5.")


def hash_list_to_file(hash_list):
    file_path = demisto.uniqueFile()
    with open(file_path, "w") as file:
        file.write("\n".join(hash_list))

    return [file_path]


def create_relationship(name: str, entities: tuple, types: tuple) -> list[EntityRelationship | None]:
    if CREATE_RELATIONSHIPS:
        return [
            EntityRelationship(
                name=name,
                entity_a=entities[0],
                entity_a_type=RELATIONSHIPS_TYPE[types[0]],
                entity_b=entities[1],
                entity_b_type=RELATIONSHIPS_TYPE[types[1]],
                reverse_name=name,
                source_reliability=RELIABILITY,
                brand="WildFire-v2",
            )
        ]
    else:
        return []


""" COMMANDS """


def test_module():
    """Test API connectivity by querying a well-known hash via /get/verdict."""
    test_hash = "dca86121cc7427e375fd24fe5871d727"
    try:
        wildfire_get_verdict(file_hash=test_hash)
    except NotFoundError:
        # Hash not found is still a valid API response —
        # connectivity and authentication are working.
        pass
    return "ok"


@logger
def wildfire_upload_file(upload):
    upload_file_uri = URL + URL_DICT["upload_file"]

    # update the body with
    # body = {'apikey': TOKEN}
    body = BODY_DICT

    file_path = demisto.getFilePath(upload)["path"]
    file_name = os.path.basename(demisto.getFilePath(upload)["name"])

    try:
        shutil.copy(file_path, file_name)
    except Exception as exc:
        demisto.error(f"Failed to prepare file for upload. Error: {exc}")
        raise Exception("Failed to prepare file for upload.")

    try:
        with open(file_name, "rb") as file:
            result = http_request(upload_file_uri, "POST", body=body, files={"file": file})
    finally:
        with contextlib.suppress(FileNotFoundError):
            os.remove(file_name)

    upload_file_data = result["wildfire"]["upload-file-info"]

    return result, upload_file_data


def wildfire_upload_file_with_polling_command(args):
    return run_polling_command(args, "wildfire-upload", wildfire_upload_file_command, wildfire_get_report_command, "FILE")


def wildfire_upload_file_command(args) -> list:
    assert_upload_argument(args)
    uploads = argToList(args.get("upload"))
    command_results_list = []
    for upload in uploads:
        result, upload_body = wildfire_upload_file(upload)
        pretty_upload_body = prettify_upload(upload_body)
        human_readable = tableToMarkdown("WildFire Upload File", pretty_upload_body, removeNull=True)
        command_results = CommandResults(
            outputs_prefix=WILDFIRE_REPORT_DT_FILE,
            outputs=pretty_upload_body,
            readable_output=human_readable,
            raw_response=result,
        )
        command_results_list.append(command_results)
    return command_results_list


@logger
def wildfire_upload_file_url(upload):
    upload_file_url_uri = URL + URL_DICT["upload_file_url"]

    # The WildFire /submit/url endpoint requires multipart/form-data. Passing form
    # fields as (None, value) tuples via `files=` makes `requests` build a compliant
    # multipart body (auto-generated boundary, CRLF separators).
    multipart_fields: dict = {key: (None, value) for key, value in BODY_DICT.items()}
    multipart_fields["url"] = (None, upload)

    result = http_request(upload_file_url_uri, "POST", files=multipart_fields)

    upload_file_url_data = result["wildfire"]["upload-file-info"]

    return result, upload_file_url_data


def wildfire_upload_file_url_with_polling_command(args) -> list:
    return run_polling_command(
        args, "wildfire-upload-file-url", wildfire_upload_file_url_command, wildfire_get_report_command, "URL"
    )


def wildfire_upload_file_url_command(args) -> list:
    assert_upload_argument(args)
    command_results_list = []
    uploads = argToList(args.get("upload"))
    for upload in uploads:
        result, upload_body = wildfire_upload_file_url(upload)
        pretty_upload_body = prettify_upload(upload_body)
        human_readable = tableToMarkdown("WildFire Upload File URL", pretty_upload_body, removeNull=True)
        command_results = CommandResults(
            outputs_prefix=WILDFIRE_REPORT_DT_FILE,
            outputs=pretty_upload_body,
            readable_output=human_readable,
            raw_response=result,
        )
        command_results_list.append(command_results)
    return command_results_list


@logger
def wildfire_upload_url(upload):
    upload_url_uri = URL + URL_DICT["upload_url"]

    # The WildFire /submit/link endpoint requires multipart/form-data. Passing form
    # fields as (None, value) tuples via `files=` makes `requests` build a compliant
    # multipart body (auto-generated boundary, CRLF separators).
    multipart_fields: dict = {key: (None, value) for key, value in BODY_DICT.items()}
    multipart_fields["link"] = (None, upload)

    result = http_request(upload_url_uri, "POST", files=multipart_fields)

    upload_url_data = result["wildfire"]["submit-link-info"]

    return result, upload_url_data


def wildfire_upload_url_command(args) -> list:
    assert_upload_argument(args)
    command_results_list = []
    uploads = argToList(args.get("upload"))
    for upload in uploads:
        result, upload_url_data = wildfire_upload_url(upload)
        pretty_upload_body = prettify_upload(upload_url_data)
        human_readable = tableToMarkdown("WildFire Upload URL", pretty_upload_body, removeNull=True)
        command_results = CommandResults(
            outputs_prefix=WILDFIRE_REPORT_DT_FILE,
            outputs=pretty_upload_body,
            readable_output=human_readable,
            raw_response=result,
        )
        command_results_list.append(command_results)
    return command_results_list


def wildfire_upload_url_with_polling_command(args):
    return run_polling_command(args, "wildfire-upload-url", wildfire_upload_url_command, wildfire_get_report_command, "URL")


def get_results_function_args(outputs, uploaded_item, args):
    """
    This function is used for the polling flow. After calling a upload command on a url\file, in order to check the
    status of the call, we need to retrieve the suitable identifier to call the results command on. for uploading a url,
     the identifier is the url itself, but for a file we need to extract the file hash from the results of the initial
     upload call. Therefore, this function extract that identifier from the data inserted to the context data by the
      upload command. The function also adds the 'verbose' and 'format' arguments that were given priorly to the upload
      command.
    Args:
        outputs: the context data from the search command
        uploaded_item: 'FILE' or 'URL'
        args: the args initially inserted to the upload function that initiated the polling sequence

    Returns:

    """
    results_function_args = {}
    if uploaded_item == "FILE":
        identifier = {"md5": outputs.get("MD5")}
    else:
        identifier = {"url": outputs.get("URL")}
    results_function_args.update(identifier)

    results_function_args.update({key: value for key, value in args.items() if key in ["verbose", "format"]})
    return results_function_args


def run_polling_command(args: dict, cmd: str, upload_function: Callable, results_function: Callable, uploaded_item):
    """
    This function is generically handling the polling flow. In the polling flow, there is always an initial call that
    starts the uploading to the API (referred here as the 'upload' function) and another call that retrieves the status
    of that upload (referred here as the 'results' function).
    The run_polling_command function runs the 'upload' function and returns a ScheduledCommand object that schedules
    the next 'results' function, until the polling is complete.
    Args:
        args: the arguments required to the command being called, under cmd
        cmd: the command to schedule by after the current command
        upload_function: the function that initiates the uploading to the API
        results_function: the function that retrieves the status of the previously initiated upload process
        uploaded_item: the type of item being uploaded

    Returns:

    """
    ScheduledCommand.raise_error_if_not_supported()
    command_results_list = []
    timeout_in_seconds = arg_to_number(args.pop("timeout_in_seconds", 600))
    interval_in_secs = int(args.get("interval_in_seconds", 60))
    # distinguish between the initial run, which is the upload run, and the results run
    is_new_search = "url" not in args and "md5" not in args and "sha256" not in args and "hash" not in args
    if is_new_search:
        assert_upload_argument(args)
        for upload in argToList(args["upload"]):
            # narrow the args to the current single url or file
            args["upload"] = upload
            # create new search
            command_results = upload_function(args)[0]
            outputs = command_results.outputs
            results_function_args = get_results_function_args(outputs, uploaded_item, args)
            # schedule next poll
            polling_args = {
                "interval_in_seconds": interval_in_secs,
                "polling": True,
                **results_function_args,
            }
            scheduled_command = ScheduledCommand(
                command=cmd, next_run_in_seconds=interval_in_secs, args=polling_args, timeout_in_seconds=timeout_in_seconds
            )
            command_results.scheduled_command = scheduled_command
            command_results_list.append(command_results)
        return command_results_list
    # not a new search, get search status
    command_results_list, status = results_function(args)
    if status != "Success":
        # schedule next poll
        polling_args = {"interval_in_seconds": interval_in_secs, "polling": True, **args}
        scheduled_command = ScheduledCommand(
            command=cmd, next_run_in_seconds=interval_in_secs, args=polling_args, timeout_in_seconds=timeout_in_seconds
        )

        command_results_list = [CommandResults(scheduled_command=scheduled_command)]
    return command_results_list


@logger
def wildfire_get_verdict(file_hash: str | None = None, url: str | None = None) -> tuple[dict, dict]:
    get_verdict_uri = URL + URL_DICT["verdict"]

    if file_hash:
        BODY_DICT["hash"] = file_hash

    else:
        BODY_DICT["url"] = url

    body = BODY_DICT

    result = http_request(get_verdict_uri, "POST", headers=DEFAULT_HEADERS, body=body)
    verdict_data = result["wildfire"]["get-verdict-info"]

    return result, verdict_data


def wildfire_get_verdict_command():
    file_hashes = hash_args_handler(demisto.args().get("hash", ""))
    urls = argToList(demisto.args().get("url", ""))
    if not urls and not file_hashes:
        raise Exception("Either hash or url must be provided.")
    if file_hashes:
        for file_hash in file_hashes:
            result, verdict_data = wildfire_get_verdict(file_hash=file_hash)

            pretty_verdict = prettify_verdict(verdict_data)
            human_readable = tableToMarkdown("WildFire Verdict", pretty_verdict, removeNull=True)

            dbot_score_list = create_dbot_score_from_verdict(pretty_verdict)
            entry_context = {
                "WildFire.Verdicts(val.SHA256 && val.SHA256 == obj.SHA256 || val.MD5 && val.MD5 == obj.MD5)": pretty_verdict,
                "DBotScore": dbot_score_list,
            }
            demisto.results(
                {
                    "Type": entryTypes["note"],
                    "Contents": result,
                    "ContentsFormat": formats["json"],
                    "HumanReadable": human_readable,
                    "ReadableContentsFormat": formats["markdown"],
                    "EntryContext": entry_context,
                }
            )
    else:
        for url in urls:
            result, verdict_data = wildfire_get_verdict(url=url)
            pretty_verdict = prettify_url_verdict(verdict_data)
            human_readable = tableToMarkdown("WildFire URL Verdict", pretty_verdict, removeNull=True)

            dbot_score_list = create_dbot_score_from_url_verdict(pretty_verdict)
            entry_context = {"WildFire.Verdicts(val.url && val.url == obj.url)": pretty_verdict, "DBotScore": dbot_score_list}

            demisto.results(
                {
                    "Type": entryTypes["note"],
                    "Contents": result,
                    "ContentsFormat": formats["json"],
                    "HumanReadable": human_readable,
                    "ReadableContentsFormat": formats["markdown"],
                    "EntryContext": entry_context,
                }
            )


@logger
def wildfire_get_verdicts(file_path):
    get_verdicts_uri = URL + URL_DICT["verdicts"]

    body = BODY_DICT

    try:
        with open(file_path, "rb") as file:
            result = http_request(get_verdicts_uri, "POST", body=body, files={"file": file})
    finally:
        shutil.rmtree(file_path, ignore_errors=True)

    verdicts_data = result["wildfire"]["get-verdict-info"]

    # When only a single hash is submitted, the WildFire API returns a plain dict
    # instead of a list (xmltodict collapses single-element XML arrays to a dict).
    # Normalize to a list so prettify_verdicts() always iterates over dicts, not string keys.
    if isinstance(verdicts_data, dict):
        demisto.debug("Verdicts data is a dict (single hash response), wrapping in a list.")
        verdicts_data = [verdicts_data]

    return result, verdicts_data


@logger
def wildfire_get_verdicts_command():
    if ("EntryID" in demisto.args() and "hash_list" in demisto.args()) or (
        "EntryID" not in demisto.args() and "hash_list" not in demisto.args()
    ):
        raise Exception("Specify exactly 1 of the following arguments: EntryID, hash_list.")

    if "EntryID" in demisto.args():
        inputs = argToList(demisto.args().get("EntryID"))
        paths = [demisto.getFilePath(element)["path"] for element in inputs]

    else:
        paths = hash_list_to_file(argToList(demisto.args().get("hash_list")))

    for file_path in paths:
        result, verdicts_data = wildfire_get_verdicts(file_path)

        pretty_verdicts = prettify_verdicts(verdicts_data)
        human_readable = tableToMarkdown("WildFire Verdicts", pretty_verdicts, removeNull=True)

        dbot_score_list = create_dbot_score_from_verdicts(pretty_verdicts)

        entry_context = {
            "WildFire.Verdicts(val.SHA256 && val.SHA256 == obj.SHA256 || val.MD5 && val.MD5 == obj.MD5)": pretty_verdicts,
            "DBotScore": dbot_score_list,
        }

        demisto.results(
            {
                "Type": entryTypes["note"],
                "Contents": result,
                "ContentsFormat": formats["json"],
                "HumanReadable": human_readable,
                "ReadableContentsFormat": formats["markdown"],
                "EntryContext": entry_context,
            }
        )


@logger
def wildfire_get_webartifacts(url: str, types: str) -> dict:
    get_webartifacts_uri = f'{URL}{URL_DICT["webartifacts"]}'

    PARAMS_DICT["url"] = url

    if types:
        PARAMS_DICT["types"] = types

    result = http_request(get_webartifacts_uri, "POST", headers=DEFAULT_HEADERS, params=PARAMS_DICT, return_raw=True)
    return result


@logger
def wildfire_get_url_webartifacts_command():
    """
    This function get the parameters for the call to webartifacts and returns the tgz of the results to download
    Also extracts inline the screenshot image if it exists as the type query, extracting the files that were downloaded
    exposes security risk for droppers from bad sites
    """
    urls = argToList(demisto.args().get("url"))
    types = demisto.args().get("types", "")
    # added ability to extract inline screenshot image only
    screenshot_inline = demisto.args().get("screenshot_inline", "")

    for url in urls:
        try:
            result = wildfire_get_webartifacts(url, types)

            empty_screenshot_tar = False
            # add check for inline screenshot extraction
            if types in ["screenshot"] and screenshot_inline in ["true"]:
                # we have a screenshot found - only a screenshot,
                # this will not extract a screenshot from a tgz with files for security reasons
                # we have a screenshot returned and we have inline extaction requested

                files = []
                exported_files = []

                # test for 0 byte tgz returned
                try:
                    image_content = result.content
                    file_like_object = io.BytesIO(image_content)
                    tar = tarfile.open(fileobj=file_like_object)
                    # get the names of the files in the TAR
                    files = tar.getnames()
                    # we have a TAR file with entries to extract
                    # this assumes there is only one screenshot per tgz
                    if files[0] in ["screenshot"]:
                        # first element is the folder name screenshot

                        members = tar.getmembers()
                        data = tar.extractfile(members[1])  # type:ignore
                        fdata = data.read()  # type:ignore
                        exported_files.append(members[1].name)
                        stored_img = fileResult(f"screenshot_{url}.png", fdata)

                        demisto.results(
                            {
                                "Type": entryTypes["image"],
                                "ContentsFormat": formats["text"],
                                "File": stored_img["File"],
                                "FileID": stored_img["FileID"],
                                "Contents": "",
                            }
                        )

                except Exception:
                    # the tgz for screenshot is empty, no screenshot provided
                    empty_screenshot_tar = True

            if empty_screenshot_tar is True:
                file_entry = fileResult(f"empty_{url}_webartifacts.tgz", result.content, entryTypes["entryInfoFile"])
            else:
                file_entry = fileResult(f"{url}_webartifacts.tgz", result.content, entryTypes["entryInfoFile"])
            demisto.results(file_entry)

        except NotFoundError as exc:
            demisto.error(f"WildFire Webartifacts were not found. Error: {exc}")
            return_results("WildFire Webartifacts were not found.")


def parse_wildfire_object(report: dict, keys: list[tuple]) -> Union[dict, None]:
    """
    This function changes the key names of the json object that came from the API response,
    for the context path.
    """

    outputs = {}
    for key in keys:
        if item_value := report.get(key[0]):
            outputs[key[1]] = item_value
    return outputs if outputs else None


def parse_file_report(file_hash, reports, file_info, extended_data: bool):
    udp_ip = []
    udp_port = []
    network_udp = []
    tcp_ip = []
    tcp_port = []
    network_tcp = []
    dns_query = []
    dns_response = []
    network_dns = []
    evidence_md5 = []
    evidence_text = []
    process_list_outputs = []
    process_tree_outputs = []
    entry_summary = []
    extract_urls_outputs = []
    elf_shell_commands = []
    feed_related_indicators = []
    platform_report = []
    software_report = []
    behavior = []
    network_url = []
    relationships = []

    # When only one report is in response, it's returned as a single json object and not a list.
    if not isinstance(reports, list):
        reports = [reports]

    for report in reports:
        if report.get("network"):
            if "UDP" in report["network"]:
                udp_objects = report["network"]["UDP"]
                if not isinstance(udp_objects, list):
                    udp_objects = [udp_objects]
                for udp_obj in udp_objects:
                    if udp_obj.get("@ip"):
                        udp_ip.append(udp_obj["@ip"])
                        feed_related_indicators.append({"value": udp_obj["@ip"], "type": "IP"})
                        relationships.extend(create_relationship("related-to", (file_hash, udp_obj["@ip"]), ("file", "ip")))
                    if "@port" in udp_obj:
                        udp_port.append(udp_obj["@port"])
                    if extended_data and (
                        network_udp_dict := parse_wildfire_object(
                            report=udp_obj,
                            keys=[("@ip", "IP"), ("@port", "Port"), ("@country", "Country"), ("@ja3", "JA3"), ("@ja3s", "JA3S")],
                        )
                    ):
                        network_udp.append(network_udp_dict)

            if "TCP" in report["network"]:
                tcp_objects = report["network"]["TCP"]
                if not isinstance(tcp_objects, list):
                    tcp_objects = [tcp_objects]
                for tcp_obj in tcp_objects:
                    if tcp_obj.get("@ip"):
                        tcp_ip.append(tcp_obj["@ip"])
                        feed_related_indicators.append({"value": tcp_obj["@ip"], "type": "IP"})
                        relationships.extend(create_relationship("related-to", (file_hash, tcp_obj["@ip"]), ("file", "ip")))
                    if "@port" in tcp_obj:
                        tcp_port.append(tcp_obj["@port"])
                    if extended_data and (
                        network_tcp_dict := parse_wildfire_object(
                            report=tcp_obj,
                            keys=[("@ip", "IP"), ("@port", "Port"), ("@country", "Country"), ("@ja3", "JA3"), ("@ja3s", "JA3S")],
                        )
                    ):
                        network_tcp.append(network_tcp_dict)

            if "dns" in report["network"]:
                dns_objects = report["network"]["dns"]
                if not isinstance(dns_objects, list):
                    dns_objects = [dns_objects]
                for dns_obj in dns_objects:
                    if dns_obj.get("@query"):
                        dns_query.append(dns_obj["@query"])
                    if dns_obj.get("@response"):
                        dns_response.append(dns_obj["@response"])
                    if extended_data and (
                        network_dns_dict := parse_wildfire_object(
                            report=dns_obj, keys=[("@query", "Query"), ("@response", "Response"), ("@type", "Type")]
                        )
                    ):
                        network_dns.append(network_dns_dict)

            if "url" in report["network"]:
                url_objects = report["network"]["url"]
                if not isinstance(url_objects, list):
                    url_objects = [url_objects]
                for url_obj in url_objects:
                    url = ""
                    if url_obj.get("@host"):
                        url = url_obj["@host"]
                    if url_obj.get("@uri"):
                        url += url_obj["@uri"]
                    if url:
                        feed_related_indicators.append({"value": url, "type": "URL"})
                        relationships.extend(create_relationship("related-to", (file_hash, url.rstrip("/")), ("file", "url")))
                    if extended_data and (
                        network_url_dict := parse_wildfire_object(
                            report=url_obj,
                            keys=[("@host", "Host"), ("@uri", "URI"), ("@method", "Method"), ("@user_agent", "UserAgent")],
                        )
                    ):
                        network_url.append(network_url_dict)

        if (
            "evidence" in report
            and report["evidence"]
            and "file" in report["evidence"]
            and isinstance(report["evidence"]["file"], dict)
            and "entry" in report["evidence"]["file"]
        ):
            if "@md5" in report["evidence"]["file"]["entry"]:
                evidence_md5.append(report["evidence"]["file"]["entry"]["@md5"])
            if "@text" in report["evidence"]["file"]["entry"]:
                evidence_text.append(report["evidence"]["file"]["entry"]["@text"])

        if report.get("elf_info"):
            if (
                "Domains" in report["elf_info"]
                and isinstance(report["elf_info"]["Domains"], dict)
                and "entry" in report["elf_info"]["Domains"]
            ):
                entry = report["elf_info"]["Domains"]["entry"]
                # when there is only one entry, it is returned as a single string not a list
                if not isinstance(entry, list):
                    entry = [entry]
                for domain in entry:
                    feed_related_indicators.append({"value": domain, "type": "Domain"})
                    relationships.extend(create_relationship("related-to", (file_hash, domain), ("file", "domain")))
            if (
                "IP_Addresses" in report["elf_info"]
                and isinstance(report["elf_info"]["IP_Addresses"], dict)
                and "entry" in report["elf_info"]["IP_Addresses"]
            ):
                entry = report["elf_info"]["IP_Addresses"]["entry"]
                # when there is only one entry, it is returned as a single string not a list
                if not isinstance(entry, list):
                    entry = [entry]
                for ip in entry:
                    feed_related_indicators.append({"value": ip, "type": "IP"})
                    relationships.extend(create_relationship("related-to", (file_hash, ip), ("file", "ip")))
            if (
                "suspicious" in report["elf_info"]
                and isinstance(report["elf_info"]["suspicious"], dict)
                and "entry" in report["elf_info"]["suspicious"]
            ):
                entry = report["elf_info"]["suspicious"]["entry"]
                # when there is only one entry, it is returned as a single json not a list
                if not isinstance(entry, list):
                    entry = [entry]
                for entry_obj in entry:
                    if "#text" in entry_obj and "@description" in entry_obj:
                        behavior.append({"details": entry_obj["#text"], "action": entry_obj["@description"]})
            if (
                "URLs" in report["elf_info"]
                and isinstance(report["elf_info"]["URLs"], dict)
                and "entry" in report["elf_info"]["URLs"]
            ):
                entry = report["elf_info"]["URLs"]["entry"]
                # when there is only one entry, it is returned as a single string not a list
                if not isinstance(entry, list):
                    entry = [entry]
                for url in entry:
                    feed_related_indicators.append({"value": url, "type": "URL"})
                    relationships.extend(create_relationship("related-to", (file_hash, url), ("file", "url")))
            if extended_data and (shell_commands := demisto.get(report, "elf_info.Shell_Commands.entry")):
                elf_shell_commands.append(shell_commands)

        if extended_data:
            if process_list := demisto.get(report, "process_list.process"):
                if not isinstance(process_list, list):
                    process_list = [process_list]
                for process in process_list:
                    if process_list_dict := parse_wildfire_object(
                        report=process,
                        keys=[
                            ("@command", "ProcessCommand"),
                            ("@name", "ProcessName"),
                            ("@pid", "ProcessPid"),
                            ("file", "ProcessFile"),
                            ("service", "Service"),
                        ],
                    ):
                        process_list_outputs.append(process_list_dict)

            if process_tree := demisto.get(report, "process_tree.process"):
                if not isinstance(process_tree, list):
                    process_tree = [process_tree]
                for process in process_tree:
                    tree_outputs = {}
                    if process_tree_dict := parse_wildfire_object(
                        report=process, keys=[("@text", "ProcessText"), ("@name", "ProcessName"), ("@pid", "ProcessPid")]
                    ):
                        tree_outputs = process_tree_dict

                    if child_process := demisto.get(process, "child.process"):
                        if not isinstance(child_process, list):
                            child_process = [child_process]
                        for child in child_process:
                            if process_tree_child_dict := parse_wildfire_object(
                                report=child, keys=[("@text", "ChildText"), ("@name", "ChildName"), ("@pid", "ChildPid")]
                            ):
                                tree_outputs["Process"] = process_tree_child_dict
                    if tree_outputs:
                        process_tree_outputs.append(tree_outputs)

            if entries := demisto.get(report, "summary.entry"):
                if not isinstance(entries, list):
                    entries = [entries]
                for entry in entries:
                    if entry_summary_dict := parse_wildfire_object(
                        report=entry, keys=[("#text", "Text"), ("@details", "Details"), ("@behavior", "Behavior")]
                    ):
                        entry_summary.append(entry_summary_dict)

            if extract_urls := demisto.get(report, "extracted_urls.entry"):
                if not isinstance(extract_urls, list):
                    extract_urls = [extract_urls]
                for urls in extract_urls:
                    if extract_urls_dict := parse_wildfire_object(report=urls, keys=[("@url", "URL"), ("@verdict", "Verdict")]):
                        extract_urls_outputs.append(extract_urls_dict)

            if "platform" in report:
                platform_report.append(report["platform"])

            if "software" in report:
                software_report.append(report["software"])

    outputs = {"Status": "Success", "SHA256": file_info.get("sha256")}

    if len(udp_ip) > 0 or len(udp_port) > 0 or len(tcp_ip) > 0 or len(tcp_port) > 0 or dns_query or dns_response:
        outputs["Network"] = {}

        if len(udp_ip) > 0 or len(udp_port) > 0:
            outputs["Network"]["UDP"] = {}
            if len(udp_ip) > 0:
                outputs["Network"]["UDP"]["IP"] = udp_ip
            if len(udp_port) > 0:
                outputs["Network"]["UDP"]["Port"] = udp_port

        if len(tcp_ip) > 0 or len(tcp_port) > 0:
            outputs["Network"]["TCP"] = {}
            if len(tcp_ip) > 0:
                outputs["Network"]["TCP"]["IP"] = tcp_ip
            if len(tcp_port) > 0:
                outputs["Network"]["TCP"]["Port"] = tcp_port

        if len(dns_query) > 0 or len(dns_response) > 0:
            outputs["Network"]["DNS"] = {}
            if len(dns_query) > 0:
                outputs["Network"]["DNS"]["Query"] = dns_query
            if len(dns_response) > 0:
                outputs["Network"]["DNS"]["Response"] = dns_response

    if network_udp or network_tcp or network_dns or network_url:
        outputs["NetworkInfo"] = {}
        if network_udp:
            outputs["NetworkInfo"]["UDP"] = network_udp
        if network_tcp:
            outputs["NetworkInfo"]["TCP"] = network_tcp
        if network_dns:
            outputs["NetworkInfo"]["DNS"] = network_dns
        if network_url:
            outputs["NetworkInfo"]["URL"] = network_url

    if platform_report:
        outputs["Platform"] = platform_report

    if software_report:
        outputs["Software"] = software_report

    if process_list_outputs:
        outputs["ProcessList"] = process_list_outputs

    if process_tree_outputs:
        outputs["ProcessTree"] = process_tree_outputs

    if entry_summary:
        outputs["Summary"] = entry_summary

    if extract_urls_outputs:
        outputs["ExtractedURL"] = extract_urls_outputs

    if elf_shell_commands:
        outputs["ELF"] = {}
        outputs["ELF"]["ShellCommands"] = elf_shell_commands

    if len(evidence_md5) > 0 or len(evidence_text) > 0:
        outputs["Evidence"] = {}
        if len(evidence_md5) > 0:
            outputs["Evidence"]["md5"] = evidence_md5
        if len(evidence_text) > 0:
            outputs["Evidence"]["Text"] = evidence_text

    feed_related_indicators = create_feed_related_indicators_object(feed_related_indicators)
    behavior = create_behaviors_object(behavior)
    return outputs, feed_related_indicators, behavior, relationships


def create_feed_related_indicators_object(feed_related_indicators):
    """
    This function is used while enhancing the integration, enabling the use of Common.FeedRelatedIndicators object

    """
    feed_related_indicators_objects_list = []
    for item in feed_related_indicators:
        feed_related_indicators_objects_list.append(
            Common.FeedRelatedIndicators(value=item["value"], indicator_type=item["type"])
        )
    return feed_related_indicators_objects_list


def create_behaviors_object(behaviors):
    """
    This function is used while enhancing the integration, enabling the use of Common.Behaviors object

    """
    behaviors_objects_list = []
    for item in behaviors:
        behaviors_objects_list.append(Common.Behaviors(details=item["details"], action=item["action"]))
    return behaviors_objects_list


def create_file_report(
    file_hash: str, reports, file_info, format_: str = "xml", verbose: bool = False, extended_data: bool = False
):
    outputs, feed_related_indicators, behavior, relationships = parse_file_report(file_hash, reports, file_info, extended_data)

    if file_info["malware"] == "yes":
        dbot_score = 3
        tags = ["malware"]
    elif file_info["malware"] == "grayware":
        dbot_score = 2
        tags = []
    else:
        dbot_score = 1
        tags = []

    dbot_score_object = Common.DBotScore(
        indicator=file_hash,
        indicator_type=DBotScoreType.FILE,
        integration_name=INTEGRATION_NAME,
        score=dbot_score,
        reliability=RELIABILITY,
    )
    file = Common.File(
        dbot_score=dbot_score_object,
        name=file_info.get("filename"),
        file_type=file_info.get("filetype"),
        md5=file_info.get("md5"),
        sha1=file_info.get("sha1"),
        sha256=file_info.get("sha256"),
        size=file_info.get("size"),
        feed_related_indicators=feed_related_indicators,
        tags=tags,
        digital_signature__publisher=file_info.get("file_signer"),
        behaviors=behavior,
        relationships=relationships,
    )

    if format_ == "pdf":
        get_report_uri = URL + URL_DICT["report"]

        PARAMS_DICT["format"] = "pdf"
        PARAMS_DICT["hash"] = file_hash

        res_pdf = http_request(get_report_uri, "POST", headers=DEFAULT_HEADERS, params=PARAMS_DICT, return_raw=True)

        file_name = "wildfire_report_" + file_hash + ".pdf"
        file_type = entryTypes["entryInfoFile"]
        result = fileResult(file_name, res_pdf.content, file_type)  # will be saved under 'InfoFile' in the context.
        demisto.results(result)
        human_readable = tableToMarkdown("WildFire File Report - PDF format", prettify_report_entry(file_info))

    # new format for wildfire reports to output in MAEC format
    elif format_ == "maec":
        get_report_uri = URL + URL_DICT["report"]

        PARAMS_DICT["format"] = "maec"
        PARAMS_DICT["hash"] = file_hash

        try:
            res_maec = http_request(get_report_uri, "POST", headers=DEFAULT_HEADERS, params=PARAMS_DICT, resp_type="json")

            report_json = res_maec.get("result")

            file_name = "wildfire_report_maec_" + file_hash + ".json"
            file_type = entryTypes["entryInfoFile"]

            result = fileResult(file_name, report_json, file_type)  # will be saved under 'InfoFile' in the context.
            demisto.results(result)
            human_readable = tableToMarkdown("WildFire File Report - MAEC format", prettify_report_entry(file_info))
            outputs["maec_report"] = json.loads(report_json)

        except Exception as exc:
            demisto.error(f"Report MAEC Exception. Error: {exc}")
            human_readable = None
            outputs = None
            relationships = None

    # catch all report type for those not specified
    else:
        human_readable = tableToMarkdown("WildFire File Report", prettify_report_entry(file_info))
        if verbose:
            for report in reports:
                if isinstance(report, dict):
                    human_readable += tableToMarkdown("Report ", report, list(report), removeNull=True)

    return human_readable, outputs, file, relationships


def get_sha256_of_file_from_report(report):
    if maec_packages := report.get("maec_packages"):
        for item in maec_packages:
            if hashes := item.get("hashes"):
                return hashes.get("SHA256")
    return None


@logger
def wildfire_get_url_report(url: str) -> tuple:
    """
    This functions is used for retrieving the results of a previously uploaded url.
    Args:
        url: The url of interest.

    Returns:
        A CommandResults object with the results of the request and the status of that upload (Pending/Success/NotFound).

    """

    get_report_uri = f"{URL}{URL_DICT['report']}"

    PARAMS_DICT["url"] = url

    entry_context = {"URL": url}
    human_readable = None

    try:
        response = http_request(get_report_uri, "POST", headers=DEFAULT_HEADERS, params=PARAMS_DICT, resp_type="json")
        report = response.get("result").get("report")

        if not report:
            entry_context["Status"] = "Pending"
            human_readable = "The sample is still being analyzed. Please wait to download the report."

        else:
            entry_context["Status"] = "Success"
            report = json.loads(report) if type(report) is not dict else report
            report.update(entry_context)
            sha256_of_file_in_url = get_sha256_of_file_from_report(report)
            human_readable_dict = {"SHA256": sha256_of_file_in_url, "URL": url, "Status": "Success"}
            human_readable = tableToMarkdown(f"Wildfire URL report for {url}", t=human_readable_dict, removeNull=True)

    except NotFoundError:
        entry_context["Status"] = "NotFound"
        human_readable = "Report not found."
        report = ""
    except Exception as e:
        entry_context["Status"] = ""
        human_readable = f"Error while requesting the report: {e}."
        report = ""
        demisto.error(f"Error while requesting the given report. Error: {e}")

    finally:
        command_results = CommandResults(
            outputs_prefix=WILDFIRE_REPORT_DT_FILE,
            outputs=report,
            readable_output=human_readable,
            raw_response=report,
        )
        return command_results, entry_context["Status"]


@logger
def wildfire_get_file_report(file_hash: str, args: dict):
    get_report_uri = URL + URL_DICT["report"]

    # we get the xml report first for all cases to parse data for reporting
    PARAMS_DICT["format"] = "xml"
    PARAMS_DICT["hash"] = file_hash

    # necessarily one of them as passed the hash_args_handler
    sha256 = file_hash if sha256Regex.match(file_hash) else None
    md5 = file_hash if md5Regex.match(file_hash) else None
    entry_context = {key: value for key, value in (["MD5", md5], ["SHA256", sha256]) if value}
    human_readable, relationships, indicator = None, None, None
    try:
        json_res = http_request(get_report_uri, "POST", headers=DEFAULT_HEADERS, params=PARAMS_DICT)
        # we get the report and file info from the XML object
        reports = ((json_res.get("wildfire") or {}).get("task_info") or {}).get("report")
        file_info = json_res.get("wildfire", {}).get("file_info")

        # extra options to provide in the query
        verbose = args.get("verbose", "false").lower() == "true"
        format_ = args.get("format", "xml")
        extended_data = argToBoolean(args.get("extended_data", False))

        if reports and file_info:
            human_readable, entry_context, indicator, relationships = create_file_report(
                file_hash, reports, file_info, format_, verbose, extended_data
            )
        else:
            entry_context["Status"] = "Pending"
            human_readable = "The sample is still being analyzed. Please wait to download the report."
            indicator = None
            relationships = None

    except NotFoundError as exc:
        entry_context["Status"] = "NotFound"
        human_readable = "Report not found."
        dbot_score_file = 0
        json_res = ""
        dbot_score_object = Common.DBotScore(
            indicator=file_hash,
            indicator_type=DBotScoreType.FILE,
            integration_name=INTEGRATION_NAME,
            score=dbot_score_file,
            reliability=RELIABILITY,
        )
        indicator = Common.File(dbot_score=dbot_score_object, md5=md5, sha256=sha256)
        demisto.error(f"Report not found. Error: {exc!s}")
        relationships = None
    except Exception as e:
        entry_context["Status"] = str(e)
        human_readable = str(e)
        dbot_score_file = 0
        json_res = ""
        dbot_score_object = Common.DBotScore(
            indicator=file_hash,
            indicator_type=DBotScoreType.FILE,
            integration_name=INTEGRATION_NAME,
            score=dbot_score_file,
            reliability=RELIABILITY,
        )
        indicator = Common.File(dbot_score=dbot_score_object, md5=md5, sha256=sha256)
        demisto.error(f"Report error: {e!s}")
        relationships = None
    finally:
        try:
            command_results = CommandResults(
                outputs_prefix=WILDFIRE_REPORT_DT_FILE,
                outputs=remove_empty_elements(entry_context),
                readable_output=human_readable,
                indicator=indicator,
                raw_response=json_res,
                relationships=relationships,
            )
            return command_results, entry_context.get("Status")
        except Exception as e:
            raise DemistoException(f"Error while trying to get the report from the API: {e!s} - {format_exc()}")


def wildfire_get_report_command(args: dict):
    """
    Args:
        args: the command arguments from demisto.args(), including url or file hash (sha256 or md5) to query on

    Returns:
        A single or list of CommandResults, and the status of the reports of the url or file of interest.
        Note that the status is only used for the polling sequence, where the command will always receive a single
        file or url. Hence, when running this command via the polling sequence, the CommandResults list will contain a
        single item, and the status will represent that result's status.

    """
    command_results_list = []
    urls = argToList(args.get("url", ""))
    if "sha256" in args:
        sha256 = args.get("sha256")
    elif "hash" in args:
        sha256 = args.get("hash")
    else:
        sha256 = None
    md5 = args.get("md5")
    inputs = urls if urls else hash_args_handler(sha256, md5)

    status = ""
    for element in inputs:
        command_results, status = wildfire_get_url_report(element) if urls else wildfire_get_file_report(element, args)
        command_results_list.append(command_results)

    return command_results_list, status


def wildfire_file_command(args: dict):
    inputs = file_args_handler(args.get("file"), args.get("md5"), args.get("sha256"))
    command_results_list = []
    for element in inputs:
        if sha1Regex.match(element):
            demisto.results(
                {
                    "Type": 11,
                    "Contents": "WildFire file hash reputation supports only MD5, SHA256 hashes",
                    "ContentsFormat": formats["text"],
                }
            )
        else:
            command_results = wildfire_get_file_report(element, args)[0]
            command_results_list.append(command_results)
    return command_results_list


def wildfire_get_sample(file_hash):
    get_report_uri = URL + URL_DICT["sample"]

    PARAMS_DICT["hash"] = file_hash

    result = http_request(get_report_uri, "POST", headers=DEFAULT_HEADERS, params=PARAMS_DICT, return_raw=True, ok_codes=[403])
    return result


def wildfire_get_sample_command():
    if "sha256" in demisto.args() or "hash" in demisto.args():
        sha256 = demisto.args().get("sha256", None)
    else:
        sha256 = None
    md5 = demisto.args().get("md5", None)
    inputs = hash_args_handler(sha256, md5)

    for element in inputs:
        try:
            result = wildfire_get_sample(element)

            # Check if we got a 403 status code (benign sample)
            if result.status_code == 403:
                demisto.results(
                    "Benign samples are not available for download. For more info contact your WildFire representative."
                )
            else:
                # filename will be found under the Content-Disposition header in the format
                # attachment; filename=<FILENAME>.000
                content_disposition = result.headers.get("Content-Disposition")
                raw_filename = content_disposition.split("filename=")[1]
                # there are 2 dots in the filename as the response saves the packet capture file
                # need to extract the string until the second occurrence of the dot char
                file_name = ".".join(raw_filename.split(".")[:2])
                # will be saved under 'File' in the context, can be further investigated.
                file_entry = fileResult(file_name, result.content)
                demisto.results(file_entry)
        except NotFoundError as exc:
            demisto.error(f"Sample was not found. Error: {exc}")
            demisto.results(
                "Sample was not found. "
                "Please note that grayware samples are available for 14 days only. "
                "For more info contact your WildFire representative."
            )


def assert_upload_argument(args: dict):
    """
    Assert the upload argument is inserted when running the command without the builtin polling flow.
    The upload argument is only required when polling is false.
    """
    if not args.get("upload"):
        raise ValueError("Please specify the item you wish to upload using the 'upload' argument.")


def get_agent(api_key_source: str, token: str) -> str:
    # Auto API expect the agent header to be 'xdr' when running from within XSIAM and 'xsoartim' when running from
    # within XSOAR (both on-prem and cloud).
    # Explicit source selection always takes priority.
    if api_key_source in ["pcc", "prismaaccessapi", "xsoartim", "xdr"]:
        return api_key_source
    # Auto-detect on XSIAM / XSOAR 8+ platforms — XDR license tokens may be 32 chars
    # but still require agent=xdr.
    if (is_xsiam() or is_demisto_version_ge("8")) and not api_key_source:
        return "xdr"
    # NGFW / WF portal keys are 32 chars and need no agent header.
    # This check is intentionally after platform detection to avoid masking XDR license tokens.
    if len(token) == 32:
        return ""
    # we have an 'other' api key that requires no additional api key headers for agent
    return ""


def set_http_params(token, agent_value):
    global AGENT_VALUE
    global BODY_DICT
    global PARAMS_DICT
    global TOKEN

    AGENT_VALUE = agent_value
    BODY_DICT = {"apikey": token}
    PARAMS_DICT = {"apikey": token}
    if agent_value:
        BODY_DICT["agent"] = agent_value
        PARAMS_DICT["agent"] = agent_value
    TOKEN = token


def main():  # pragma: no cover
    command = demisto.command()
    args = demisto.args()
    params = demisto.params()
    demisto.info(f"command is {command}")

    try:
        token = params.get("token") or (params.get("credentials") or {}).get("password")
        # get the source of the credentials to ensure the correct agent is set for all API calls
        # other = ngfw or wf api based keys that are 32 chars long and require no agent
        # pcc and prismaaccessapi are 64 char long and require the correct agent= value in the api call
        if not token:
            # Added support for all platforms from version 2.1.42.
            with contextlib.suppress(Exception):
                token = demisto.getLicenseCustomField("WildFire-Reports.token")

        if not token:
            # If token is empty when test-module is running, return a more readable output to the user.
            if command == "test-module":
                raise DemistoException(
                    "Authorization Error: It's seems that the token is empty and you have not a TIM license "
                    "that is up-to-date, Please fill the token or update your TIM license and try again."
                )
            else:
                return_results(
                    {
                        "status": "error",
                        "error": {
                            "title": "Couldn't execute Wildfire command.",
                            "description": "The token can't be empty.",
                            "techInfo": "The token can't be empty, Please fill the token in the instance configuration "
                            "or update your TIM license.",
                        },
                    }
                )
                sys.exit()

        # update the default headers with the correct agent version based on the selection in the instance config.
        agent_value = get_agent(params.get("credentials_source"), token)

        # if the apikey is longer than 32 characters agent is not set, and we're not in XSIAM or XSOAR SaaS, send exception
        # otherwise API calls will fail.
        if len(token) > 32 and not agent_value:
            # the token is longer than 32 so one of pcc, prismaaccessapi, xsoartim, xdr needs to be set or a
            # license from XSIAM/XSOAR NG.
            raise DemistoException(
                "API Key is longer than 32 characters. Select an 'API Key Type' in the integration's instance configuration."
            )
        set_http_params(token, agent_value)

        # Log diagnostic info for troubleshooting credential/agent issues.
        token_type = type(token).__name__
        token_length = len(token) if isinstance(token, str) else "N/A"
        demisto.info(f"WildFire_v2: using agent_value={agent_value}, token_type={token_type}, token_length={token_length}")

        if command == "test-module":
            return_results(test_module())

        elif command == "wildfire-upload":
            if args.get("polling") == "true":
                return_results(wildfire_upload_file_with_polling_command(args))
            else:
                return_results(wildfire_upload_file_command(args))

        elif command in ["wildfire-upload-file-remote", "wildfire-upload-file-url"]:
            if args.get("polling") == "true":
                return_results(wildfire_upload_file_url_with_polling_command(args))
            else:
                return_results(wildfire_upload_file_url_command(args))

        elif command == "wildfire-upload-url":
            if args.get("polling") == "true":
                return_results(wildfire_upload_url_with_polling_command(args))
            else:
                return_results(wildfire_upload_url_command(args))

        elif command == "wildfire-report":
            return_results(wildfire_get_report_command(args)[0])

        elif command == "file":
            return_results(wildfire_file_command(args))

        elif command == "wildfire-get-sample":
            wildfire_get_sample_command()

        elif command == "wildfire-get-verdict":
            wildfire_get_verdict_command()

        elif command == "wildfire-get-verdicts":
            wildfire_get_verdicts_command()

        elif command == "wildfire-get-url-webartifacts":
            wildfire_get_url_webartifacts_command()

        else:
            raise NotImplementedError(f"command {command} is not implemented.")

    except Exception as err:
        return_error(str(err))

    finally:
        LOG.print_log()


if __name__ in ["__main__", "__builtin__", "builtins"]:
    main()