tudelft-cda-lab / sage Goto Github PK
View Code? Open in Web Editor NEW[TDSC 2021] IntruSion alert-driven Attack Graph Extractor. https://ieeexplore.ieee.org/document/9557854
License: MIT License
[TDSC 2021] IntruSion alert-driven Attack Graph Extractor. https://ieeexplore.ieee.org/document/9557854
License: MIT License
Make the code cleaner
Currently, all alert signatures and mappings are hard-coded into sage.py
. This makes the file unnecessarily large.
Extract alert signatures and mappings into separate files and read/import them at the beginning of a program.
Determine how to represent vertices in AGs that come from sink states. If we do assign |Sink, then all sinks of that type will be merged in one node (cool for simplicity, bad for readability). However, state IDs taken from sinks (salvaged) might be misleading for analysts (think different context). In either case, vertices related to sink states MUST ALWAYS have a dotted border.
Currently, parsing of the input arguments is happening manually, which is a bit cumbersome.
Furthermore, another optional parameter has to be added for the dataset name. This is needed because some if-checks in the code are CPTC-specific (see issue #24).
ArgumentParser
(similar to SECLEDS)dataset_name
(options = {"cptc", "other"}, default value = "other")docker
branch accordingly (only the new options have to be added to the script.sh
and input.ini
files, no other changes are necessary, i.e. the input.ini
file remains only on the docker
branch, spdfa-config.ini
remains only on the main
branch, argument parsing remains in the sage.py
, with the exception that default values of optional parameters will be added to the script.sh
file).main
and docker
branchesIn the current implementation, when computing the most targeted service, the first most frequent service is taken, so that the result is deterministic (see PR #10).
On the other hand, there are "unknown" services, which are used when SAGE cannot infer the service based on IANA port-mapping.
A potential improvement to the tie-breaker might be to explicitly not choose "unknown" as the most targeted service in case of a tie, or to add a small margin (for example, if http
has a count of 3 and unknown
has a count of 4, then http
can still be used). This way a security analyst might get better insights from the AGs since a specific service might reveal more information than an "unknown" service.
Add provision to limit the amount of alerts available for making AGs
What happens if the input folder contains files with alternating order of alerts? Currently the {mode} applies to all files in a folder. Make it more flexible.
Hi,
Thanks for the source code. Is it possible to put a sample input file I tried some .json files in http://cptc.rit.edu/2018/t2/events/ but I guess it does not work for all the .json files there.
Implement support for partial paths. For a victim X, all paths that have reached an intermediate vertex Y.
Currently, SAGE is one file sage.py
, which is over 1k lines of code. The largest part of the file consists of the functions, and only at the very end there is the actual main part. A better approach might be to split sage.py
into separate files, as it was also done in SECLEDS.
sage.py
with the main part, alert parsing and global parametersplotting.py
with the functions that are related to plotting (including make_state_groups
)episode_sequence_generation.py
(from making hyperalert sequences to trace generation, i.e. from aggregate_into_episodes
until generate_traces
excluding)model_learning.py
(from generate_traces
until make_state_sequences
and group_episodes_per_av
including; the code in group_episodes_per_av
can go to make_state_sequences
function, since it just makes the state sequences on an attacker or victim level)ag_generation.py
(converting state sequences into AGs, i.e. make_attack_graphs
and the related functions)docker
branch will be updated accordingly to make sure that all the files are copied.Hello, is the cptc data set in this experiment under this link? I wonder which of these files should I download? Just like the sample-input.json you gave. Thanks very much!
Sometimes, there are attackers which generate 99% of the alerts, which in the code are called bad_ip
and are skipped. Furthermore, there are alerts that occur way too often and could be filtered, if necessary (see below).
The bad_ip
might be dataset-specific, and the checks for "Attempted Information Leak" and "Non Suspicious Traffic" might be needed only in case of bad_ip
.
_remove_duplicate
method checks for NON_MALICIOUS
traffic, however the former is a SURICATA category, while the latter is part of the MicroAttackStage
framework_parse
function accordinglyThis code snippet below (part of the traverse
function) does not work in an intended way. As mentioned here, a defaultdict
in Python creates a new element when it is not present in the dict and is accessed. When the ID is removed from a low-severity sink and there are more states in the trace, state_list[-1]
will be -1, which will be queried in the sinks
dictionary (which is a defaultdict
). This will lead to -1 being added to the (global) sinks
(sinks_model
) variable, which should not be there (otherwise states with ID -1 will sort of be sinks).
This was the issue for CCDC dataset. In the image below, there is a (still reversed) sequence where netDOS
follows vulnD
. Because the ID is removed from vuldD
(as it is a low-severity node), the next transition is from state -1 which cannot be correct as there are no nodes in S-PDFA with ID -1 (except for one dummy node, but that's not a problem here).
The states in the AGs are essentially the same, except for this -1 ID on some states.
The IDs can be stored in a separate list here (for example, transitions_list
), which is used only for transitions. The original list (state_list
) will have -1s will be returned.
Some episode subsequences start with a high-severity episode.
The viable cuts for subsequences are: [med, low]
, [high, low]
, and [high, med]
. It could happen that episode subsequences start with a high-severity episode if the attackers did actually start with a high-severity alert, and we even claim that the alert driven AGs have this as a special property. However, we need to make sure this artefact does not exist because of the sequence cutting.
The problem with the image above happens because of the line if pieces < 1:
(in break_into_subbehaviours
). The subsequence above had length 3. In break_into_subbehaviours
function, cut_length = 4
. As a result, the line pieces = math.floor(len(episodes) / cut_length)
will evaluate to pieces = 0
and if pieces < 1
to True, because the corresponding episode sequence has length 3. Hence, the entire subsequence of length 3 will be added to the episode subsequences.
Ideally we want the sequence to be cut entirely based on severity, not on the cut_length basis
In CPTC-2017 and CPTC-2018 datasets, attacker IPs are known, however this might not be the case for other datasets (e.g. CCDC). Because of that, some parts of the code that are CPTC-specific have to be commented out when using, for example, CCDC dataset.
For example:
Furthermore, the code snippet above is executed after learning the S-PDFA, which is too late.
Move this check from make_state_sequences
into group_alerts_per_team
(in sage.py
):
src_ip
or in dst_ip
- if not present, then discardsrc_ip
, then add (src_ip, dst_ip)
. If in dst_ip
, then add (dst_ip, src_ip)
make_state_sequences
functionFor the future, we might want to address internal paths (leave this as a TODO).
bad_ip
can be renamed to cptc_bad_ip
Furthermore, add a specific flag for the dataset (enum or a string) and add this flag to the if-check, so that it is triggered only for the CPTC dataset. In PR #35, ArgumentParser
will be used to parse this option or set the default one.
UPDATE: PR #35 has already added the --dataset
option. In this PR, this option only has to be added to the correct places.
How can alert-driven AGs be used to predict future attacks?
Currently, the method load_IANA_mapping
throws an error when a time-out occurs.
Send another request when a time-out happens. In either case, the user should not have to deal with this error, so it has to be hidden from the user.
Currently, SAGE does not have any tests. Below are some ideas for potential test cases.
Keep the "ground truth" version of the attack graphs (the dot files). Before every merge to main
, run the new implementation and compare the AGs to the "ground truth". An example using the scripts from my Research Project repository:
In this test file, AGs are compared with the "ground truth" based on the nodes and edges present (diff-ags.sh
) and node stats (stats-nodes-ags.sh
), and the episode traces passed to FlexFringe are also compared with the "ground truth".
This would be very useful for PRs related to refactoring and changing minor parts of the code that do not affect the resulting AGs.
The disadvantage, however, is that the "ground truth" files will have to be updated if the AGs change. On the other hand, the graphs can be compared to the latest SAGE version on the main
branch.
As mentioned in #14, sinks in AGs can be compared with the sinks in the json
files generated by FlexFringe. Here no "ground truth" is necessary as FlexFringe output (the S-PDFA) is taken as the "ground truth".
# All found sinks in 2017 are indeed sinks (after all fixes)
[jegor@arch SAGE-fork]$ sinks_after_2017=$(find after-2017AGs/ -type f -name '*.dot' | xargs grep -F -l "dotted" | xargs gvpr 'N [ $.style == "dotted" || $.style == "filled,dotted" || $.style == "dotted,filled" ] { print(gsub(gsub($.name, "\r"), "\n", " | ")); }' | sort -u | uniq -i)
[jegor@arch SAGE-fork]$ all_sinks_2017=$(jq '.nodes[] | select(.issink==1) | .id' before-2017.txt.ff.finalsinks.json | sort)
[jegor@arch SAGE-fork]$ echo -e "$sinks_after_2017" | wc -l
141
[jegor@arch SAGE-fork]$ comm -12 <(echo -e "$sinks_after_2017" | sed 's/^.*ID: \([0-9-]\+\)$/\1/' | sort) <(echo -e "$all_sinks_2017") | wc -l
141
# All found sinks in 2018 are indeed sinks (after all fixes)
[jegor@arch SAGE-fork]$ sinks_after_2018=$(find after-2018AGs/ -type f -name '*.dot' | xargs grep -F -l "dotted" | xargs gvpr 'N [ $.style == "dotted" || $.style == "filled,dotted" || $.style == "dotted,filled" ] { print(gsub(gsub($.name, "\r"), "\n", " | ")); }' | sort -u | uniq -i)
[jegor@arch SAGE-fork]$ all_sinks_2018=$(jq '.nodes[] | select(.issink==1) | .id' before-2018.txt.ff.finalsinks.json | sort)
[jegor@arch SAGE-fork]$ echo -e "$sinks_after_2018" | wc -l
104
[jegor@arch SAGE-fork]$ comm -12 <(echo -e "$sinks_after_2018" | sed 's/^.*ID: \([0-9-]\+\)$/\1/' | sort) <(echo -e "$all_sinks_2018") | wc -l
104
# All non-sinks with IDs in 2017 are indeed non-sinks (after all fixes)
[jegor@arch SAGE-fork]$ non_sinks_with_ids_after_2017=$(find after-2017AGs/ -type f -name '*.dot' | xargs gvpr 'N [ $.style != "dotted" && $.style != "filled,dotted" && $.style != "dotted,filled" ] { print(gsub(gsub($.name, "\r"), "\n", " | ")); }' | sort -u | uniq -i | grep 'ID: ')
[jegor@arch SAGE-fork]$ echo -e "$non_sinks_with_ids_after_2017" | wc -l
28
[jegor@arch SAGE-fork]$ all_non_sinks_after_2017=$(jq '.nodes[] | select(.issink==0) | .id' after-2017.txt.ff.final.json | sort -u)
[jegor@arch SAGE-fork]$ comm -12 <(echo -e "$non_sinks_with_ids_after_2017" | sed 's/^.*ID: \([0-9-]\+\)$/\1/' | sort -u) <(echo -e "$all_non_sinks_after_2017") | wc -l
28
# All non-sinks with IDs in 2018 are indeed non-sinks (after all fixes)
[jegor@arch SAGE-fork]$ non_sinks_with_ids_after_2018=$(find after-2018AGs/ -type f -name '*.dot' | xargs gvpr 'N [ $.style != "dotted" && $.style != "filled,dotted" && $.style != "dotted,filled" ] { print(gsub(gsub($.name, "\r"), "\n", " | ")); }' | sort -u | uniq -i | grep 'ID: ')
[jegor@arch SAGE-fork]$ echo -e "$non_sinks_with_ids_after_2018" | wc -l
16
[jegor@arch SAGE-fork]$ all_non_sinks_after_2018=$(jq '.nodes[] | select(.issink==0) | .id' after-2018.txt.ff.final.json | sort -u)
[jegor@arch SAGE-fork]$ comm -12 <(echo -e "$non_sinks_with_ids_after_2018" | sed 's/^.*ID: \([0-9-]\+\)$/\1/' | sort -u) <(echo -e "$all_non_sinks_after_2018") | wc -l
16
_get_episodes
method has test cases for episode creation that are commented out. A better solution would be to move these commented tests into a separate test file
Cutting episode sequences into episode subsequences could also potentially be tested.
Depending on whether implementation allows this or not, episode sequences could be compared with state sequences for consistency.
The break_into_subbehaviors
function, which is responsible for cutting episode sequences into episode subsequences, discards short subsequences, for example:
This check occurs multiple times within the function and it is also present in generate_traces
function. Ideally, it should be only in one place (in generate_traces
).
Furthermore, when splitting, if a sequence goes like [low, low, medium, high, low]
, then [low, low, medium, high]
is saved but the last [low]
is just discarded. We probably shouldn't lose alerts like this. On the other hand, it is not clear what to do with a single event either. Maybe we keep them regardless?
generate_traces
function and update the break_into_subbehaviors
function accordingly. The resulting attack graphs should be the same as before.A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.