├── requirements.txt ├── LICENSE ├── README.md └── attackintel.py /requirements.txt: -------------------------------------------------------------------------------- 1 | termcolor 2 | requests 3 | time 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # UPDATE (26 Nov 2018) 3 | MITRE has deprecated the ATT&CK API functionality and will soon be phasing it out. They have moved to utilizing STIX. My code will be available for reference but you can check out my ATT&CK Intel webapp which is in the preliminary phases of development and offers the same info. The link is below. I'm always looking to improve. Got a suggestion for the webapp, please drop me a suggestion at gr4ym4ntx[at]gmail[dot]com. Enjoy. 4 | 5 | - [ATT&CK INTEL Webapp](https://gr4ym4ntx.pythonanywhere.com/) 6 | 7 | # ATT&CK Intel 8 | A simple python script to query the MITRE ATT&CK API for tactics, techniques, mitigations, & detection methods for specific threat groups. 9 | 10 | # Goals 11 | - Quickly align updated tactics, techniques, mitigation, and detection information from MITRE ATT&CK API for a specific threat 12 | - Brush up on my python skills and get familiar with GIT while drinking coffee 13 | 14 | # How To 15 | Use one of two methods: 16 | - If (python3 is installed): 17 | - Download script from git 18 | - `pip3 install -r requirements.txt` 19 | - `python3 attackintel.py` 20 | - Else: 21 | - Cut & paste script from git into your favorite [online python emulator](https://repl.it/languages/python3) 22 | - Select a threat number from the menu to get tactics, techniques, mitigation, and detection information 23 | 24 | # Resources 25 | - [MITRE ATT&CK API](https://attack.mitre.org/wiki/Using_the_API) 26 | 27 | # Requirements 28 | - Python ver.3+ 29 | 30 | # Limitations 31 | - Can only select a single threat group at a time 32 | - Information is only displayed to the screen (for now) 33 | 34 | # Contribute 35 | - New ideas are great! Got ideas for improvement, submit a PR. Thanks! 36 | -------------------------------------------------------------------------------- /attackintel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import importlib.util 3 | import pip 4 | import urllib.parse 5 | import requests 6 | from sys import exit 7 | from termcolor import colored, cprint 8 | from time import sleep 9 | 10 | class attackintel(): 11 | """Quickly dump known tactics/techniques of a specific Mitre Threat & find the Mitre detection/mitigation references""" 12 | 13 | __version__ = "0.1" 14 | __author__ = "gr4ym4ntx" 15 | 16 | def check_install(self): 17 | # Check for non-standard packages 18 | pkgs_req = ['requests','termcolor','time'] 19 | for pkg in pkgs_req: 20 | spec = importlib.util.find_spec(pkg) 21 | if spec is None: 22 | print(pkg + " is not installed \n") 23 | pip.main(['install',pkg]) 24 | print(pkg + " is now installed \n") 25 | print('Module check complete.') 26 | 27 | def get_URL(self, qry): 28 | # Build Encoded MITRE Query URL 29 | cprint("Mitre query built ...", 'green') 30 | return ('https://attack.mitre.org/api.php?action=ask&format=json&query=' + urllib.parse.quote(qry)) 31 | 32 | def get_json(self, URL): 33 | # Gather returned JSON data from MITRE website 34 | try: 35 | requests.get(URL).raise_for_status() 36 | cprint("Successfully retrieved the info from Mitre", 'cyan') 37 | return(requests.get(URL).json()) 38 | except requests.exceptions.ConnectionError: 39 | cprint("Cannot connect. Verify URL is valid and try again ... exiting.", 'red') 40 | exit() 41 | except requests.exceptions.HTTPError: 42 | err_code = requests.get(URL).status_code 43 | cprint("Failed to connect to Mitre. Error code {} returned. Try re-running script .. exiting.".format(err_code), 'red') 44 | exit() 45 | #self.user_input() 46 | except: 47 | cprint("Something happened .. rerun script .. exiting.", 'red') 48 | exit() 49 | 50 | def get_techniques(self, grp_json, grp): 51 | idx_json = grp_json['query']['results']['Group/' + grp]['printouts'] 52 | grp_name = idx_json['Has display name'][0] 53 | if not idx_json['Has alias']: 54 | alias_array = [] 55 | else: 56 | alias_array = idx_json['Has alias'] 57 | 58 | # Gather techniques used by the threat actor & dump into array 59 | technique_name_array = [] 60 | technique_id_array = [] 61 | for info in idx_json['Has technique']: 62 | technique_name_array.append(info['displaytitle']) 63 | technique_id_array.append(info["fulltext"]) 64 | cprint("Got techniques for the threat actor ...", 'green') 65 | return (grp_name,alias_array,technique_name_array,technique_id_array) 66 | 67 | def get_technique_info(self, tech_arr): 68 | # Gather returned JSON data for techniques from MITRE 69 | technique_json = [] 70 | for technique_name in tech_arr: 71 | technique_qry = '[[Category:Technique]][[Has display name::' + technique_name + ']]|?Has tactic|?Has technical description#-ia|?Has mitigation#-ia|?Has analytic details#-ia' 72 | technique_json.append(self.get_json(self.get_URL(technique_qry))) 73 | cprint("Got the Mitre information ... dumping info to screen ...", 'green') 74 | sleep(3) 75 | return (technique_json) 76 | 77 | def prt2screen(self, g_name, a_array, t_json, t_id_array, t_name_array): 78 | cprint("\n" + '******************************* ' + 'THREAT REPORT FOR ' + g_name + ' *******************************' + "\n", 'yellow', attrs=['bold']) 79 | cprint("Aliases: ", 'yellow') 80 | for i,v in enumerate(a_array): 81 | print(a_array[i]) 82 | 83 | # Handle JSON data for techniques and print description, detection & mitigation info to screen 84 | for idx,val in enumerate(t_json): 85 | 86 | # Ensure values exist in tNameArr & tIDArr arrays 87 | if not t_id_array[idx] or not t_name_array[idx]: 88 | print("No techniques available") 89 | else: 90 | cprint("\n" + '******************************* ' + t_id_array[idx] + ": " + t_name_array[idx] + ' *******************************' + "\n", 'yellow', attrs=['bold']) 91 | 92 | # Ensure value exist for technique description 93 | if not t_json[idx]['query']['results'][t_id_array[idx]]['printouts']['Has tactic']: 94 | cprint("Tactic: ", 'yellow') 95 | print ('No tactic available') 96 | else: 97 | cprint("Tactic: ", 'yellow') 98 | print (t_json[idx]['query']['results'][t_id_array[idx]]['printouts']['Has tactic'][0]['fulltext'] + "\n") 99 | 100 | # Ensure value exist for technique description 101 | if not t_json[idx]['query']['results'][t_id_array[idx]]['printouts']['Has technical description']: 102 | cprint("Description: ", 'yellow') 103 | print ('No description available') 104 | else: 105 | cprint("Description: ", 'yellow') 106 | print (t_json[idx]['query']['results'][t_id_array[idx]]['printouts']['Has technical description'][0] + "\n") 107 | 108 | # Ensure value exist for technique detection 109 | if not t_json[idx]['query']['results'][t_id_array[idx]]['printouts']['Has analytic details']: 110 | cprint("Detection Tip(s): ", 'yellow') 111 | print ("No tips available") 112 | else: 113 | cprint("Detection Tip(s): ", 'yellow') 114 | print (t_json[idx]['query']['results'][t_id_array[idx]]['printouts']['Has analytic details'][0] + "\n") 115 | 116 | # Ensure value exist for technique mitigation 117 | if not t_json[idx]['query']['results'][t_id_array[idx]]['printouts']['Has mitigation']: 118 | cprint("Mitigation(s): ", 'yellow') 119 | print ("No mitigation available") 120 | else: 121 | cprint("Mitigation(s): ", 'yellow') 122 | print (t_json[idx]['query']['results'][t_id_array[idx]]['printouts']['Has mitigation'][0] + "\n") 123 | 124 | def logo(self): 125 | cprint (''' 126 | /$$$$$$ /$$$$$$$$ /$$$$$$$$ /$$$ /$$$$$$ /$$ /$$ /$$$$$$ /$$ /$$ 127 | /$$__ $$|__ $$__/|__ $$__//$$ $$ /$$__ $$| $$ /$$/ |_ $$_/ | $$ | $$ 128 | | $$ \ $$ | $$ | $$ | $$$ | $$ \__/| $$ /$$/ | $$ /$$$$$$$ /$$$$$$ /$$$$$$ | $$ 129 | | $$$$$$$$ | $$ | $$ /$$ $$/$$| $$ | $$$$$/ | $$ | $$__ $$|_ $$_/ /$$__ $$| $$ 130 | | $$__ $$ | $$ | $$ | $$ $$_/| $$ | $$ $$ | $$ | $$ \ $$ | $$ | $$$$$$$$| $$ 131 | | $$ | $$ | $$ | $$ | $$\ $$ | $$ $$| $$\ $$ | $$ | $$ | $$ | $$ /$$| $$_____/| $$ 132 | | $$ | $$ | $$ | $$ | $$$$/$$| $$$$$$/| $$ \ $$ /$$$$$$| $$ | $$ | $$$$/| $$$$$$$| $$ 133 | |__/ |__/ |__/ |__/ \____/\_/ \______/ |__/ \__/ |______/|__/ |__/ \___/ \_______/|__/ 134 | \n by gr4ym4ntx\n ver: {0}\n'''.format(self.__version__), 'yellow', attrs=['bold']) 135 | 136 | def menu(self): 137 | cprint ("\n" + 'THREAT LIST ::', 'yellow', attrs=['bold','underline']) 138 | cprint (''' 139 | 01 - Axiom, Group 72 11 - PittyTiger 26 - APT18, Threat Group 37 - FIN6 55 - NEODYMIUM 140 | 02 - Moafee 12 - Darkhotel (TG)-0416, Dynamite 38 - Stealth Falcon 56 - PROMETHIUM 141 | 03 - Cleaver, 13 - APT30 Panda 39 - Suckfly 57 - APT34 142 | Threat Group(TG)-2889 14 - Night Dragon 27 - Threat Group (TG)- 40 - Patchwork, Dropping 58 - Charming Kitten 143 | 04 - Ke3chang 15 - Taidoor 3390, Emissary Panda, Elephant, Chinastrats 59 - Magic Hound, Rocket Kitten, 144 | 05 - APT12, IXESHE, DynCalc, 16 - APT29, The Dukes BRONZE UNION 41 - Project Sauron Operation Saffron Rose, 145 | Numbered Panda, DNSCALC Cozy Bear, Cozy Duke 28 - Threat Group (TG)- 42 - MONSOON, Operation Ajax Security Team, Operation 146 | 06 - APT1, Comment Crew / 17 - DragonOK 1314 Hangover Woolen-Goldfish, Newscaster, 147 | Panda / Group 18 - admin@338 29 - Scarlet Mimic 43 - Group5 Cobalt Gypsy 148 | 07 - APT28, Sednit, Sofacy, 19 - Naikon 30 - Lotus Blossom, Spring 44 - Winnti Group, Blackfly 60 - BRONZE BUTLER, REDBALDKNIGHT, 149 | Pawn Storm, Fancy Bear, 20 - Equation Dragon 45 - menuPass, Stone Panda, Tick 150 | STRONTIUM, Tsar Team, 21 - Molerats, Operation 31 - Dust Storm APT10, Red Apollo, CVNX 151 | Threat Group(TG)-4127 Molerats, Gaza 32 - Lazarus Group, HIDDEN 46 - FIN10 152 | 08 - Carbanak, Anunak, Cybergang COBRA, Guardians of 47 - Gamaredon Group 153 | Carbon Spider 22 - APT3, Gothic Panda, Peace, ZINC, NICKEL 48 - RTM 154 | 09 - Deep Panda, Shell Crew, Pirpi, UPS Team, ACADEMY 49 - Oilrig 155 | WebMasters, KungFu Buckeye, Threat 33 - Poseidon Group 50 - APT32, OceanLotus Group 156 | Kittens, PinkPanther, Group (TG)-0110 34 - Sandworm Team, Quedagh 51 - FIN10 157 | Black Vine 23 - APT16 35 - Dragonfly, Energetic 52 - CopyKittens 158 | 10 - Turla, Waterbug, 24 - MSUpdater Bear 53 - FIN5 159 | White Bear 25 - APT17, Deputy Dog 36 - GCMAN 54 - Sowbug 160 | ''' + "\n", 'yellow', attrs=['bold']) 161 | 162 | def user_input(self): 163 | selection = '' 164 | flg = 1 165 | 166 | while(flg == 1): 167 | # Verify user input is a digit 168 | while(selection.isdigit() == False): 169 | selection = input("Select a threat ID number (e.g. 01-60): ") 170 | 171 | # If digit, verify it is in acceptable range and build query portion of URL else try again 172 | if int(selection) in range(1,10): 173 | grp_id = 'G000' + str(int(selection)) 174 | grpQry = '[[Has ID::' + grp_id + ']]|?Has display name|?Has technique|?Has alias' 175 | flg = 0 176 | elif int(selection) in range(10,61): 177 | grp_id = 'G00' + selection 178 | grpQry = '[[Has ID::' + grp_id + ']]|?Has display name|?Has technique|?Has alias' 179 | flg = 0 180 | else: 181 | selection = '' 182 | return(grp_id,grpQry) 183 | 184 | def main(self): 185 | self.check_install() 186 | 187 | # Get ascii art & menu 188 | self.logo() 189 | self.menu() 190 | 191 | # Handle user input 192 | group_id,grp_query = self.user_input() 193 | 194 | # Get the Mitre intel 195 | grp_URL = self.get_URL(grp_query) 196 | grp_json = self.get_json(grp_URL) 197 | group_name,alias_arr,tech_name_array,tech_id_array = self.get_techniques(grp_json,group_id) 198 | tech_json = self.get_technique_info(tech_name_array) 199 | 200 | # Display Mitre intel to screen 201 | self.prt2screen(group_name,alias_arr,tech_json,tech_id_array,tech_name_array) 202 | 203 | attackintel().main() 204 | --------------------------------------------------------------------------------