You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
8.3 KiB
312 lines
8.3 KiB
---
|
|
title: "ATT&CK Metrics Overview"
|
|
output:
|
|
html_document:
|
|
toc: true
|
|
toc_float: true
|
|
toc_depth: 2
|
|
editor_options:
|
|
chunk_output_type: console
|
|
---
|
|
|
|
```{r setup, include=FALSE}
|
|
knitr::opts_chunk$set(
|
|
echo = FALSE,
|
|
message = FALSE,
|
|
warning = FALSE,
|
|
fig.retina = 2,
|
|
fig.width=900/72,
|
|
fig.height = 700/72
|
|
)
|
|
```
|
|
|
|
```{r libs}
|
|
library(hrbrthemes)
|
|
library(attckr)
|
|
library(tidyverse)
|
|
```
|
|
|
|
```{r atk-data}
|
|
xdf <- readRDS(system.file("extdat/more-incidents.rds", package = "attckr"))
|
|
xdf <- mutate(xdf, quarter = sprintf("Q%d", lubridate::quarter(containment_ts)))
|
|
xdf <- mutate(xdf, month = lubridate::month(containment_ts, abbr=FALSE, label=TRUE))
|
|
```
|
|
|
|
# Dwell Time
|
|
|
|
```{r dwell-calc}
|
|
xdf %>%
|
|
mutate(delta = as.numeric(containment_ts - first_event_ts, "days")) %>%
|
|
ungroup() %>%
|
|
mutate(
|
|
delta_ord = case_when(
|
|
delta < 1 ~ "< 1 day",
|
|
delta < 2 ~ "1 day",
|
|
delta <= 7 ~ "1 week",
|
|
delta <= 14 ~ "2 weeks",
|
|
delta <= 28 ~ "1 month",
|
|
delta <= 46 ~ "2 months",
|
|
delta <= 84 ~ "3 months",
|
|
delta <= 112 ~ ">1 qtr",
|
|
is.na(delta) ~ "Unknown",
|
|
TRUE ~ "> 1 qtr"
|
|
)
|
|
) %>%
|
|
mutate(
|
|
delta_ord = factor(delta_ord, levels = c("< 1 day", "1 day", "1 week", "2 weeks", "1 month", "2 months", "3 months", ">1 qtr", "Unknown"))
|
|
) -> dwell_df
|
|
```
|
|
|
|
## Overall
|
|
|
|
```{r dwell-time-overall, fig.height = 400/72}
|
|
count(dwell_df, delta_ord) %>%
|
|
mutate(pct = n/sum(n)) %>%
|
|
ggplot(aes(delta_ord, pct)) +
|
|
geom_col(width=0.55, fill = ft_cols$blue) +
|
|
scale_y_percent(limits = c(0, 1)) +
|
|
labs(
|
|
x = NULL, y = "% Incidents",
|
|
title = "Dwell Time % (Overall)"
|
|
) +
|
|
theme_ipsum_es(grid="Y") +
|
|
theme(legend.position = "bottom")
|
|
```
|
|
|
|
## by Quarter
|
|
|
|
```{r dwell-time-quarter, fig.height = 900/72}
|
|
count(dwell_df, quarter, delta_ord) %>%
|
|
group_by(quarter) %>%
|
|
mutate(pct = n/sum(n)) %>%
|
|
ungroup() %>%
|
|
ggplot(aes(delta_ord, pct)) +
|
|
geom_col(width=0.55, fill = ft_cols$blue) +
|
|
scale_x_discrete(
|
|
breaks = c("< 1 day", "1 day", "1 week", "2 weeks", "1 month", "2 months", "3 months", ">1 qtr", "Unknown"),
|
|
limits = c("< 1 day", "1 day", "1 week", "2 weeks", "1 month", "2 months", "3 months", ">1 qtr", "Unknown")
|
|
) +
|
|
scale_y_percent(limits = c(0, 1)) +
|
|
facet_wrap(~quarter, ncol=1, scales = "free") +
|
|
labs(
|
|
x = NULL, y = "% Incidents",
|
|
title = "Dwell Time % (by Quarter)"
|
|
) +
|
|
theme_ipsum_es(grid="Y") +
|
|
theme(legend.position = "bottom")
|
|
```
|
|
|
|
```{r dwell-time-quarter-line}
|
|
count(dwell_df, quarter, delta_ord) %>%
|
|
group_by(quarter) %>%
|
|
mutate(pct = n/sum(n)) %>%
|
|
ungroup() %>%
|
|
arrange(quarter) %>%
|
|
mutate(quarter = fct_inorder(as.character(quarter)) %>% fct_rev()) %>%
|
|
complete(quarter, delta_ord) %>%
|
|
ggplot(aes(quarter, pct, group = delta_ord, color = delta_ord)) +
|
|
geom_line() +
|
|
geom_label(
|
|
aes(
|
|
label = scales::percent(pct),
|
|
), lineheight = 0.875, family = font_es, size = 4, show.legend = FALSE
|
|
) +
|
|
scale_x_discrete(position = "top") +
|
|
scale_y_percent(limits = c(-0.05, 1.05)) +
|
|
labs(
|
|
x = NULL, y = NULL, color = 'Dwell Time',
|
|
title = "Dwell Time % (by Quarter)"
|
|
) +
|
|
theme_ipsum_es(grid="Y") +
|
|
theme(legend.position = "bottom")
|
|
```
|
|
|
|
## by Month
|
|
|
|
```{r dwell-time-month}
|
|
count(dwell_df, month, delta_ord) %>%
|
|
group_by(month) %>%
|
|
mutate(pct = n/sum(n)) %>%
|
|
ungroup() %>%
|
|
arrange(month) %>%
|
|
mutate(month = fct_inorder(as.character(month)) %>% fct_rev()) %>%
|
|
complete(month, delta_ord) %>%
|
|
ggplot(aes(delta_ord, month)) +
|
|
geom_tile(aes(fill = pct), color = "white", size = 0.5) +
|
|
geom_text(
|
|
aes(
|
|
label = scales::percent(pct),
|
|
color = I(ifelse(pct > 0.1, "white", "black"))
|
|
), lineheight = 0.875, family = font_es, size = 4
|
|
) +
|
|
scale_x_discrete(position = "top") +
|
|
scale_fill_viridis_c(
|
|
option = "magma", direction = -1, trans = "identity", label = scales::percent
|
|
) +
|
|
labs(
|
|
x = NULL, y = NULL, fill = '%',
|
|
title = "Dwell Time % (by Month)"
|
|
) +
|
|
theme_ipsum_es(grid="Y") +
|
|
theme(legend.position = "bottom") +
|
|
theme(legend.key.width = unit(3, "lines"))
|
|
```
|
|
|
|
```{r dwell-time-month-line}
|
|
count(dwell_df, month, delta_ord) %>%
|
|
group_by(month) %>%
|
|
mutate(pct = n/sum(n)) %>%
|
|
ungroup() %>%
|
|
arrange(month) %>%
|
|
mutate(month = fct_inorder(as.character(month)) %>% fct_rev()) %>%
|
|
complete(month, delta_ord) %>%
|
|
ggplot(aes(month, pct, group = delta_ord, color = delta_ord)) +
|
|
geom_line() +
|
|
geom_label(
|
|
aes(
|
|
label = scales::percent(pct),
|
|
), lineheight = 0.875, family = font_es, size = 4, show.legend = FALSE
|
|
) +
|
|
scale_x_discrete(position = "top") +
|
|
scale_y_percent(limits = c(-0.05, 1.05)) +
|
|
labs(
|
|
x = NULL, y = NULL, color = 'Dwell Time',
|
|
title = "Dwell Time % (by Month)"
|
|
) +
|
|
theme_ipsum_es(grid="Y") +
|
|
theme(legend.position = "bottom")
|
|
```
|
|
|
|
# ATT&CK Heatmap
|
|
|
|
## Overall
|
|
|
|
```{r}
|
|
unnest(xdf, mitre_attack) %>%
|
|
select(tactic, technique) %>%
|
|
attck_map(
|
|
input = "pretty", output = "nl", matrix = "enterprise",
|
|
family = font_es, size = 3
|
|
) +
|
|
scale_fill_viridis_c(option = "magma") +
|
|
guides(
|
|
fill = guide_colourbar(title.position = "top")
|
|
) +
|
|
labs(
|
|
fill = "Tactics Raw Count: ",
|
|
title = "Overall ATT&CK Heatmap"
|
|
) +
|
|
theme_ipsum_es(grid="") +
|
|
theme_enhance_atkmap()
|
|
```
|
|
|
|
## by Quarter
|
|
|
|
```{r heatmap-by-quarter, fig.width = 1200/72, fig.height = 1200/72}
|
|
unnest(xdf, mitre_attack) %>%
|
|
mutate(quarter = sprintf("Q%d", lubridate::quarter(containment_ts))) %>%
|
|
count(quarter, tactic, technique) %>%
|
|
mutate(
|
|
tactic = fct_tactic(tactic, "pretty", "nl", "enterprise"),
|
|
technique = gsub("[[:space:]]+", "\n", technique),
|
|
) %>%
|
|
group_by(quarter, tactic) %>%
|
|
mutate(ids = (n():1)) -> plot_df
|
|
|
|
plot_df %>%
|
|
ggplot(aes(tactic, ids)) +
|
|
geom_tile(aes(fill = n), color = "white") +
|
|
geom_text(
|
|
aes(
|
|
label = technique,
|
|
color = I(ifelse(n > 20, "black", "white"))
|
|
),
|
|
family = font_es, size = 3, lineheight = 0.875
|
|
) +
|
|
scale_x_discrete(
|
|
breaks = levels(plot_df$tactic), limits = levels(plot_df$tactic),
|
|
position = "top"
|
|
) +
|
|
scale_y_reverse() +
|
|
scale_fill_viridis_c(option = "magma", trans = "log10", label = scales::comma) +
|
|
facet_wrap(~quarter, ncol = 1, scales = "free") +
|
|
guides(
|
|
fill = guide_colourbar(title.position = "top")
|
|
) +
|
|
labs(
|
|
fill = "Tactics Raw Count: ",
|
|
title = "Quarterly ATT&CK Heatmap"
|
|
) +
|
|
theme_ipsum_es(grid="") +
|
|
theme_enhance_atkmap() +
|
|
theme(strip.placement = "outside")
|
|
```
|
|
|
|
# Cumulative ATT&CK Tactics Distributions
|
|
|
|
## Overall
|
|
|
|
```{r attck-cdf-all}
|
|
unnest(xdf, mitre_attack) %>%
|
|
count(tactic) %>%
|
|
mutate(tactic = fct_tactic(tactic, "pretty", "nl")) %>%
|
|
arrange(tactic) %>%
|
|
mutate(pct = n/sum(n)) %>%
|
|
mutate(cpct = cumsum(pct)) -> cdf
|
|
|
|
ggplot(cdf, aes(tactic, cpct, group=1)) +
|
|
geom_path() +
|
|
geom_label(
|
|
aes(
|
|
label = sprintf("%s\n%s\n%s", scales::comma(n), scales::percent(pct), scales::percent(cpct)),
|
|
), lineheight = 0.875, family = font_es, size = 3
|
|
) +
|
|
scale_x_discrete(
|
|
expand = c(0, 0.5), position = "top",
|
|
breaks = levels(cdf$tactic), limits = levels(cdf$tactic)
|
|
) +
|
|
scale_y_continuous(
|
|
expand = c(0, 0.05), limits = c(-0.05, 1.05), label = scales::percent
|
|
) +
|
|
labs(
|
|
x = NULL, y = NULL,
|
|
title = "ATT&CK Tactics Cumulative Distribution (All Time)"
|
|
) +
|
|
theme_ipsum_es(grid="XY")
|
|
```
|
|
|
|
## by Industry
|
|
|
|
```{r attck-cdf-industry}
|
|
unnest(xdf, mitre_attack) %>%
|
|
count(tactic, industry) %>%
|
|
mutate(tactic = fct_tactic(tactic, "pretty", "nl")) %>%
|
|
arrange(tactic) %>%
|
|
group_by(industry) %>%
|
|
mutate(pct = n/sum(n)) %>%
|
|
mutate(cpct = cumsum(pct)) %>%
|
|
ungroup() -> cdf
|
|
|
|
ggplot(cdf, aes(tactic, cpct, group=industry)) +
|
|
geom_path(aes(colour = industry)) +
|
|
geom_label(
|
|
aes(
|
|
label = sprintf("%s\n%s\n%s", scales::comma(n), scales::percent(pct), scales::percent(cpct)),
|
|
color = industry,
|
|
), lineheight = 0.875, family = font_es, size = 3, show.legend = FALSE
|
|
) +
|
|
scale_x_discrete(
|
|
expand = c(0, 0.5), position = "top",
|
|
breaks = levels(cdf$tactic), limits = levels(cdf$tactic)
|
|
) +
|
|
scale_y_continuous(
|
|
expand = c(0, 0.05), limits = c(-0.05, 1.05), label = scales::percent
|
|
) +
|
|
ggthemes::scale_colour_tableau("Tableau 20") +
|
|
labs(
|
|
x = NULL, y = NULL, colour = NULL,
|
|
title = "ATT&CK Tactics Cumulative Distribution By Industry"
|
|
) +
|
|
theme_ipsum_es(grid="XY") +
|
|
theme(legend.position = "bottom")
|
|
```
|
|
|