diff --git a/DESCRIPTION b/DESCRIPTION index 7e5490a..e41d624 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,6 +30,9 @@ Imports: jsonlite, tibble, ggplot2, + readr, + glue, + dplyr, shiny, stringi, rmarkdown diff --git a/NAMESPACE b/NAMESPACE index 978c6eb..98977f4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,8 @@ # Generated by roxygen2: do not edit by hand export(fct_tactic) +export(read_events) +export(tactics_f) export(validate_tactics) export(validate_technique_ids) export(validate_techniques) @@ -9,4 +11,11 @@ import(rmarkdown) import(shiny) import(stringi) import(tibble) +importFrom(dplyr,case_when) +importFrom(dplyr,distinct) +importFrom(dplyr,filter) +importFrom(dplyr,mutate) +importFrom(dplyr,select) +importFrom(glue,glue) importFrom(jsonlite,fromJSON) +importFrom(readr,read_csv) diff --git a/R/attckr-package.R b/R/attckr-package.R index 8a5f16e..453502a 100644 --- a/R/attckr-package.R +++ b/R/attckr-package.R @@ -14,5 +14,8 @@ #' @docType package #' @author Bob Rudis (bob@@rud.is) #' @import tibble ggplot2 shiny rmarkdown stringi +#' @importFrom glue glue +#' @importFrom dplyr filter distinct mutate select case_when +#' @importFrom readr read_csv #' @importFrom jsonlite fromJSON "_PACKAGE" \ No newline at end of file diff --git a/R/fct-tactic.R b/R/fct-tactic.R index 12c846c..5a6f73c 100644 --- a/R/fct-tactic.R +++ b/R/fct-tactic.R @@ -1,30 +1,10 @@ -.tactics_f <- list() - -.tactics_f[["mitre-attack"]] <- list( - id = c( - "initial-access", "execution", "persistence", "privilege-escalation", - "defense-evasion", "credential-access", "discovery", "lateral-movement", - "collection", "command-and-control", "exfiltration", "impact" - ), - pretty = c( - "Initial Access", "Execution", "Persistence", "Privilege Escalation", - "Defense Evasion", "Credential Access", "Discovery", "Lateral Movement", - "Collection", "Command And Control", "Exfiltration", "Impact" - ), - nl = c( - "Initial\nAccess", "Execution", "Persistence", "Privilege\nEscalation", - "Defense\nEvasion", "Credential\nAccess", "Discovery", "Lateral\nMovement", - "Collection", "Command\nAnd\nControl", "Exfiltration", "Impact" - ) -) - #' Make an ordered Tactics factor with optional better labelling #' -#' #' @param tactics a character vector #' @param input what is in `tactics`? #' @param output what do you want the factor label to be? #' @param matrix which matrix? +#' @seealso [tactics_f] for direct access to the ordered Tactics #' @export fct_tactic <- function(tactics, input = c("id", "pretty", "nl"), @@ -42,11 +22,9 @@ fct_tactic <- function(tactics, pre = "mitre-pre-attack" ) -> tax - input <- .tactics_f[[tax]][[input]] - output <- .tactics_f[[tax]][[output]] + input <- tactics_f[[tax]][[input]] + output <- tactics_f[[tax]][[output]] factor(x = tactics, levels = input, labels = output, ordered = TRUE) } - - diff --git a/R/read-events.R b/R/read-events.R index 1b25bbb..4181ac7 100644 --- a/R/read-events.R +++ b/R/read-events.R @@ -1,6 +1,109 @@ #' Read in ATT&CK events from a file -read_events <- function(path) { +#' +#' This is a convenience wrapper for [readr::read_csv()] that sets up +#' a contract to read in incident events with some pre-determined expectations. +#' See Details for more information. +#' +#' While sufficient metadata and helpers have been provided with this package +#' to enable customized use of the ATT&CK matricies sometimes you just want +#' to get stuff done quickly and for that we need to establish some ground rules. +#' +#' This function defines and "incident event" record as something that +#' contains the fields: +#' +#' - `event_id`: a unique identifier for this event +#' - `incident_id`: the associated incident for the `event_id`; again, a unique +#' identifier for each incident +#' - `event_ts`: the timestamp for when the event occurred (anything date-like) +#' - `detection_ts`: the timestamp for when the event was detected (anything date-like) +#' - `tactic`: the ATT&CK Tactic; can be in "id" format (dashed lowercase), "pretty" +#' (spaces, titlecase), or "newline" (newlines, titlecase) +#' - `technique`: the ATT&CK Technique id or precise spelling if spelled out +#' - `discovery_source`: free text field (it should still be "identifier-ish") +#' that helps pinpoint which control/logging source enabled discovery of the +#' event. +#' - `reporting_source`: free text field (it should still be "identifier-ish") +#' that identifies what did the reporting for the `discovery_source`. +#' - `responder_id` the id of the incident reponder associated with this +#' combination of `event_id` and `incident_id`. +#' +#' You can think of `discovery_source` & `reporting_source` this way: say +#' the Windows Event Log captured the evidence of a failed (or successful) +#' local admin logon event. It passes that on to your centralized logging +#' facility and/or your SIEM. You can make `discovery_source` "`Windows Event Log`" +#' and `reporting_source` whichever technology you used. +#' +#' Any column not-present will be turned into `NA`. Columns not matching the +#' above names will be removed from the object returned. +#' @param path path to a CSV file that contains ATT&CK events. This will be [path.expand()]ed. +#' @param matrix which matrix are the events associated with? +#' @param ... passed on to [readr::read_csv()] +#' @export +#' @examples +#' read_events(system.file("extdat/sample-incidents.csv.gz", package = "attckr")) +read_events <- function(path, matrix = c("enterprise", "mobile", "pre"), ...) { -} + matrix <- match.arg(matrix[1], c("enterprise", "mobile", "pre")) + + switch( + matrix, + enterprise = "mitre-attack", + mobile = "mitre-mobile-attack", + pre = "mitre-pre-attack" + ) -> tax + + path <- path.expand(path[1]) + + stopifnot(file.exists(path)) + + xdf <- suppressWarnings(readr::read_csv(path, ...)) + + c( + "event_id", "incident_id", "event_ts", "detection_ts", "tactic", + "technique", "discovery_source", "reporting_source", "responder_id" + ) -> req_fields + col_miss <- setdiff(req_fields, colnames(xdf)) + if (length(col_miss)) for (fld in col_miss) xdf[[fld]] <- NA_character_ + xdf <- xdf[,req_fields] + + if (any(grepl("\n", xdf[["tactic"]]))) { + fct_tactic( + tactics = xdf[["tactic"]], + input = "nl", + output = "pretty", + matrix = matrix + ) -> xdf[["tactic"]] + message("You appear to be using Tactic full names with crlfs.") + } else if (any(grepl("[[:upper:]]", xdf[["tactic"]]))) { + fct_tactic( + tactics = xdf[["tactic"]], + input = "pretty", + output = "pretty", + matrix = matrix + ) -> xdf[["tactic"]] + message("You appear to be using Tactic full names.") + } else if (any(grepl("-", xdf[["tactic"]]))) { + fct_tactic( + tactics = xdf[["tactic"]], + input = "id", + output = "pretty", + matrix = matrix + ) -> xdf[["tactic"]] + message("You appear to be using Tactic ids.") + } else { + warning("Could not identify Tactic encoding.", call. = FALSE) + } + + if (all(grepl("^[[:upper:]]+[\\-]*[[:digit:]]+$", xdf[["technique"]]))) { + message("You appear to be using Techinque ids.") + } else if (any(tidy_attack$technique %in% xdf[["technique"]])) { + message("You appear to be using Technique full names.") + } else { + warning("Could not determine if you are using Technique ids or names.", call. = FALSE) + } + + xdf + +} diff --git a/R/tactics-factors.R b/R/tactics-factors.R new file mode 100644 index 0000000..4fa571f --- /dev/null +++ b/R/tactics-factors.R @@ -0,0 +1,76 @@ +tactics_f <- list() + +tactics_f[["mitre-attack"]] <- tibble( + id = c( + "initial-access", "execution", "persistence", "privilege-escalation", + "defense-evasion", "credential-access", "discovery", "lateral-movement", + "collection", "command-and-control", "exfiltration", "impact" + ), + pretty = c( + "Initial Access", "Execution", "Persistence", "Privilege Escalation", + "Defense Evasion", "Credential Access", "Discovery", "Lateral Movement", + "Collection", "Command & Control", "Exfiltration", "Impact" + ), + nl = c( + "Initial\nAccess", "Execution", "Persistence", "Privilege\nEscalation", + "Defense\nEvasion", "Credential\nAccess", "Discovery", "Lateral\nMovement", + "Collection", "Command\n&\nControl", "Exfiltration", "Impact" + ) +) + +tactics_f[["mitre-pre-attack"]] <- tibble( + id = c( + "priority-definition-planning", "priority-definition-direction", + "target-selection", "technical-information-gathering", + "people-information-gathering", "organizational-information-gathering", + "technical-weakness-identification", "people-weakness-identification", + "organizational-weakness-identification", "adversary-opsec", + "establish-&-maintain-infrastructure", "persona-development", + "build-capabilities", "test-capabilities", "stage-capabilities", + "launch", "compromise" + ), + pretty = c( + "Priority Definition Planning", "Priority Definition Direction", + "Target Selection", "Technical Information Gathering", "People Information Gathering", + "Organizational Information Gathering", "Technical Weakness Identification", + "People Weakness Identification", "Organizational Weakness Identification", + "Adversary Opsec", "Establish & Maintain Infrastructure", "Persona Development", + "Build Capabilities", "Test Capabilities", "Stage Capabilities", + "Launch", "Compromise" + ), + nl = c( + "Priority\nDefinition\nPlanning", "Priority\nDefinition\nDirection", + "Target\nSelection", "Technical\nInformation\nGathering", "People\nInformation\nGathering", + "Organizational\nInformation\nGathering", "Technical\nWeakness\nIdentification", + "People\nWeakness\nIdentification", "Organizational\nWeakness\nIdentification", + "Adversary\nOpsec", "Establish\n&\nMaintain\nInfrastructure", + "Persona\nDevelopment", "Build\nCapabilities", "Test\nCapabilities", + "Stage\nCapabilities", "Launch", "Compromise" + ) +) + +tactics_f[["mitre-mobile-attack"]] <- tibble( + id = c( + "initial-access", "persistence", "privilege-escalation", "defense-evasion", + "credential-access", "discovery", "lateral-movement", "effects", "network-effects", + "remote-service-effects", "collection", "exfiltration", "command-and-control" + ), + pretty = c( + "Initial Access", "Persistence", "Privilege Escalation", "Defense Evasion", + "Credential Access", "Discovery", "Lateral Movement", "Effects", + "Network Effects", "Remote Service Effects", "Collection", "Exfiltration", + "Command & Control" + ), + nl = c( + "Initial\nAccess", "Persistence", "Privilege\nEscalation", + "Defense\nEvasion", "Credential\nAccess", "Discovery", "Lateral\nMovement", + "Effects", "Network\nEffects", "Remote\nService\nEffects", "Collection", + "Exfiltration", "Command\n&\nControl" + ) +) + +#' @title Tactics factors (generally for sorting & pretty-printing) +#' @name tactics_f +#' @docType data +#' @export +NULL diff --git a/README.Rmd b/README.Rmd index 8bb22eb..ac4bf62 100644 --- a/README.Rmd +++ b/README.Rmd @@ -6,7 +6,7 @@ hrbrpkghelpr::global_opts() ``` ```{r badges, results='asis', echo=FALSE, cache=FALSE} -hrbrpkghelpr::stinking_badges() +hrbrpkghelpr::stinking_badges(repo_status = "WIP") ``` ```{r description, results='asis', echo=FALSE, cache=FALSE} @@ -31,12 +31,40 @@ hrbrpkghelpr::install_block() ```{r lib-ex} library(attckr) +library(tidyverse) # current version packageVersion("attckr") ``` +```{r} +tidy_attack +``` + +```{r events, fig.width=11, fig.height=6} +events <- read_events(system.file("extdat/sample-incidents.csv.gz", package = "attckr")) + +count(head(events, 30), tactic, technique) %>% + mutate(tactic = fct_tactic(tactic, "pretty", "nl")) %>% + left_join( + filter(tidy_attack, matrix == "mitre-attack") %>% + distinct(id, technique), + c("technique" = "id") + ) %>% + complete(tactic, technique.y) %>% + mutate(technique.y = factor(technique.y, rev(sort(unique(technique.y))))) %>% + ggplot(aes(tactic, technique.y)) + + geom_tile(aes(fill = n), color = "#2b2b2b", size = 0.125) + + scale_x_discrete(expand = c(0, 0), position = "top") + + scale_fill_viridis_c(direction = -1, na.value = "white") + + labs( + x = NULL, y = NULL + ) + + theme_minimal() + + theme(panel.grid=element_blank()) +``` + ## attckr Metrics ```{r cloc, echo=FALSE} diff --git a/README.md b/README.md index c5166c8..7343f6b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -[![Project Status: Active – The project has reached a stable, usable -state and is being actively -developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![Project Status: WIP – Initial development is in progress, but there +has not yet been a stable, usable release suitable for the +public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) [![Signed by](https://img.shields.io/badge/Keybase-Verified-brightgreen.svg)](https://keybase.io/hrbrmstr) ![Signed commit -%](https://img.shields.io/badge/Signed_Commits-66.7%25-lightgrey.svg) +%](https://img.shields.io/badge/Signed_Commits-75.0%25-lightgrey.svg) [![Linux build Status](https://travis-ci.org/hrbrmstr/attckr.svg?branch=master)](https://travis-ci.org/hrbrmstr/attckr) ![Minimal R @@ -38,6 +38,8 @@ The following functions are implemented: - `mobile_attack`: Mobile Attack Taxonomy - `pre_attack`: Pre-Attack Taxonomy - `read_events`: Read in ATT\&CK events from a file + - `tactics_f`: Tactics factors (generally for sorting & + pretty-printing) - `tidy_attack`: Combined ATT\&CK Matricies Tactics, Techniques and Technique detail - `validate_tactics`: Validate Tactics strings against MITRE @@ -65,18 +67,62 @@ NOTE: To use the ‘remotes’ install options you will need to have the ``` r library(attckr) +library(tidyverse) # current version packageVersion("attckr") ## [1] '0.1.0' ``` +``` r +tidy_attack +## # A tibble: 708 x 5 +## technique description id tactic matrix +## +## 1 .bash_profile and… "~/.bash_profile and ~/.bashrc are exec… T1156 persistence mitre-at… +## 2 Access Token Mani… "Windows uses access tokens to determine the ownership of a runni… T1134 defense-evas… mitre-at… +## 3 Access Token Mani… "Windows uses access tokens to determine the ownership of a runni… T1134 privilege-es… mitre-at… +## 4 Accessibility Fea… "Windows contains accessibility features that may be launched wit… T1015 persistence mitre-at… +## 5 Accessibility Fea… "Windows contains accessibility features that may be launched wit… T1015 privilege-es… mitre-at… +## 6 Accessibility Fea… "Windows contains accessibility features that may be launched wit… CAPEC-… persistence mitre-at… +## 7 Accessibility Fea… "Windows contains accessibility features that may be launched wit… CAPEC-… privilege-es… mitre-at… +## 8 Account Discovery "Adversaries may attempt to get a listing of local system or doma… T1087 discovery mitre-at… +## 9 Account Discovery "Adversaries may attempt to get a listing of local system or doma… CAPEC-… discovery mitre-at… +## 10 Account Manipulat… Account manipulation may aid adversaries in maintaining access to… T1098 credential-a… mitre-at… +## # … with 698 more rows +``` + +``` r +events <- read_events(system.file("extdat/sample-incidents.csv.gz", package = "attckr")) + +count(head(events, 30), tactic, technique) %>% + mutate(tactic = fct_tactic(tactic, "pretty", "nl")) %>% + left_join( + filter(tidy_attack, matrix == "mitre-attack") %>% + distinct(id, technique), + c("technique" = "id") + ) %>% + complete(tactic, technique.y) %>% + mutate(technique.y = factor(technique.y, rev(sort(unique(technique.y))))) %>% + ggplot(aes(tactic, technique.y)) + + geom_tile(aes(fill = n), color = "#2b2b2b", size = 0.125) + + scale_x_discrete(expand = c(0, 0), position = "top") + + scale_fill_viridis_c(direction = -1, na.value = "white") + + labs( + x = NULL, y = NULL + ) + + theme_minimal() + + theme(panel.grid=element_blank()) +``` + + + ## attckr Metrics -| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) | -| :--- | -------: | ---: | --: | ---: | ----------: | ---: | -------: | ---: | -| R | 10 | 0.91 | 168 | 0.95 | 50 | 0.77 | 72 | 0.74 | -| Rmd | 1 | 0.09 | 8 | 0.05 | 15 | 0.23 | 25 | 0.26 | +| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) | +| :--- | -------: | ---: | --: | --: | ----------: | ---: | -------: | ---: | +| R | 11 | 0.92 | 270 | 0.9 | 59 | 0.77 | 121 | 0.81 | +| Rmd | 1 | 0.08 | 29 | 0.1 | 18 | 0.23 | 29 | 0.19 | ## Code of Conduct diff --git a/inst/extdat/sample-incidents.csv.gz b/inst/extdat/sample-incidents.csv.gz new file mode 100644 index 0000000..b27265e Binary files /dev/null and b/inst/extdat/sample-incidents.csv.gz differ diff --git a/man/fct_tactic.Rd b/man/fct_tactic.Rd index b86d7c0..383e8ee 100644 --- a/man/fct_tactic.Rd +++ b/man/fct_tactic.Rd @@ -20,3 +20,6 @@ fct_tactic(tactics, input = c("id", "pretty", "nl"), \description{ Make an ordered Tactics factor with optional better labelling } +\seealso{ +\link{tactics_f} for direct access to the ordered Tactics +} diff --git a/man/figures/README-events-1.png b/man/figures/README-events-1.png new file mode 100644 index 0000000..0614d74 Binary files /dev/null and b/man/figures/README-events-1.png differ diff --git a/man/read_events.Rd b/man/read_events.Rd index 7ee7f30..3920013 100644 --- a/man/read_events.Rd +++ b/man/read_events.Rd @@ -4,8 +4,54 @@ \alias{read_events} \title{Read in ATT&CK events from a file} \usage{ -read_events(path) +read_events(path, matrix = c("enterprise", "mobile", "pre"), ...) +} +\arguments{ +\item{path}{path to a CSV file that contains ATT&CK events. This will be \code{\link[=path.expand]{path.expand()}}ed.} + +\item{matrix}{which matrix are the events associated with?} + +\item{...}{passed on to \code{\link[readr:read_csv]{readr::read_csv()}}} } \description{ -Read in ATT&CK events from a file +This is a convenience wrapper for \code{\link[readr:read_csv]{readr::read_csv()}} that sets up +a contract to read in incident events with some pre-determined expectations. +See Details for more information. +} +\details{ +While sufficient metadata and helpers have been provided with this package +to enable customized use of the ATT&CK matricies sometimes you just want +to get stuff done quickly and for that we need to establish some ground rules. + +This function defines and "incident event" record as something that +contains the fields: +\itemize{ +\item \code{event_id}: a unique identifier for this event +\item \code{incident_id}: the associated incident for the \code{event_id}; again, a unique +identifier for each incident +\item \code{event_ts}: the timestamp for when the event occurred (anything date-like) +\item \code{detection_ts}: the timestamp for when the event was detected (anything date-like) +\item \code{tactic}: the ATT&CK Tactic; can be in "id" format (dashed lowercase), "pretty" +(spaces, titlecase), or "newline" (newlines, titlecase) +\item \code{technique}: the ATT&CK Technique id or precise spelling if spelled out +\item \code{discovery_source}: free text field (it should still be "identifier-ish") +that helps pinpoint which control/logging source enabled discovery of the +event. +\item \code{reporting_source}: free text field (it should still be "identifier-ish") +that identifies what did the reporting for the \code{discovery_source}. +\item \code{responder_id} the id of the incident reponder associated with this +combination of \code{event_id} and \code{incident_id}. +} + +You can think of \code{discovery_source} & \code{reporting_source} this way: say +the Windows Event Log captured the evidence of a failed (or successful) +local admin logon event. It passes that on to your centralized logging +facility and/or your SIEM. You can make \code{discovery_source} "\code{Windows Event Log}" +and \code{reporting_source} whichever technology you used. + +Any column not-present will be turned into \code{NA}. Columns not matching the +above names will be removed from the object returned. +} +\examples{ +read_events(system.file("extdat/sample-incidents.csv.gz", package = "attckr")) } diff --git a/man/tactics_f.Rd b/man/tactics_f.Rd new file mode 100644 index 0000000..271ea51 --- /dev/null +++ b/man/tactics_f.Rd @@ -0,0 +1,9 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tactics-factors.R +\docType{data} +\name{tactics_f} +\alias{tactics_f} +\title{Tactics factors (generally for sorting & pretty-printing)} +\description{ +Tactics factors (generally for sorting & pretty-printing) +} diff --git a/tools/fill-mat.R b/tools/fill-mat.R index 3cb23ab..b26fecd 100644 --- a/tools/fill-mat.R +++ b/tools/fill-mat.R @@ -1,6 +1,15 @@ +library(hrbrthemes) +library(tidyverse) + +distinct(tidy_attack, id, tactic, technique, matrix) + +table(nchar(tidy_attack$id)) +table(substr(tidy_attack$technique[which(nchar(tidy_attack$technique) < 9)], 1,1 )) + filter(tidy_attack, matrix == "mitre-attack") %>% distinct(tactic, technique) %>% - mutate(tactic = fct_tactic("id", "nl")) %>% + arrange(technique) %>% + mutate(tactic = fct_tactic(tactic, "id", "nl")) %>% group_by(tactic) %>% mutate(ypos = 1:n()) %>% ggplot(aes(tactic, ypos)) + @@ -15,7 +24,123 @@ filter(tidy_attack, matrix == "mitre-attack") %>% theme(panel.grid = element_blank()) + theme(axis.text.y = element_blank()) +library(igraph) +library(ggraph) + +filter(tidy_attack, matrix == "mitre-attack") %>% + distinct(id, tactic) %>% + select(2, 1) -> gdf + +g <- graph_from_data_frame(gdf) + +tactic_v <- c("initial-access", "persistence", "defense-evasion", "privilege-escalation", "discovery", "credential-access", "execution", "lateral-movement", "collection", "exfiltration", "command-and-control") + +strength_df <- enframe(strength(g, vids = V(g), "in")) + +e_focus <- filter(strength_df, (value > 1), !(name %in% tactic_v)) %>% pull(name) +strength_df %>% + mutate( + fil = case_when( + name %in% tactic_v ~ ft_cols$blue, + value > 1 ~ ft_cols$slate, + TRUE ~ "white" + ), + col = case_when( + name %in% tactic_v ~ ft_cols$yellow, + value > 1 ~ "white", + TRUE ~ ft_cols$slate + ), + lab_size = case_when( + name %in% tactic_v ~ 4, + TRUE ~ 2 + ) + ) -> vdf + +g <- graph_from_data_frame(gdf, vertices = vdf) + +map_chr(E(g), ~{ + if (any(unlist(strsplit(attr(.x, "vnames"), "|", fixed = TRUE)) %in% e_focus)) { + ft_cols$red + } else { + ft_cols$gray + } +}) -> E(g)$ecol + +map_dbl(E(g), ~{ + if (any(unlist(strsplit(attr(.x, "vnames"), "|", fixed = TRUE)) %in% e_focus)) { + 0.25 + } else { + 0.125 + } +}) -> E(g)$ewid + +minC <- rep(-Inf, vcount(g)) +maxC <- rep(Inf, vcount(g)) +minC[1] <- maxC[1] <- 0 + +ggraph(g, layout = "fr", niter = 5000, start.temp = 2*(vcount(g)), minx=minC, maxx=maxC, miny=minC, maxy=maxC) + + geom_edge_link( + aes(colour = I(ecol), width = I(ewid)), alpha=1/4 + ) + + geom_node_label( + aes(label = name, fill = I(fil), color = I(col), size = I(lab_size)), + family = font_es + ) + + labs( + x = NULL, y = NULL + ) + + theme_ft_rc(grid="") + + theme(axis.text = element_blank()) + + + + +c( + "event_id", "incident_id", "event_ts", "detection_ts", "tactic", + "technique", "discovery_source", "reporting_source", "responder_id" +) + +n <- 200 filter(tidy_attack, matrix == "mitre-attack") %>% - distinct(tactic, technique) + distinct(tactic, id) -> pre + +ttidx <- sample(nrow(pre), n, replace = TRUE) + +tibble( + event_id = ulid::ulid_generate(n), + incident_id = sample(ulid::ulid_generate(30), n, replace = TRUE), + event_ts = sample(seq(as.Date("2019-04-01"), as.Date("2019-06-29"), "1 day"), n, replace = TRUE), + detection_ts = event_ts + 1, + tactic = pre[["tactic"]][ttidx], + technique = pre[["id"]][ttidx], + discovery_source = sample(c("Log src 1", "Log src 2", "Log src 3"), n, replace = TRUE), + reporting_source = rep("Insight IDR", n), + responder_id = sample(c("Thing one", "Thing two"), n, replace = TRUE) +) -> smpl + +write_csv(smpl, "inst/extdat/sample-incidents.csv.gz") + +count(xx, tactic, technique) %>% + mutate(tactic = fct_tactic(tactic, "pretty", "nl")) %>% + left_join( + filter(tidy_attack, matrix == "mitre-attack") %>% + distinct(id, technique), + c("technique" = "id") + ) %>% + complete(tactic, technique.y) %>% + mutate(technique.y = factor(technique.y, rev(sort(unique(technique.y))))) %>% + ggplot(aes(tactic, technique.y)) + + geom_tile(aes(fill = n), color = "#2b2b2b", size = 0.125) + + scale_x_discrete(expand = c(0, 0), position = "top") + + scale_fill_viridis_c(direction = -1, na.value = "white") + + labs( + x = NULL, y = NULL + ) + + theme_minimal() + + theme(panel.grid=element_blank()) + +count(xx, detection_ts) %>% + ggplot() + + geom_col(aes(detection_ts, n), width=0.6)