{
  "version": "Notebook/1.0",
  "items": [
    {
      "type": 1,
      "content": {
        "json": "## HoneyLabs Threat Intelligence\n\nIndicators from internet-facing honeypot sensors. Every one is **evidence-backed**: the IP ran an exploit or loader command against a sensor, or the URL was pulled out of a payload those commands fetched. Known research scanners are excluded and indicators expire as activity stops.\n\n`Confidence` grades the evidence behind each indicator (90 = 100+ observed attacks, 60 = a single sighting), so you can pick an alerting threshold instead of muting the feed. Labels carry the source network (`asn:ASxxxx`), origin (`country:XX`) and the indicator kind."
      },
      "name": "Header"
    },
    {
      "type": 9,
      "content": {
        "version": "KqlParameterItem/1.0",
        "parameters": [
          {
            "id": "a1b2c3d4-0000-4000-8000-000000000001",
            "version": "KqlParameterItem/1.0",
            "name": "TimeRange",
            "label": "Time range",
            "type": 4,
            "isRequired": true,
            "typeSettings": {
              "selectableValues": [
                {
                  "durationMs": 86400000
                },
                {
                  "durationMs": 604800000
                },
                {
                  "durationMs": 2592000000
                },
                {
                  "durationMs": 7776000000
                }
              ]
            },
            "timeContext": {
              "durationMs": 604800000
            },
            "value": {
              "durationMs": 2592000000
            }
          }
        ],
        "style": "pills",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces"
      },
      "name": "Parameters"
    },
    {
      "type": 1,
      "content": {
        "json": "### Feed health"
      },
      "name": "Health header"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| summarize Indicators = dcount(Id), LastReceived = max(TimeGenerated)\n| extend Status = iff(isempty(LastReceived), 'No HoneyLabs indicators yet: check the TAXII connector', 'Connected')\n| project Status, Indicators, LastReceived",
        "size": 4,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "title": "Connection",
        "noDataMessage": "No HoneyLabs indicators in this workspace yet. See 'Connecting the feed' at the bottom of this workbook for the TAXII server details."
      },
      "name": "Feed status"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| extend ActivityTime = todatetime(todynamic(Data).valid_from)\n| summarize Indicators = dcount(Id) by bin(ActivityTime, 1d)\n| order by ActivityTime asc",
        "size": 0,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "timechart",
        "title": "Indicator activity over time"
      },
      "name": "Ingest over time"
    },
    {
      "type": 1,
      "content": {
        "json": "### Choosing an alerting threshold\n\nHigher confidence means more observed attacks behind the indicator. A practical start is to alert on the top band and watchlist the rest, then widen once you see the real volume in your environment."
      },
      "name": "Tuning header"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| summarize Indicators = count() by Confidence\n| extend Evidence = case(Confidence >= 90, '100+ observed attacks', Confidence >= 80, '10-99 observed attacks', Confidence >= 70, '3-9 observed attacks', '1-2 observed attacks')\n| project Confidence, Evidence, Indicators\n| sort by Confidence desc",
        "size": 1,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "title": "Active indicators by confidence"
      },
      "name": "Confidence bands"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| extend Kind = case(ObservableKey == 'url:value', 'Malware loader/C2 URL', ObservableKey == 'ipv4-addr:value', 'Exploiter source IP', 'Other')\n| summarize Indicators = count() by Kind",
        "size": 1,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "piechart",
        "title": "What the feed contains"
      },
      "name": "Kind split"
    },
    {
      "type": 1,
      "content": {
        "json": "### Where the attacks come from\n\nConcentration by network is what makes a blocking decision defensible: a handful of ASNs usually carry a disproportionate share of the traffic."
      },
      "name": "Networks header"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| mv-expand Label = Data.labels to typeof(string)\n| where Label startswith 'asn:'\n| extend ASN = replace_string(Label, 'asn:', '')\n| summarize Indicators = count() by ASN\n| top 15 by Indicators desc",
        "size": 1,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "barchart",
        "title": "Top source networks"
      },
      "name": "Top ASNs"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| mv-expand Label = Data.labels to typeof(string)\n| where Label startswith 'country:'\n| extend Country = replace_string(Label, 'country:', '')\n| summarize Indicators = count() by Country\n| top 15 by Indicators desc",
        "size": 1,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "barchart",
        "title": "Top origin countries"
      },
      "name": "Top countries"
    },
    {
      "type": 1,
      "content": {
        "json": "### Which CVEs are being probed in the wild\n\nAvailable on paid HoneyLabs plans. Each of these indicators is a source IP that went after a specific CVE's exploit path against a sensor, labelled with the CVE ids it targeted. Unlike a vendor severity score this is an observed, in-the-wild signal, so it is a practical input to patch prioritisation: cross-reference the top rows against what you actually run and expose."
      },
      "name": "CVE header"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| mv-expand Label = Data.labels to typeof(string)\n| where Label startswith 'cve-'\n| extend CVE = toupper(tostring(Label))\n| summarize ProbingSources = dcount(Id), Evidence = max(Confidence) by CVE\n| top 20 by ProbingSources desc\n| extend Report = strcat('https://honeylabs.net/cves/', CVE)",
        "size": 0,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "title": "Most-probed CVEs by number of distinct source IPs",
        "gridSettings": {
          "formatters": [
            {
              "columnMatch": "Report",
              "formatter": 7,
              "formatOptions": {
                "linkTarget": "Url",
                "linkLabel": "Who is probing it"
              }
            },
            {
              "columnMatch": "ProbingSources",
              "formatter": 8,
              "formatOptions": {
                "palette": "red"
              }
            }
          ]
        }
      },
      "name": "CVEs probed"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| where Data.labels has 'cve-probing'\n| extend ActivityTime = todatetime(todynamic(Data).valid_from)\n| summarize ProbingSources = dcount(Id) by bin(ActivityTime, 1d)\n| order by ActivityTime asc",
        "size": 0,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "timechart",
        "title": "CVE-probing sources over time"
      },
      "name": "CVE probers over time"
    },
    {
      "type": 1,
      "content": {
        "json": "### Matches in your environment\n\nWhere these indicators line up with your own traffic. An empty panel here is good news. Matches are ordered by confidence so the best-evidenced hits surface first."
      },
      "name": "Matches header"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| where ObservableKey == 'ipv4-addr:value'\n| extend TI_ip = tostring(ObservableValue)\n| join kind=innerunique (CommonSecurityLog\n    | where TimeGenerated {TimeRange}\n    | where isnotempty(SourceIP)\n    | extend CS_ip = SourceIP) on $left.TI_ip == $right.CS_ip\n| summarize Matches = count() by bin(TimeGenerated, 1h)",
        "size": 0,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "timechart",
        "title": "Matches against CommonSecurityLog",
        "noDataMessage": "No CommonSecurityLog data in this workspace. This panel matches HoneyLabs indicators against firewall and proxy logs in CEF format; the ASIM panel below covers workspaces that normalise network data instead.",
        "noDataMessageStyle": 4
      },
      "name": "Matches over time"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| where ObservableKey == 'ipv4-addr:value'\n| extend TI_ip = tostring(ObservableValue),\n         Report = strcat('https://honeylabs.net/lookup/', tostring(ObservableValue))\n| join kind=innerunique (\n    _Im_NetworkSession(starttime=ago(7d))\n    | where isnotempty(SrcIpAddr)\n    | summarize Sessions = count(), LastSeen = max(TimeGenerated) by SrcIpAddr\n  ) on $left.TI_ip == $right.SrcIpAddr\n| project TI_ip, Confidence, Sessions, LastSeen, Report\n| order by Confidence desc, Sessions desc\n| take 50",
        "size": 0,
        "title": "Matches against normalised network data (ASIM)",
        "noDataMessage": "No ASIM network sessions in this workspace yet. This panel needs a source that populates _Im_NetworkSession, such as Azure Firewall, AWS VPC flow logs or a normalised syslog feed. Nothing is wrong with the HoneyLabs feed itself: the Feed health panel above shows whether indicators are arriving.",
        "noDataMessageStyle": 4,
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "gridSettings": {
          "formatters": [
            {
              "columnMatch": "Report",
              "formatter": 7,
              "formatOptions": {
                "linkTarget": "Url",
                "linkLabel": "View evidence"
              }
            },
            {
              "columnMatch": "Confidence",
              "formatter": 8,
              "formatOptions": {
                "palette": "blue"
              }
            }
          ]
        }
      },
      "name": "asim-matches"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "ThreatIntelIndicators\n| where TimeGenerated {TimeRange}\n| where IsActive == true and ValidUntil > now()\n| summarize arg_max(TimeGenerated, *) by Id\n| where SourceSystem startswith 'HoneyLabs'\n| where ObservableKey == 'ipv4-addr:value'\n| extend TI_ip = tostring(ObservableValue)\n| join kind=innerunique (CommonSecurityLog\n    | where TimeGenerated {TimeRange}\n    | where isnotempty(SourceIP)\n    | extend CS_ip = SourceIP) on $left.TI_ip == $right.CS_ip\n| summarize Events = count(), Hosts = dcount(DeviceName), Blocked = countif(DeviceAction has_any ('block', 'deny', 'drop')), LastSeen = max(TimeGenerated) by TI_ip, Confidence\n| extend Report = strcat('https://honeylabs.net/lookup/', TI_ip)\n| sort by Confidence desc, Events desc\n| take 50",
        "size": 0,
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "title": "Matched indicators (highest evidence first)",
        "noDataMessage": "No CommonSecurityLog data in this workspace. This panel matches HoneyLabs indicators against firewall and proxy logs in CEF format; the ASIM panel below covers workspaces that normalise network data instead.",
        "noDataMessageStyle": 4
      },
      "name": "Matched detail"
    },
    {
      "type": 1,
      "content": {
        "json": "### Connecting the feed\n\nIndicators reach this workbook through Microsoft Sentinel's built-in **Threat Intelligence - TAXII** data connector, which polls the HoneyLabs TAXII 2.1 server. A free API key from [honeylabs.net/dashboard](https://honeylabs.net/dashboard?src=sentinel) is the only prerequisite; it is the password below and the username can be any string.\n\n| Field | Value |\n|---|---|\n| API root URL | `https://honeylabs.net/taxii2/api/` |\n| Attacker IPs collection | `019bc26f-7216-562c-b110-16ccd9c553f6` |\n| Malware infrastructure collection | `e144c129-a19a-55c8-b926-dd2dfbbd8138` |\n| Username | `taxii` |\n| Polling frequency | hourly |\n\nStart with the attacker IPs collection, which is the core of the dataset. The malware infrastructure collection carries the loader and command-and-control URLs pulled out of captured payloads, and is kept separate so it does not dilute the IPs. Full notes: [honeylabs.net/integrations/sentinel](https://honeylabs.net/integrations/sentinel)."
      },
      "name": "Connecting the feed"
    },
    {
      "type": 1,
      "content": {
        "json": "Each row links to its full HoneyLabs report: first seen, the exact requests, payloads and CVEs probed. Data from [HoneyLabs](https://honeylabs.net)."
      },
      "name": "Footer"
    }
  ],
  "styleSettings": {},
  "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
}