Skip to content

ScanHtml

Collects metadata and extracts embedded scripts from HTML files.

Options

parser: Sets the HTML parser used during scanning. Defaults to 'html.parser'.

Source code in strelka/src/python/strelka/scanners/scan_html.py
class ScanHtml(strelka.Scanner):
    """Collects metadata and extracts embedded scripts from HTML files.

    Options:
        parser: Sets the HTML parser used during scanning.
            Defaults to 'html.parser'.
    """

    def scan(self, data, file, options, expire_at):
        parser = options.get("parser", "html.parser")
        max_hyperlinks = options.get("max_hyperlinks", 50)

        self.event["total"] = {
            "scripts": 0,
            "forms": 0,
            "inputs": 0,
            "frames": 0,
            "extracted": 0,
        }

        try:
            soup = bs4.BeautifulSoup(data, parser)

            if soup.title:
                self.event["title"] = soup.title.text

            hyperlinks = []
            hyperlinks.extend(soup.find_all("a", href=True))
            hyperlinks.extend(soup.find_all("img", src=True))
            self.event.setdefault("hyperlinks", [])
            for hyperlink in hyperlinks:
                link = hyperlink.get("href") or hyperlink.get("src")

                if link and link.startswith("data:") and ";base64," in link:
                    hyperlink_data = link.split(";base64,")[1]
                    self.emit_file(
                        hyperlink_data.encode(),
                        name="base64_hyperlink",
                        flavors=["base64"],
                    )
                else:
                    if link not in self.event["hyperlinks"]:
                        self.event["hyperlinks"].append(link)

            # Gather count of links and reduce potential link duplicates and restrict amount of
            # links returned using the configurable max_hyperlinks.
            if self.event["hyperlinks"]:
                self.event["hyperlinks_count"] = len(self.event["hyperlinks"])
                self.event["hyperlinks"] = self.event["hyperlinks"][:max_hyperlinks]

            forms = soup.find_all("form")
            self.event["total"]["forms"] = len(forms)
            self.event.setdefault("forms", [])
            for form in forms:
                form_entry = {
                    "action": form.get("action"),
                    "method": form.get("method"),
                }
                if form_entry not in self.event["forms"]:
                    self.event["forms"].append(form_entry)

            frames = []
            frames.extend(soup.find_all("frame"))
            frames.extend(soup.find_all("iframe"))
            self.event["total"]["frames"] = len(frames)
            self.event.setdefault("frames", [])
            for frame in frames:
                frame_entry = {
                    "src": frame.get("src"),
                    "name": frame.get("name"),
                    "height": frame.get("height"),
                    "width": frame.get("width"),
                    "border": frame.get("border"),
                    "id": frame.get("id"),
                    "style": frame.get("style"),
                }
                if frame_entry not in self.event["frames"]:
                    self.event["frames"].append(frame_entry)

            inputs = soup.find_all("input")
            self.event["total"]["inputs"] = len(inputs)
            self.event.setdefault("inputs", [])
            for html_input in inputs:
                input_entry = {
                    "type": html_input.get("type"),
                    "name": html_input.get("name"),
                    "value": html_input.get("value"),
                }
                if input_entry not in self.event["inputs"]:
                    self.event["inputs"].append(input_entry)

            scripts = soup.find_all("script")
            self.event["total"]["scripts"] = len(scripts)
            self.event.setdefault("scripts", [])
            for index, script in enumerate(scripts):
                script_flavors = [
                    script.get("language", "").lower(),
                    script.get("type", "").lower(),
                ]
                script_entry = {
                    "src": script.get("src"),
                    "language": script.get("language"),
                    "type": script.get("type"),
                }
                if script_entry not in self.event["scripts"]:
                    self.event["scripts"].append(script_entry)

                if script.text:
                    self.emit_file(
                        script.text.encode(),
                        name=f"script_{index}",
                        flavors=script_flavors,
                    )
                    self.event["total"]["extracted"] += 1

            spans = soup.find_all("span")
            self.event["total"]["spans"] = len(spans)
            self.event.setdefault("spans", [])
            for span in spans:
                span_entry = {
                    "class": span.get("class"),
                    "style": span.get("style"),
                }
                if span_entry not in self.event["spans"]:
                    self.event["spans"].append(span_entry)

            divs = soup.find_all("div")
            for div in divs:
                div_content = div.string
                if div_content is None:
                    continue

                is_maybe_base64 = base64Re.search(div_content)

                if is_maybe_base64:
                    self.emit_file(div_content, name="base64_div", flavors=["base64"])

        except TypeError:
            self.flags.append("type_error")

Features

The features of this scanner are detailed below. These features represent the capabilities and the type of analysis the scanner can perform. This may include support for Indicators of Compromise (IOC), the ability to emit files for further analysis, and the presence of extended documentation for complex analysis techniques.

Feature
Support
IOC Support
Emit Files
Extended Docs
Malware Scanner
Image Thumbnails

Tastes

Strelka's file distribution system assigns scanners to files based on 'flavors' and 'tastes'. Flavors describe the type of file, typically determined by MIME types from libmagic, matches from YARA rules, or characteristics of parent files. Tastes are the criteria used within Strelka to determine which scanners are applied to which files, with positive and negative tastes defining files to be included or excluded respectively.

Source Filetype
Include / Exclude
hta_file
html_file
text/html

Scanner Fields

This section provides a list of fields that are extracted from the files processed by this scanner. These fields include the data elements that the scanner extracts from each file, representing the analytical results produced by the scanner. If the test file is missing or cannot be parsed, this section will not contain any data.

Field Name
Field Type
elapsed
str
flags
list
forms
list
frames
list
hyperlinks
list
hyperlinks_count
int
inputs
list
scripts
list
scripts.language
NoneType
scripts.src
NoneType
scripts.src
str
scripts.type
NoneType
scripts.type
str
spans
list
spans.class
NoneType
spans.style
str
title
str
total
dict
total.extracted
int
total.forms
int
total.frames
int
total.inputs
int
total.scripts
int
total.spans
int

Sample Event

Below is a sample event generated by this scanner, demonstrating the kind of output that can be expected when it processes a file. This sample is derived from a mock scan event configured in the scanner's test file. If no test file is available, this section will not display a sample event.

    test_scan_event = {
        "elapsed": 0.001,
        "flags": [],
        "total": {
            "scripts": 2,
            "forms": 0,
            "inputs": 0,
            "frames": 0,
            "extracted": 1,
            "spans": 35,
        },
        "title": "Lorem Ipsum",
        "hyperlinks": [],
        "forms": [],
        "frames": [],
        "inputs": [],
        "scripts": [
            {
                "src": "https://example.com/example.js",
                "language": None,
                "type": "text/javascript",
            },
            {"src": None, "language": None, "type": None},
        ],
        "spans": [
            {"class": None, "style": "font-size:11pt"},
            {"class": None, "style": "background-color:white"},
            {
                "class": None,
                "style": "font-family:Calibri,sans-serif",
            },
            {"class": None, "style": "font-size:52.5pt"},
            {"class": None, "style": "color:black"},
            {"class": None, "style": "font-size:12pt"},
            {
                "class": None,
                "style": 'font-family:"Times New Roman",serif',
            },
            {"class": None, "style": "font-size:10.5pt"},
            {
                "class": None,
                "style": 'font-family:"Arial",sans-serif',
            },
        ],
    }