boB Rudis 5 years ago
parent
commit
244d431846
No known key found for this signature in database GPG Key ID: 1D7529BE14E2BBA9
  1. 3
      DESCRIPTION
  2. 9
      NAMESPACE
  3. 3
      R/attckr-package.R
  4. 28
      R/fct-tactic.R
  5. 107
      R/read-events.R
  6. 76
      R/tactics-factors.R
  7. 30
      README.Rmd
  8. 62
      README.md
  9. BIN
      inst/extdat/sample-incidents.csv.gz
  10. 3
      man/fct_tactic.Rd
  11. BIN
      man/figures/README-events-1.png
  12. 50
      man/read_events.Rd
  13. 9
      man/tactics_f.Rd
  14. 129
      tools/fill-mat.R

3
DESCRIPTION

@ -30,6 +30,9 @@ Imports:
jsonlite,
tibble,
ggplot2,
readr,
glue,
dplyr,
shiny,
stringi,
rmarkdown

9
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)

3
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"

28
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)
}

107
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
}

76
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

30
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}

62
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
## <chr> <chr> <chr> <chr> <chr>
## 1 .bash_profile and… "<code>~/.bash_profile</code> and <code>~/.bashrc</code> 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())
```
<img src="man/figures/README-events-1.png" width="1056" />
## 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

BIN
inst/extdat/sample-incidents.csv.gz

Binary file not shown.

3
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
}

BIN
man/figures/README-events-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

50
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"))
}

9
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)
}

129
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)

Loading…
Cancel
Save