This code takes an initial (prior) estimate of the frequency of attacks against an organization. This range would be similar to Threat Event Frequency (TEF) in the Factor Analysis of Information Risk (FAIR) taxonomy, but likely a bit higher (analysts likely weed out or are unaware of many attacks that fail early on).
Note: There is an option (lines 270 & 271) to enter actual observational data (i.e. if you know there were 2 successful incidents in the past 3 year).
It then simulates each attack as it progresses through the relevant MITRE ATT&CK tactics. Each tactic has an individually estimated range of control strength that gets applied. There is also logic that assumes that when an attacker fails with a tactic, they may retry and/or fallback and try a different path. However, as they fallback and try different techniques the chance of being discovered or blocked increases.
The result is then an posterior projection of the number of successful attacks that will actually get through the entire attack process to full compromise. These are then combined with estimates of loss aligned to FAIR loss categories to compute Annualized Loss Expectancy (ALE).
You can use either the .py file or if you prefer Jupyter Notebooks you can use the .ipynb file.
Note: The Jupyter Notebook version of the code is dialed back to run fewer simulations, this is because Jupyter Notebooks won't always play nice with the parallelization that is implemented in the .py version.
- Python
- PyMC (Recommended installation using Anaconda: https://www.pymc.io/projects/docs/en/latest/installation.html#)
- Jupyer Notebooks (optional)
- I got a lot of help on this from Claude and ChatGPT.
The code also outputs two CSV files, to the same directory the .py file is save in, that includes the summary statistics and the full annualized simulation data for each draw.
This is intended to show attackers progression through the MITRE ATT&CK tactics, so you can see how far attackers are getting, where controls may be more effective at stopping attackers, etc.
Note: I have only implemented this view into the Jupyter Notebook version of the code, due to the extended time it takes to gather these statistics.