├── frontend ├── .nojekyll ├── .gitignore ├── assets │ ├── images │ │ └── flags │ │ │ └── doc.png │ └── icons │ │ ├── logo.svg │ │ └── states │ │ ├── usa.svg │ │ └── world.svg ├── dist │ ├── webfonts │ │ ├── fa-brands-400.eot │ │ ├── fa-brands-400.ttf │ │ ├── fa-solid-900.eot │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff │ │ ├── fa-brands-400.woff │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.eot │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff │ │ ├── fa-regular-400.woff2 │ │ └── fa-solid-900.woff2 │ └── js │ │ ├── about.js │ │ ├── navAction.js │ │ ├── main.js │ │ └── sidebar.js ├── package.json ├── scss │ ├── _header.scss │ ├── _legend.scss │ ├── _colors.scss │ ├── _buttons.scss │ ├── style.scss │ └── _sidebar.scss ├── package-lock.json ├── index.html └── data │ └── ex_groups.json ├── images ├── ex0_header.png ├── ex1_header.png ├── ex2_header.png ├── ex3_header.png ├── preview_header.png ├── showcase_header.png └── wip_pics │ ├── duplicates.png │ ├── group30_tools.png │ ├── initial_mess.png │ ├── group_tool_teq.png │ ├── more_duplicates.png │ ├── group10_tool_teq.png │ ├── relevant_nodes_fix.png │ ├── unique_nodes_fixed.png │ ├── unwarranted_nodes.png │ ├── only_relevant_nodes.png │ └── group_tool_teq_thumbnail.png ├── requirements.txt ├── examples ├── example0 │ └── group30.html ├── example1 │ └── group30-tools.html ├── example2 │ └── group10-tool-teq.html └── example3 │ └── all-group-tool-teqs.html ├── LICENSE ├── .gitignore ├── README.md └── graph_generator.py /frontend/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /images/ex0_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/ex0_header.png -------------------------------------------------------------------------------- /images/ex1_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/ex1_header.png -------------------------------------------------------------------------------- /images/ex2_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/ex2_header.png -------------------------------------------------------------------------------- /images/ex3_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/ex3_header.png -------------------------------------------------------------------------------- /images/preview_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/preview_header.png -------------------------------------------------------------------------------- /images/showcase_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/showcase_header.png -------------------------------------------------------------------------------- /images/wip_pics/duplicates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/duplicates.png -------------------------------------------------------------------------------- /images/wip_pics/group30_tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/group30_tools.png -------------------------------------------------------------------------------- /images/wip_pics/initial_mess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/initial_mess.png -------------------------------------------------------------------------------- /images/wip_pics/group_tool_teq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/group_tool_teq.png -------------------------------------------------------------------------------- /images/wip_pics/more_duplicates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/more_duplicates.png -------------------------------------------------------------------------------- /frontend/assets/images/flags/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/assets/images/flags/doc.png -------------------------------------------------------------------------------- /images/wip_pics/group10_tool_teq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/group10_tool_teq.png -------------------------------------------------------------------------------- /images/wip_pics/relevant_nodes_fix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/relevant_nodes_fix.png -------------------------------------------------------------------------------- /images/wip_pics/unique_nodes_fixed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/unique_nodes_fixed.png -------------------------------------------------------------------------------- /images/wip_pics/unwarranted_nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/unwarranted_nodes.png -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /images/wip_pics/only_relevant_nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/only_relevant_nodes.png -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /frontend/dist/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/frontend/dist/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /images/wip_pics/group_tool_teq_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/ATTACK-Force-Graph/main/images/wip_pics/group_tool_teq_thumbnail.png -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@fortawesome/fontawesome-free": "^5.15.3", 4 | "@iconfu/svg-inject": "^1.2.3", 5 | "bootstrap": "^5.0.2" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | antlr4-python3-runtime==4.8 2 | attackcti==0.3.4.4 3 | certifi==2021.5.30 4 | chardet==4.0.0 5 | idna==2.10 6 | pytz==2021.1 7 | requests==2.25.1 8 | simplejson==3.17.2 9 | six==1.16.0 10 | stix2==2.1.0 11 | stix2-patterns==1.3.2 12 | taxii2-client==2.3.0 13 | urllib3==1.26.6 14 | -------------------------------------------------------------------------------- /frontend/dist/js/about.js: -------------------------------------------------------------------------------- 1 | import { setNodeColor, setNodeType, setNodeName, updateSideBar } from './sidebar.js'; 2 | 3 | document.getElementById("btn-about").addEventListener("click", () => { 4 | setNodeColor("default"); 5 | 6 | // change node type text 7 | setNodeType("about"); 8 | setNodeName("About This Awesome Sauce"); 9 | 10 | updateSideBar(); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /frontend/scss/_header.scss: -------------------------------------------------------------------------------- 1 | .header-main { 2 | position: fixed; 3 | display: flex; 4 | z-index: 6; 5 | width: 100%; 6 | padding: .8rem; 7 | color: $white-dove; 8 | background-color: $header-background; 9 | border-bottom: solid #4BDFFF .2px; 10 | 11 | .logo-box { 12 | align-items: center; 13 | 14 | .logo { 15 | width: 3.5rem; 16 | padding-left: 1rem; 17 | padding-right: 0; 18 | // margin-left: 1rem; 19 | } 20 | } 21 | 22 | h2 { 23 | width: auto; 24 | margin: 0rem; 25 | margin-top: .3rem; 26 | } 27 | } -------------------------------------------------------------------------------- /frontend/assets/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /examples/example0/group30.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /examples/example1/group30-tools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /examples/example2/group10-tool-teq.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /examples/example3/all-group-tool-teqs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /frontend/scss/_legend.scss: -------------------------------------------------------------------------------- 1 | .legend { 2 | position: fixed; 3 | bottom: 2rem; 4 | padding: .5rem; 5 | z-index: 7; 6 | color: #00B273; 7 | letter-spacing: 1px; 8 | 9 | .node-key { 10 | display: flex; 11 | align-items: center; 12 | } 13 | 14 | .circle { 15 | display: block; 16 | margin-right: .5rem; 17 | width: 20px; 18 | height: 20px; 19 | border-radius: 50%; 20 | } 21 | .circle-group { 22 | @extend .circle; 23 | background: -webkit-radial-gradient(1px 1px, circle, $group-color, $group-dark); 24 | } 25 | .circle-tool { 26 | @extend .circle; 27 | background: -webkit-radial-gradient(1px 1px, circle, $tool-color, $tool-dark); 28 | } 29 | .circle-technique { 30 | @extend .circle; 31 | background: -webkit-radial-gradient(1px 1px, circle, $technique-color, $technique-dark); 32 | } 33 | 34 | p { 35 | margin: 0; 36 | } 37 | } -------------------------------------------------------------------------------- /frontend/scss/_colors.scss: -------------------------------------------------------------------------------- 1 | 2 | $primary: #4BDFFF; 3 | $primary-disabled: rgba(4, 211, 255, .40); 4 | $primary-dark: #00a6b2; 5 | 6 | $white-dove: #c7d4dd; 7 | $primary-blue: #00b9e4; 8 | 9 | $retro-green: #4BFFBE; 10 | $retro-green-disabled: #2bffbf6b; 11 | $retro-dark: #00B273; 12 | 13 | $contraband-yellow: #ffc600; 14 | $contraband-disabled: rgba(255, 209, 4, 0.4); 15 | $contraband-dark: #c49900; 16 | 17 | $dark-text: #3a3a3a; 18 | $dark-noir: rgba(23, 30, 45, 0.60); 19 | 20 | $dark-background: rgba(12, 14, 23, 0.9); 21 | $header-background: rgba(23, 30, 45, 0.60); 22 | 23 | $group-color: #FF585B; 24 | $group-disabled: rgba(255, 88, 91, .15); 25 | $group-dark: #910000; 26 | 27 | $tool-color: #4BDFFF; 28 | $tool-disabled: rgba(4, 211, 255, .15); 29 | $tool-dark: #00a6b2; 30 | 31 | 32 | $technique-color: #9157EB; 33 | $technique-disabled: rgba(91, 116, 231, .15); 34 | $technique-dark: #4b0091; 35 | 36 | -------------------------------------------------------------------------------- /frontend/dist/js/navAction.js: -------------------------------------------------------------------------------- 1 | let open = false; 2 | 3 | function openSidebar() { 4 | open = true; 5 | document.getElementById("infoSidebar").style.width = "28%"; 6 | document.getElementById("info-toggle-icon").setAttribute("class", "fas fa-chevron-right"); 7 | } 8 | 9 | function sidebarCalled() { 10 | if (!open) { 11 | openSidebar(); 12 | document.getElementById("info-toggle-icon").setAttribute("class", "fas fa-chevron-right"); 13 | document.getElementById("toggle-info-btn").classList.remove("toggle-info-btn-closed"); 14 | document.getElementById("toggle-info-btn").classList.add("toggle-info-btn"); 15 | } else { 16 | open = false; 17 | document.getElementById("infoSidebar").style.width = "0%"; 18 | document.getElementById("info-toggle-icon").setAttribute("class", "fas fa-chevron-left"); 19 | document.getElementById("toggle-info-btn").classList.remove("toggle-info-btn"); 20 | document.getElementById("toggle-info-btn").classList.add("toggle-info-btn-closed"); 21 | } 22 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/scss/_buttons.scss: -------------------------------------------------------------------------------- 1 | .btn-outline-primary { 2 | font-family: "Roboto", sans-serif; 3 | color: $white-dove; 4 | background-color: $dark-noir; 5 | padding: 0.5rem 1rem; 6 | border: 0; 7 | 8 | i { 9 | color: $primary-blue; 10 | margin-right: .7rem; 11 | } 12 | } 13 | 14 | .btn-icon { 15 | color: $white-dove; 16 | padding: 0 1rem; 17 | 18 | &:hover { 19 | color: $primary-blue; 20 | } 21 | } 22 | 23 | .link-outline-primary { 24 | @extend .btn-outline-primary; 25 | text-decoration: none; 26 | } 27 | 28 | .toggle-info-btn { 29 | position: absolute; 30 | top: 40%; 31 | left: -2.1rem; 32 | height: 4rem; 33 | color: white; 34 | background-color: $retro-green; 35 | align-items: center; 36 | 37 | .fas { 38 | padding: .6rem; 39 | } 40 | } 41 | 42 | .toggle-info-btn-closed { 43 | @extend .toggle-info-btn; 44 | background-color: $retro-green-disabled; 45 | } 46 | 47 | .btn-group-connection { 48 | display: flex; 49 | align-items: center; 50 | 51 | .btn-hi-viz { 52 | color: white; 53 | font-size: 1.2rem; 54 | letter-spacing: 1px; 55 | padding: 0 1.5rem; 56 | margin: .5rem 0; 57 | border: none; 58 | border-radius: 0; 59 | background-color: $dark-noir; 60 | border-left: solid $primary-disabled 5px; 61 | 62 | &:hover, &:focus { 63 | color: $dark-text; 64 | border-color: $retro-dark; 65 | background-color: $retro-green; 66 | 67 | &:focus { 68 | box-shadow: 0 0 0 0.25rem $primary-dark; 69 | } 70 | } 71 | } 72 | 73 | .active { 74 | color: white; 75 | border-left: solid $primary 5px; 76 | background-color: $primary-disabled; 77 | } 78 | 79 | i { 80 | margin: 0 1rem; 81 | } 82 | } 83 | 84 | .camera-reset { 85 | position: fixed; 86 | display: flex; 87 | align-items: center; 88 | z-index: 2; 89 | bottom: 2.6rem; 90 | justify-content: center; 91 | width: 100%; 92 | 93 | .btn-light { 94 | background-color: $dark-noir; 95 | padding: 1rem; 96 | color: white; 97 | border: none; 98 | border-top: $primary solid 2px; 99 | } 100 | } 101 | 102 | .btn-mitre { 103 | background-color: $dark-noir; 104 | padding: 1rem 1.5rem; 105 | border: 0 $contraband-disabled solid; 106 | border-bottom-width: 5px; 107 | 108 | &:hover { 109 | background-color: $contraband-disabled; 110 | border-color: $contraband-yellow; 111 | 112 | a { 113 | color: $contraband-yellow; 114 | } 115 | } 116 | 117 | a { 118 | text-decoration: none; 119 | color: $contraband-yellow; 120 | } 121 | 122 | h4 { 123 | margin: 0; 124 | } 125 | } 126 | 127 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | downloads/ 14 | eggs/ 15 | .eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | -------------------------------------------------------------------------------- /frontend/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Teko:wght@300&display=swap'); 2 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap'); 3 | 4 | @import "../node_modules/bootstrap/scss/bootstrap"; 5 | 6 | @import "../node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss"; 7 | @import "../node_modules/@fortawesome/fontawesome-free/scss/solid.scss"; 8 | @import "../node_modules/@fortawesome/fontawesome-free/scss/brands.scss"; 9 | @import "../node_modules/@fortawesome/fontawesome-free/scss/regular.scss"; 10 | 11 | @import "_colors.scss"; 12 | @import "_buttons.scss"; 13 | 14 | @import "_header.scss"; 15 | @import "_sidebar.scss"; 16 | @import "_legend.scss"; 17 | 18 | * { 19 | margin: 0; 20 | } 21 | 22 | * div { 23 | color: white; 24 | } 25 | 26 | 27 | body { 28 | margin: 0; 29 | font-family: "Teko", sans-serif; 30 | } 31 | 32 | .parent { 33 | position: relative; 34 | } 35 | 36 | .group-text { 37 | color: $group-color; 38 | } 39 | 40 | .tool-text { 41 | color: $tool-color; 42 | } 43 | 44 | .technique-text { 45 | color: $technique-color; 46 | } 47 | 48 | .divider { 49 | height: 2.5rem; 50 | margin: 0 1rem; 51 | border-right: $primary-blue solid 1px; 52 | } 53 | 54 | 55 | .about-box { 56 | display: flex; 57 | align-items: center; 58 | justify-content: flex-end; 59 | } 60 | 61 | .control-box { 62 | position: absolute; 63 | z-index: 7; 64 | top: 5rem; 65 | left: 1rem; 66 | } 67 | 68 | .bottom-bar { 69 | opacity: .5; 70 | position: fixed; 71 | display: flex; 72 | justify-content: space-between; 73 | bottom: 0; 74 | z-index: 7; 75 | color: white; 76 | margin-top: 1rem; 77 | 78 | .made-by { 79 | margin-left: .5rem; 80 | font-size: .8rem; 81 | font-family: Roboto, sans-serif; 82 | i { 83 | color: $group-color; 84 | } 85 | 86 | a { 87 | color: $retro-green; 88 | text-decoration: none; 89 | } 90 | } 91 | 92 | .control-guide { 93 | border-top: $primary-blue solid 1px; 94 | margin: 0; 95 | background-color: $header-background; 96 | 97 | p { 98 | text-align: center; 99 | margin: 0; 100 | letter-spacing: 1px; 101 | } 102 | } 103 | } 104 | 105 | .affiliation-avatar { 106 | height: 80px; 107 | width: 80px; 108 | } 109 | 110 | .white-space { 111 | hr { 112 | color: $retro-green; 113 | margin: 4rem 0; 114 | } 115 | } 116 | 117 | .control-section-title { 118 | @extend .info-section-title; 119 | margin-top: 1rem; 120 | 121 | hr { 122 | margin-bottom: 0; 123 | } 124 | } 125 | 126 | .warning-box { 127 | margin-top: 1rem; 128 | padding: .5rem; 129 | color: $group-color; 130 | border-left: 5px solid $group-color; 131 | background-color: $group-disabled; 132 | 133 | p { 134 | font-size: 1.1rem; 135 | margin: 0; 136 | } 137 | } 138 | 139 | .guide-box { 140 | @extend .warning-box; 141 | color: $retro-green; 142 | border-color: $retro-green; 143 | background-color: $retro-green-disabled; 144 | 145 | letter-spacing: 1px; 146 | } 147 | 148 | -------------------------------------------------------------------------------- /frontend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Frontend", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "@fortawesome/fontawesome-free": "^5.15.3", 9 | "@iconfu/svg-inject": "^1.2.3", 10 | "bootstrap": "^5.0.2" 11 | } 12 | }, 13 | "node_modules/@fortawesome/fontawesome-free": { 14 | "version": "5.15.3", 15 | "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz", 16 | "integrity": "sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w==", 17 | "hasInstallScript": true, 18 | "engines": { 19 | "node": ">=6" 20 | } 21 | }, 22 | "node_modules/@iconfu/svg-inject": { 23 | "version": "1.2.3", 24 | "resolved": "https://registry.npmjs.org/@iconfu/svg-inject/-/svg-inject-1.2.3.tgz", 25 | "integrity": "sha512-3v1MUAJqmJS4jmhHoCkSxt+EdJrjPHlLXrWocCT25kCxnxJto8028Z6CC406EL11KA53SDZgI/QQA5GEJAoiRw==" 26 | }, 27 | "node_modules/@popperjs/core": { 28 | "version": "2.9.2", 29 | "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz", 30 | "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==", 31 | "peer": true, 32 | "funding": { 33 | "type": "opencollective", 34 | "url": "https://opencollective.com/popperjs" 35 | } 36 | }, 37 | "node_modules/bootstrap": { 38 | "version": "5.0.2", 39 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz", 40 | "integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q==", 41 | "funding": { 42 | "type": "opencollective", 43 | "url": "https://opencollective.com/bootstrap" 44 | }, 45 | "peerDependencies": { 46 | "@popperjs/core": "^2.9.2" 47 | } 48 | } 49 | }, 50 | "dependencies": { 51 | "@fortawesome/fontawesome-free": { 52 | "version": "5.15.3", 53 | "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz", 54 | "integrity": "sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w==" 55 | }, 56 | "@iconfu/svg-inject": { 57 | "version": "1.2.3", 58 | "resolved": "https://registry.npmjs.org/@iconfu/svg-inject/-/svg-inject-1.2.3.tgz", 59 | "integrity": "sha512-3v1MUAJqmJS4jmhHoCkSxt+EdJrjPHlLXrWocCT25kCxnxJto8028Z6CC406EL11KA53SDZgI/QQA5GEJAoiRw==" 60 | }, 61 | "@popperjs/core": { 62 | "version": "2.9.2", 63 | "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz", 64 | "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==", 65 | "peer": true 66 | }, 67 | "bootstrap": { 68 | "version": "5.0.2", 69 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz", 70 | "integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q==", 71 | "requires": {} 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /frontend/scss/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | height: 100%; 3 | width: 0; 4 | position: fixed; 5 | z-index: 10; 6 | top: 4.4rem; 7 | right: 0; 8 | background-color: $dark-background; 9 | transition: 0.5s; 10 | border-left: solid $retro-green 5px; 11 | padding: 0; 12 | 13 | .inner-sidebar { 14 | overflow-y: scroll; 15 | height: 85%; 16 | } 17 | 18 | .node-type { 19 | position: relative; 20 | height: 5rem; 21 | width: 2rem; 22 | background-color: $retro-green; 23 | display: flex; 24 | align-items: center; 25 | 26 | .header-input { 27 | font-size: 1.5rem; 28 | color: white; 29 | width: 5rem; 30 | background-color: transparent; 31 | text-transform: uppercase; 32 | letter-spacing: 2px; 33 | transform: rotate(270deg); 34 | border: none; 35 | position: relative; 36 | text-align: center; 37 | left: -1.4rem; 38 | } 39 | 40 | } 41 | 42 | .node-title { 43 | width: 100%; 44 | background-color: $retro-green-disabled; 45 | display: flex; 46 | align-items: center; 47 | 48 | h2 { 49 | color: $retro-green; 50 | padding-left: 1rem; 51 | margin-top: .8rem; 52 | } 53 | } 54 | 55 | .backdrop-noir-box { 56 | background-color: $dark-noir; 57 | height: 6.7rem; 58 | margin-top: .5rem; 59 | justify-content: center; 60 | text-align: center; 61 | display: grid; 62 | } 63 | 64 | .dual-text-box-sm { 65 | text-align: center; 66 | 67 | .element-sub-header { 68 | font-size: 1rem; 69 | font-weight: bold; 70 | } 71 | 72 | .element-sub-text { 73 | font-size: .9rem; 74 | } 75 | 76 | img { 77 | height: 3.5rem; 78 | margin: .3rem 0; 79 | } 80 | 81 | .custom-svg { 82 | @extend img; 83 | 84 | svg { 85 | @extend img; 86 | width: 4rem; 87 | 88 | path { 89 | transform: scale(0.07); 90 | } 91 | 92 | } 93 | } 94 | } 95 | 96 | .vertical-box { 97 | @extend .node-type; 98 | background-color: transparent; 99 | font-family: "Teko", sans-serif; 100 | 101 | input { 102 | @extend .header-input; 103 | width: 6.3rem; 104 | } 105 | 106 | } 107 | 108 | .vertical-noir-box { 109 | display: grid; 110 | background-color: $dark-noir; 111 | text-align: center; 112 | padding: .3rem 1rem; 113 | } 114 | } 115 | 116 | .info-element { 117 | display: flex; 118 | align-items: center; 119 | justify-content: center; 120 | 121 | .stat-number { 122 | text-align: center; 123 | 124 | p { 125 | font-family: "Teko", sans-serif; 126 | font-size: 2.5rem; 127 | margin: 0; 128 | height: 3.3rem; 129 | } 130 | 131 | } 132 | } 133 | 134 | .more-information { 135 | display: grid; 136 | align-items: center; 137 | justify-content: center; 138 | text-align: center; 139 | margin-top: 1rem; 140 | } 141 | 142 | .info-box { 143 | padding: 1rem; 144 | font-family: "Roboto", sans-serif; 145 | overflow: hidden; 146 | 147 | .info-subtitle { 148 | padding-top: 1rem; 149 | text-align: center; 150 | font-weight: bold; 151 | } 152 | 153 | .credits { 154 | a { 155 | color: $primary; 156 | text-decoration: none; 157 | } 158 | } 159 | } 160 | 161 | .info-section-title { 162 | font-family: "Roboto", sans-serif; 163 | font-weight: bold; 164 | margin: 0; 165 | 166 | p { 167 | margin: 0; 168 | } 169 | 170 | hr { 171 | color: $retro-green; 172 | margin-top: .4rem; 173 | margin-bottom: 1rem; 174 | } 175 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ATTACK-Force-Graph 3 | - Generate and visualize MITRE's ATTACK data in a 3D force-directed graph with a UI made specifically for the ATTACK data 4 | 5 |  6 | 7 | ## DEMO: 8 | 9 | - ## [View Demo (GitHub Pages)](https://osv22.github.io/ATTACK-Force-Graph/main-site/index.html) 10 | 11 | ## Preview: 12 |  13 | 14 | 15 | ## TODO: 16 | - [ ] Add affiliation template 17 | - [ ] Add speciality template 18 | - [ ] Add attacks/ targets template 19 | - [ ] Optimize time complexity for certain sections 20 | 21 | ## What? 22 | - Generate JSON force-graph data nodes and links from the MITRE ATTACK Framework based on your preferences. (`graph_generator.py`) 23 | - Use that data to visualize your results with a UI tailored around the ATTACK data. (`frontend` directory) 24 | 25 | - The JSON data generated is formatted for use with @vasturiano's awesome various force-directed graph projects. (Tested and works on both 2D and 3D versions) 26 | - At the moment you can do the following combinations: 27 | 1. Group -> Group Tools -> Group Tool Techniques: 28 | - Each group (APT) node will be linked to the corresponding tools/ software the group uses along. 29 | - Total node connections: **(group * *n* group tools * *n* tool techniques)** 30 | 2. Group -> Group Tools/ Software: 31 | - Links just the tools without fetching tool techniques data 32 | - Total node connections: **(group * *n* group tools)** 33 | 3. Groups Only 34 | - A node for each group will be generated but no links. (What would you link a group to?!) 35 | - Total node connections: 0 36 | 4. Other 37 | - You can generate standalone nodes of groups or group tools/ software. 38 | 39 | ## Why? 40 | - Draw conclusions and spot patterns fast thanks to the power of data visualization. 41 | - Present compelling data stakeholders can easily understand and interact with. 42 | - Force-directed graphs are seriously... :sunglasses: *cool* 43 | 44 | ## How? 45 | - The graph generator script uses [@Cyb3rWard0g's](https://github.com/Cyb3rWard0g) [ATTACK-Python-Client](https://github.com/OTRF/ATTACK-Python-Client) to build the ATTACK graph data. Having used MITRE's STIX... just use Roberto's package. 46 | - Lift requests are network requests and can be time-consuming. Efficiency improvements planned after the main portion is done. 47 | - The 3D graph in the UI is the [3D version](https://github.com/vasturiano/3d-force-graph) of [vasturiano's](https://github.com/vasturiano) awesome JS force-directed graphs. 48 | 49 | ## Example Demos 50 | - ## [Full ATTACK data demo with UI](https://osv22.github.io/ATTACK-Force-Graph/main-site/index.html) 51 | - Graphs Only: 52 | - [Example 0: Only Group Nodes](https://osv22.github.io/ATTACK-Force-Graph/show-example/example0/group30.html) 53 | - [Example 1: Group -> Tool(s)](https://osv22.github.io/ATTACK-Force-Graph/show-example/example1/group30-tools.html) 54 | - [Example 2: Group -> Group Tool(s) -> Tool Technique(s)](https://osv22.github.io/ATTACK-Force-Graph/show-example/example2/group10-tool-teq.html) 55 | - [Example 3 - ALL Group -> Tools -> Techniques (1MB of JSON Data)](https://osv22.github.io/ATTACK-Force-Graph/show-example/example3/all-group-tool-teqs.html) 56 | 57 | ## Examples 58 |  59 |  60 |  61 |  62 | 63 | ## Data Preview 64 | - NOTE: Specific values are not part of the MITRE ATTACK Framework: 65 | - Group: `affiliation` - This is subjective and can be *problematic*, so it is up to your assessment to evaluate which group is affiliated with whom. 66 | - Group: `targets` - Work-in-progress for defaults but hard to maintain over time given that it is not part of the ATTACK framework. 67 | - Group: `speciality` - Work-in-progress and is much easier to maintain. 68 | 69 | 70 | - `val` - This is can be used to determine how big you want a node element to be. 71 | - For groups: The `val` is based on the number of tools the group uses 72 | - For tools: The `val` is based on the number of techniques the tool makes possible 73 | - For techniques: Set to `None` by default, leaving it up to you 74 | 75 | ### Group Node 76 | ``` 77 | "id": "MITRE GROUP ID HERE. EX: G0005", 78 | "type": "group", 79 | "val": 2, 80 | "attributes": { 81 | "name": "Cool Group/ APT Name Here", 82 | "aliases": [ 83 | "Group alias", 84 | "Operation Golden Kitty", 85 | ], 86 | "description": "Cleaned description. Do not use `get_desc()` if you want raw desc...", 87 | "affiliation": "You have to assign this value yourself, read the NOTE above :)", 88 | "targets": ["Also you have to", "set this. Read NOTE above"], 89 | "speciality": ["set this based on your assessment", "Read NOTE above"], 90 | "tools": { 91 | "tool_id_here": "tool_name_here", 92 | "S0225": "sqlmap", 93 | }, 94 | "techniques": { 95 | "technique_id_here": "technique_name_here", 96 | "T1566.003": "Spearphishing via Service", 97 | } 98 | } 99 | ``` 100 | ### Tool Node 101 | ``` 102 | "id": "MITRE Software ID HERE. EX: S0084", 103 | "type": "tool", 104 | "val": 1 105 | "attributes": { 106 | "name": "tool_name_here", 107 | "aliases": [], 108 | "labels": [ 109 | "tool" 110 | ], 111 | "description": "Cleaned description. Do not use `get_desc()` if you want raw desc...", 112 | "platforms": [ 113 | "Linux", 114 | ... 115 | ], 116 | "techniques": { 117 | "T1190": "Exploit Public-Facing Application" 118 | } 119 | } 120 | ``` 121 | 122 | ### Technique Node 123 | - `val` is set to `None` by default, you can set it to length of platforms or whatever your preference is. 124 | ``` 125 | "id": "MITRE Technique ID HERE. EX: T1190", 126 | "type": "technique", 127 | "val": null, 128 | "attributes": { 129 | "name": "Exploit Public-Facing Application", 130 | "chain_phase": "initial-access", 131 | "description": "Cleaned description. Do not use `get_desc()` if you want raw desc.", 132 | "detection": "Cleaned detection. Do not use `get_desc()` if you want raw detection paragraph...", 133 | "is_subtype": false, 134 | "platforms": [ 135 | "Windows", 136 | ... 137 | ] 138 | } 139 | ``` 140 | -------------------------------------------------------------------------------- /frontend/dist/js/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | updateSideBar 3 | } from "./sidebar.js"; 4 | 5 | // --------------------------------- 6 | // GRAPH 7 | // --------------------------------- 8 | 9 | const defaultData = "entry_ex_groups_tools.json" 10 | 11 | const groupNodeColor = "red"; 12 | const toolNodeColor = "cyan"; 13 | const techniqueNodeColor = "#9157EB"; 14 | const defaultNodeColor = "yellow"; 15 | 16 | const graphElement = document.getElementById("3d-graph"); 17 | 18 | const Graph = ForceGraph3D({ 19 | extraRenderers: [new THREE.CSS2DRenderer()] 20 | }) 21 | (graphElement) 22 | .jsonUrl(defaultData) 23 | .nodeLabel(node => { 24 | return node.attributes.name 25 | }) 26 | .nodeLabel(node => { 27 | return node.attributes.name 28 | }) 29 | .nodeColor(node => { 30 | switch (node.type) { 31 | case "group": 32 | return groupNodeColor; 33 | case "tool": 34 | return toolNodeColor; 35 | case "technique": 36 | return techniqueNodeColor; 37 | default: 38 | return defaultNodeColor; 39 | } 40 | }) 41 | .linkDirectionalArrowLength(3.5) 42 | .linkDirectionalArrowRelPos(1) 43 | .linkCurvature(0.25) 44 | .onNodeClick(node => { 45 | // Aim at node from outside it 46 | const distance = 80; 47 | const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z); 48 | 49 | Graph.cameraPosition({ 50 | x: node.x * distRatio, 51 | y: node.y * distRatio, 52 | z: node.z * distRatio 53 | }, // new position 54 | node, // lookAt ({ x, y, z }) 55 | 3000 // ms transition duration 56 | ); 57 | 58 | updateSideBar(node); 59 | hideGuide(true); 60 | 61 | // Will be moved later on 62 | // document.getElementById("affiliation-avatar").src = `assets/icons/states/doc.png`; 63 | }) 64 | .showNavInfo(false); 65 | 66 | 67 | // Make Graph Responsive 68 | window.addEventListener("resize", () => { 69 | Graph.width(innerWidth); 70 | Graph.height(innerHeight); 71 | }); 72 | 73 | 74 | // --------------------------------- 75 | // GRAPH OPTIONS 76 | // --------------------------------- 77 | 78 | Graph.d3Force("charge").strength(-120); 79 | 80 | Graph 81 | .warmupTicks(0) // Adjust number of iterations to taste 82 | .cooldownTicks(1000) 83 | .nodeThreeObject(node => { 84 | if (node.type === "group") { 85 | const nodeEl = document.createElement('div'); 86 | nodeEl.textContent = node.attributes.name; 87 | nodeEl.style.color = "#FF585B"; 88 | nodeEl.style.backgroundColor = "rgba(0, 0, 0, .30)"; 89 | nodeEl.style.padding = "0 10px"; 90 | nodeEl.className = "node-label"; 91 | return new THREE.CSS2DObject(nodeEl); 92 | } else if (node.type === "tool") { 93 | const nodeEl = document.createElement('div'); 94 | nodeEl.textContent = node.attributes.name; 95 | nodeEl.style.color = "cyan"; 96 | nodeEl.className = "node-label"; 97 | return new THREE.CSS2DObject(nodeEl); 98 | } 99 | 100 | }) 101 | .nodeThreeObjectExtend(true) 102 | // A few feaures that will be added later 103 | 104 | 105 | // .nodeThreeObject(node => { 106 | // const sprite = new SpriteText(node.id); 107 | // sprite.material.depthWrite = false; // make sprite background transparent 108 | // sprite.color = "#90ff00"; 109 | // sprite.textHeight = 8; 110 | // return sprite; 111 | // }) 112 | 113 | 114 | // --------------------------------- 115 | // CAMERA 116 | // --------------------------------- 117 | function resetCamera() { 118 | Graph.cameraPosition({ 119 | x: 0, 120 | y: 0, 121 | z: 500 122 | }) 123 | } 124 | 125 | document.getElementById("btn-camera").addEventListener("click", () => { 126 | resetCamera(); 127 | }); 128 | 129 | 130 | // --------------------------------- 131 | // GENERAL 132 | // --------------------------------- 133 | function changeGraphData(newGraph) { 134 | Graph.jsonUrl(`${newGraph}.json`); 135 | } 136 | 137 | function hideFullDataWarning(bool) { 138 | document.getElementById("warning-box").hidden = bool; 139 | } 140 | 141 | function hideGuide(bool) { 142 | bool === true ? document.getElementById("guide-box").hidden = true : false; 143 | } 144 | 145 | function disableLinks(graph, bool) { 146 | bool === true ? graph.linkVisibility(false) : graph.linkVisibility(true); 147 | } 148 | 149 | // --------------------------------- 150 | // DATA INPUT 151 | // --------------------------------- 152 | function setDataBtnActive(dataButtonId) { 153 | document.getElementById(dataButtonId).classList.add("active"); 154 | } 155 | 156 | function setDataBtnInactive(dataButtonId) { 157 | document.getElementById(dataButtonId).classList.remove("active"); 158 | } 159 | 160 | function deactivateBtnGroup(btnGroup) { 161 | if (btnGroup === "mitre") { 162 | setDataBtnInactive("full-groups"); 163 | setDataBtnInactive("full-group-tools"); 164 | setDataBtnInactive("full-group-tools-teqs"); 165 | 166 | hideFullDataWarning(true); 167 | } else if (btnGroup === "examples") { 168 | setDataBtnInactive("example-groups"); 169 | setDataBtnInactive("example-group-tools"); 170 | setDataBtnInactive("example-group-tools-teqs"); 171 | 172 | } 173 | 174 | } 175 | 176 | // EXAMPLES 177 | document.getElementById("example-groups").addEventListener("click", () => { 178 | changeGraphData("data/ex_groups"); 179 | 180 | setDataBtnActive("example-groups"); 181 | setDataBtnInactive("example-group-tools"); 182 | setDataBtnInactive("example-group-tools-teqs"); 183 | 184 | deactivateBtnGroup("mitre"); 185 | }); 186 | 187 | document.getElementById("example-group-tools").addEventListener("click", () => { 188 | changeGraphData("data/ex_groups_tools"); 189 | 190 | setDataBtnActive("example-groups"); 191 | setDataBtnActive("example-group-tools"); 192 | setDataBtnInactive("example-group-tools-teqs"); 193 | 194 | deactivateBtnGroup("mitre"); 195 | }); 196 | 197 | document.getElementById("example-group-tools-teqs").addEventListener("click", () => { 198 | changeGraphData("data/ex_groups_tools_teqs"); 199 | 200 | setDataBtnActive("example-groups"); 201 | setDataBtnActive("example-group-tools"); 202 | setDataBtnActive("example-group-tools-teqs"); 203 | 204 | deactivateBtnGroup("mitre"); 205 | }); 206 | 207 | // MITRE 208 | document.getElementById("full-groups").addEventListener("click", () => { 209 | changeGraphData("data/all_groups"); 210 | setDataBtnActive("full-groups"); 211 | setDataBtnInactive("full-group-tools"); 212 | setDataBtnInactive("full-group-tools-teqs"); 213 | 214 | deactivateBtnGroup("examples"); 215 | }); 216 | 217 | document.getElementById("full-group-tools").addEventListener("click", () => { 218 | changeGraphData("data/all_groups_tools"); 219 | setDataBtnActive("full-groups"); 220 | setDataBtnActive("full-group-tools"); 221 | setDataBtnInactive("full-group-tools-teqs"); 222 | 223 | deactivateBtnGroup("examples"); 224 | hideFullDataWarning(false); 225 | 226 | }); 227 | 228 | document.getElementById("full-group-tools-teqs").addEventListener("click", () => { 229 | changeGraphData("data/all_groups_tools_teqs"); 230 | 231 | setDataBtnActive("full-groups"); 232 | setDataBtnActive("full-group-tools"); 233 | setDataBtnActive("full-group-tools-teqs"); 234 | 235 | deactivateBtnGroup("examples"); 236 | hideFullDataWarning(false); 237 | }); 238 | 239 | // Links Visibility 240 | const miterWrapper = document.getElementById('mitre-data-wrapper'); 241 | 242 | miterWrapper.addEventListener('click', (event) => { 243 | const isButton = event.target.nodeName === 'BUTTON'; 244 | if (isButton) { 245 | disableLinks(Graph, true); 246 | } 247 | }) 248 | 249 | const examplesWrapper = document.getElementById('examples-data-wrapper'); 250 | examplesWrapper.addEventListener('click', (event) => { 251 | const isButton = event.target.nodeName === 'BUTTON'; 252 | if (isButton) { 253 | disableLinks(Graph, false); 254 | } 255 | }) -------------------------------------------------------------------------------- /graph_generator.py: -------------------------------------------------------------------------------- 1 | from os import link 2 | from attackcti import attack_client 3 | import json 4 | import re 5 | 6 | 7 | lift = attack_client() 8 | groups = lift.get_groups() 9 | groups = lift.remove_revoked(groups) 10 | 11 | json_data = { 12 | "nodes": [], 13 | "links": [] 14 | } 15 | 16 | all_tools = [] 17 | all_techniques = [] 18 | 19 | 20 | def remove_dupe(group_list, verbose=False): 21 | """ 22 | Sometimes certain groups turn out to be a single group 23 | and are then merged, resulting in duplicates with different names. 24 | This is on top of `remove_revoked`, just to be safe 25 | """ 26 | for idx, group in enumerate(group_list): 27 | group = group_list[idx] 28 | 29 | try: 30 | group.aliases 31 | except AttributeError: 32 | if verbose: 33 | print(f"[+] {group.name}: NO aliases found") 34 | 35 | for inner_group in group_list: 36 | try: 37 | if group.name in inner_group.aliases: 38 | if verbose: 39 | print(f"\t Found {group.name} as an alias for: {inner_group.name}\n") 40 | group_list.remove(group) 41 | except AttributeError: 42 | continue 43 | 44 | if verbose: 45 | print(f"[!] Done Cleaning Duplicate Groups\n") 46 | 47 | 48 | def get_desc(md_text): 49 | #! Can run remove_dupe here if we really wanted 50 | try: 51 | rm_citations = re.sub(r"\(Citation([^\)]+)\)", "", md_text) 52 | rm_mitre_links = re.sub(r"\(https([^\)]+)\)", "", rm_citations) 53 | clean_desc = rm_mitre_links.translate(str.maketrans({'[':None, ']':None})).strip() 54 | return clean_desc 55 | # In case a dupe with no desc makes it through 56 | except AttributeError: 57 | return "No Description Available Yet" 58 | 59 | 60 | def get_item_ids(item_list): 61 | return [item.external_references[0].external_id for item in item_list] 62 | 63 | def get_item_names(item_list): 64 | return [item["name"] for item in item_list] 65 | 66 | def get_id_name(item_list): 67 | items_dict = {} 68 | for item in item_list: 69 | items_dict[item.external_references[0].external_id] = item.name 70 | return items_dict 71 | 72 | def make_group_node(group, group_software, group_techniques, link_tool=False, 73 | link_tool_techniques=False, lift_tool_techniques=False): 74 | 75 | group_id = group.external_references[0].external_id 76 | group_name = group.name 77 | group_description = get_desc(group.description) 78 | node_type = "group" 79 | 80 | # For you to decide/ evaluate 81 | group_affiliation = "" 82 | group_targets = [] 83 | group_speciality = [] 84 | 85 | if len(group_software) > 200: # Invalid groups return a list of all 500+(all) tools 86 | group_val = 0 87 | group_tools = [] 88 | else: 89 | # group_tools = list(set(get_item_ids(group_software))) # To get unique tools only 90 | group_tools = get_id_name(group_software) 91 | group_val = len(group_tools) 92 | 93 | try: 94 | group_aliases = group.aliases 95 | # We already know the group's actual name is an "alias" 96 | group_aliases.remove(group_name) 97 | except: 98 | group_aliases = [] 99 | 100 | try: 101 | group_techniques = get_id_name(group_techniques) 102 | except: 103 | group_techniques = [] 104 | 105 | new_group_node = { 106 | "id": group_id, 107 | "type": node_type, 108 | "val": group_val, 109 | "attributes": { 110 | "name": group_name, 111 | "aliases": group_aliases, 112 | "description": group_description, 113 | "affiliation": group_affiliation, 114 | "targets": group_targets, 115 | "speciality": group_speciality, 116 | "tools": group_tools, 117 | "techniques": group_techniques, 118 | } 119 | }, 120 | 121 | 122 | json_data["nodes"] += new_group_node 123 | 124 | if link_tool and (len(group_software) < 150): 125 | for tool in group_software: 126 | tool_id = tool.external_references[0].external_id 127 | 128 | if tool_id not in all_tools: 129 | make_tool_node(tool, link_techniques=link_tool_techniques, lift_techniques=lift_tool_techniques) 130 | all_tools.append(tool_id) 131 | 132 | new_link = { 133 | "source": group_id, 134 | "target": tool_id, 135 | }, 136 | 137 | json_data["links"] += new_link 138 | 139 | 140 | def make_tool_node(tool, link_techniques=False, lift_techniques=False): 141 | 142 | tool_name = tool.name 143 | tool_id = tool.external_references[0].external_id 144 | tool_description = get_desc(tool.description) 145 | node_type = "tool" 146 | 147 | try: 148 | tool_platforms = tool.x_mitre_platforms 149 | except: 150 | tool_platforms = [] 151 | 152 | try: 153 | tool_labels = tool.labels 154 | except: 155 | tool_labels = [] 156 | 157 | try: 158 | tool_aliases = tool.x_mitre_aliases 159 | # We already know the tool's actual name is an "alias" 160 | tool_aliases.remove(tool_name) 161 | except: 162 | tool_aliases = [] 163 | 164 | new_tool_node = { 165 | "id": tool_id, 166 | "type": node_type, 167 | "attributes": { 168 | "name": tool_name, 169 | "aliases": tool_aliases, 170 | "labels": tool_labels, 171 | "description": tool_description, 172 | "platforms": tool_platforms, 173 | } 174 | }, 175 | 176 | if lift_techniques or link_techniques: 177 | lift_teqs = lift.get_techniques_used_by_software(tool) 178 | 179 | if len(lift_teqs) < 100: 180 | tool_techniques = get_id_name(lift_teqs) 181 | else: 182 | tool_techniques = [] 183 | 184 | new_tool_node[0]["val"] = len(tool_techniques) 185 | new_tool_node[0]["attributes"]["techniques"] = tool_techniques 186 | 187 | if link_techniques: 188 | 189 | for technique in lift_teqs: 190 | technique_id = technique.external_references[0].external_id 191 | 192 | if technique_id not in all_techniques: 193 | make_technique_node(technique) 194 | all_techniques.append(technique_id) 195 | 196 | new_link = { 197 | "source": tool_id, 198 | "target": technique_id, 199 | }, 200 | json_data["links"] += new_link 201 | 202 | json_data["nodes"] += new_tool_node 203 | 204 | 205 | def make_technique_node(technique): 206 | technique_id = technique.external_references[0].external_id 207 | technique_name = technique.name 208 | technique_val = None 209 | node_type = "technique" 210 | 211 | try: 212 | chain_phase = technique.kill_chain_phases[0].phase_name 213 | except: 214 | chain_phase = None 215 | 216 | try: 217 | technique_description = get_desc(technique.description) 218 | except: 219 | technique_description = "No Description Available Yet" 220 | 221 | try: 222 | technique_detection = technique.x_mitre_detection 223 | except: 224 | technique_detection = [] 225 | 226 | try: 227 | technique_platforms = technique.x_mitre_platforms 228 | except: 229 | technique_platforms = [] 230 | 231 | try: 232 | is_sub_technique = technique.x_mitre_is_subtechnique 233 | except: 234 | is_sub_technique = False 235 | 236 | new_technique_node = { 237 | "id": technique_id, 238 | "type": node_type, 239 | "val": technique_val, 240 | "attributes": { 241 | "name": technique_name, 242 | "chain_phase": chain_phase, 243 | "description": technique_detection, 244 | "detection": technique_description, 245 | "is_subtype": is_sub_technique, 246 | "platforms": technique_platforms, 247 | } 248 | }, 249 | 250 | json_data["nodes"] += new_technique_node 251 | 252 | 253 | def main(): 254 | for group in groups: 255 | group_software = lift.get_software_used_by_group(group) 256 | group_techniques = lift.get_techniques_used_by_group(group) 257 | make_group_node(group, group_software, group_techniques, link_tool=True, lift_tool_techniques=True) 258 | 259 | print(f"Finished Group: {group.name}") 260 | 261 | with open('force_graph_data.json', 'w') as f: 262 | json.dump(json_data, f, ensure_ascii=False, indent=4) 263 | 264 | 265 | if __name__ == "__main__": 266 | main() -------------------------------------------------------------------------------- /frontend/dist/js/sidebar.js: -------------------------------------------------------------------------------- 1 | // --------------------------------- 2 | // INFO BAR - Sidebar 3 | // --------------------------------- 4 | 5 | // General 6 | 7 | function nodeColors(regualrColor, disabledColor) { 8 | document.getElementById("infoSidebar").style.borderLeft = "5px solid " + regualrColor; 9 | document.getElementById("set-name").style.color = regualrColor; 10 | document.getElementById("set-type").style.backgroundColor = regualrColor; 11 | document.getElementById("set-node-title").style.backgroundColor = disabledColor; 12 | document.getElementById("toggle-info-btn").style.backgroundColor = regualrColor; 13 | } 14 | 15 | export function setNodeColor(nodeType) { 16 | 17 | const groupColor = "#FF585B"; 18 | const groupDisabled = "rgba(255, 88, 91, .15)"; 19 | 20 | const toolColor = "#4BDFFF"; 21 | const toolDisabled = "rgba(4, 211, 255, .15)"; 22 | 23 | const techniqueColor = "#9157EB"; 24 | const techniqueDisabled = "rgba(91, 116, 231, .15)"; 25 | 26 | const defaultColor = "#4BFFBE"; 27 | const defaultDisabled = "#2bffbf6b"; 28 | 29 | 30 | if (nodeType === "group") { 31 | nodeColors(groupColor, groupDisabled); 32 | 33 | } else if (nodeType === "tool") { 34 | nodeColors(toolColor, toolDisabled); 35 | 36 | } else if (nodeType === "technique") { 37 | nodeColors(techniqueColor, techniqueDisabled); 38 | 39 | } else { 40 | nodeColors(defaultColor, defaultDisabled); 41 | } 42 | } 43 | 44 | function setMitreLink(nodeID, nodeType) { 45 | if (nodeID) { 46 | let mitreUrl = "" 47 | if (nodeID.indexOf(".") > -1) { 48 | const idToUrl = nodeID.replace(".", "/"); 49 | mitreUrl = `https://attack.mitre.org/${(nodeType != "tool") ? nodeType+"s" : "software"}/${idToUrl}/`; 50 | } else { 51 | mitreUrl = `https://attack.mitre.org/${(nodeType != "tool") ? nodeType+"s" : "software"}/${nodeID}/`; 52 | } 53 | document.getElementById("set-mitre-link").href = mitreUrl; 54 | document.getElementById("set-mitre-id").innerHTML = ` | ${nodeID}`; 55 | 56 | } else { 57 | document.getElementById("set-mitre-link").href = "https://attack.mitre.org/"; 58 | document.getElementById("set-mitre-id").innerHTML = " ATT&CK"; 59 | 60 | } 61 | 62 | } 63 | 64 | export function setNodeName(nodeName) { 65 | document.getElementById("set-name").innerHTML = nodeName; 66 | } 67 | 68 | export function setNodeType(nodeType) { 69 | document.getElementById("set-type").value = (nodeType != "technique") ? nodeType : "Techn." 70 | } 71 | 72 | function setChainPhase(nodeChainPhase) { 73 | document.getElementById("set-chain-phase").innerHTML = nodeChainPhase; 74 | } 75 | 76 | 77 | function setDescription(nodeDescription) { 78 | document.getElementById("set-description").innerHTML = nodeDescription; 79 | } 80 | 81 | // NODE SPECIFIC 82 | function setDetection(nodeDeteciton) { 83 | document.getElementById("set-detection").innerHTML = nodeDeteciton; 84 | } 85 | 86 | 87 | function setAliases(nodeAliases) { 88 | if (nodeAliases) { 89 | const aliases = nodeAliases; 90 | const aliasesList = document.getElementById("set-aliases"); 91 | aliasesList.innerHTML = ""; 92 | aliases.forEach(alias => { 93 | const li = document.createElement("li"); 94 | li.innerHTML = alias; 95 | aliasesList.appendChild(li); 96 | }); 97 | } 98 | } 99 | 100 | 101 | function setPlatformsTargeted(nodePlatforms) { 102 | if (nodePlatforms) { 103 | const platforms = nodePlatforms; 104 | const platformsList = document.getElementById("set-platforms-targeted"); 105 | platformsList.innerHTML = ""; 106 | platforms.forEach(platform => { 107 | const li = document.createElement("li"); 108 | li.innerHTML = platform; 109 | platformsList.appendChild(li); 110 | }); 111 | } 112 | 113 | } 114 | 115 | 116 | function setGroupTools(groupTools) { 117 | if (groupTools) { 118 | const toolsDiv = document.getElementById("set-group-tools"); 119 | toolsDiv.innerHTML = ""; 120 | for (const tool in groupTools) { 121 | const li = document.createElement("li"); 122 | li.innerHTML = groupTools[tool]; 123 | toolsDiv.appendChild(li); 124 | } 125 | } 126 | } 127 | 128 | 129 | function setGroupTechniques(groupTechniques) { 130 | if (groupTechniques) { 131 | const technqiuesList = document.getElementById("set-group-techniques"); 132 | technqiuesList.innerHTML = ""; 133 | for (const teq in groupTechniques) { 134 | const li = document.createElement("li"); 135 | li.innerHTML = groupTechniques[teq]; 136 | technqiuesList.appendChild(li); 137 | } 138 | } 139 | } 140 | 141 | 142 | function setToolTechniques(toolTechniques) { 143 | if (toolTechniques) { 144 | const techniquesList = document.getElementById("set-tool-techniques"); 145 | techniquesList.innerHTML = ""; 146 | for (const teq in toolTechniques) { 147 | const li = document.createElement("li"); 148 | li.innerHTML = toolTechniques[teq]; 149 | techniquesList.appendChild(li); 150 | } 151 | } 152 | } 153 | 154 | 155 | function setQuickStats(node, nodeType) { 156 | if (nodeType === "group") { 157 | const toolCount = document.getElementById("tools-count"); 158 | const techniqueCount = document.getElementById("technique-count"); 159 | 160 | const nodeToolCount = Object.keys(node.attributes.tools).length; 161 | toolCount.innerHTML = nodeToolCount; 162 | 163 | const nodeTechniqueCount = Object.keys(node.attributes.techniques).length; 164 | techniqueCount.innerHTML = nodeTechniqueCount; 165 | } 166 | 167 | } 168 | 169 | 170 | function showInfoSection(sectionID) { 171 | document.getElementById(`${sectionID}`).hidden = false; 172 | } 173 | 174 | function hideInfoSection(sectionID) { 175 | document.getElementById(`${sectionID}`).hidden = true; 176 | } 177 | 178 | 179 | function isValid(obj) { 180 | if (obj && ((obj.length > 0) || (Object.keys(obj).length > 0))) { 181 | return true 182 | } 183 | return false 184 | } 185 | 186 | function showGroupSpecifics(node) { 187 | if (node.type === "group") { 188 | setQuickStats(node, "group"); 189 | showInfoSection("group-quick-stats-box"); 190 | 191 | // GROUP TOOLS 192 | if (isValid(node.attributes.tools)) { 193 | showInfoSection("group-tools-box"); 194 | setGroupTools(node.attributes.tools); 195 | } 196 | // GROUP TECHNIQUES 197 | if (isValid(node.attributes.techniques)) { 198 | showInfoSection("group-techniques-box"); 199 | setGroupTechniques(node.attributes.techniques); 200 | } 201 | } else { 202 | hideInfoSection("group-quick-stats-box"); 203 | hideInfoSection("group-tools-box"); 204 | hideInfoSection("group-techniques-box"); 205 | } 206 | } 207 | 208 | function showToolSpecifics(node) { 209 | if (node.type === "tool") { 210 | // TOOL TECHNIQUES 211 | if (isValid(node.attributes.techniques)) { 212 | showInfoSection("tool-techniques-box"); 213 | setToolTechniques(node.attributes.techniques); 214 | } 215 | // PLATFORMS 216 | if (isValid(node.attributes.platforms)) { 217 | showInfoSection("platforms-targeted-box"); 218 | setPlatformsTargeted(node.attributes.platforms); 219 | } 220 | } else { 221 | hideInfoSection("tool-techniques-box"); 222 | hideInfoSection("platforms-targeted-box"); 223 | } 224 | } 225 | 226 | function showTechniqueSpecifics(node) { 227 | if (node.type == "technique") { 228 | // CHAIN PHASE 229 | if (isValid(node.attributes.chain_phase)) { 230 | showInfoSection("chain-phase-box"); 231 | setChainPhase(node.attributes.chain_phase); 232 | } 233 | // DETECTION 234 | if (isValid(node.attributes.detection)) { 235 | showInfoSection("detection-box"); 236 | setDetection(node.attributes.detection); 237 | } 238 | } else { 239 | hideInfoSection("chain-phase-box"); 240 | hideInfoSection("detection-box"); 241 | } 242 | } 243 | 244 | export function updateSideBar(node) { 245 | if (node) { 246 | setMitreLink(node.id, node.type); 247 | setNodeName(node.attributes.name); 248 | setNodeColor(node.type); 249 | setNodeType(node.type); 250 | 251 | // ALIASES 252 | if (isValid(node.attributes.aliases)) { 253 | showInfoSection("aliases-box"); 254 | setAliases(node.attributes.aliases); 255 | } else { 256 | hideInfoSection("aliases-box"); 257 | } 258 | 259 | // DESCRIPTION 260 | if (isValid(node.attributes.description)) { 261 | showInfoSection("description-box"); 262 | setDescription(node.attributes.description); 263 | } 264 | 265 | showGroupSpecifics(node); 266 | showToolSpecifics(node); 267 | showTechniqueSpecifics(node); 268 | 269 | hideInfoSection("about-what-box"); 270 | hideInfoSection("about-why-box"); 271 | hideInfoSection("about-credits-box"); 272 | 273 | 274 | } else { 275 | setMitreLink(); 276 | 277 | hideInfoSection("description-box"); 278 | hideInfoSection("aliases-box"); 279 | hideInfoSection("chain-phase-box"); 280 | hideInfoSection("detection-box"); 281 | hideInfoSection("platforms-targeted-box"); 282 | hideInfoSection("group-quick-stats-box"); 283 | hideInfoSection("group-tools-box"); 284 | hideInfoSection("group-techniques-box"); 285 | hideInfoSection("tool-techniques-box"); 286 | 287 | showInfoSection("about-what-box") 288 | showInfoSection("about-why-box") 289 | showInfoSection("about-credits-box") 290 | } 291 | 292 | openSidebar(); 293 | } -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |Click a node to learn more about the group, tool, or technique!
74 |Relationship links have been disabled due to the large number of connections.
77 |Group
325 |Tool
329 |Technique
333 |