├── static └── .gitkeep ├── .gitignore ├── VERSION.txt ├── data ├── payloads │ ├── scanner.sh │ └── msf_extract.rb ├── payload │ └── 90ef8eaa-01b7-4e98-9070-105eca3bac39.yml └── abilities │ ├── reconnaissance │ └── 567eaaba-94cc-4a27-83f8-768e5638f4e1.yml │ └── resource-development │ └── bed8f28e-c0ed-463e-9e31-d5607e5473df.yml ├── README.md ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── question.md │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── hook.py ├── stores └── accessStore.js ├── app └── access_api.py ├── gui └── views │ └── access.vue └── templates └── access.html /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/* 3 | **/__pycache__ 4 | -------------------------------------------------------------------------------- /VERSION.txt: -------------------------------------------------------------------------------- 1 | 2.9.0-d1394c1508a1c89fc5b59651fce7abec 2 | -------------------------------------------------------------------------------- /data/payloads/scanner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo '[+] Starting basic NMAP scan' 4 | nmap -Pn $1 5 | echo '[+] Complete with module' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MITRE Caldera Plugin: Access 2 | 3 | Plugin supplying Caldera with red-team tools and functions, as applied to initial access. 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Documentation 3 | url: https://caldera.readthedocs.io/en/latest/ 4 | about: Your question may be answered in the documentation 5 | -------------------------------------------------------------------------------- /data/payload/90ef8eaa-01b7-4e98-9070-105eca3bac39.yml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | 4 | id: 90ef8eaa-01b7-4e98-9070-105eca3bac39 5 | name: Access Payloads 6 | standard_payloads: 7 | msf_extract.py: 8 | description: GET METASPLOIT EXPLOITS 9 | id: 169b74a7-e31b-4dd6-b6ff-721f3a615cc7 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U00002753 Question" 3 | about: Support questions 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /data/abilities/reconnaissance/567eaaba-94cc-4a27-83f8-768e5638f4e1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - id: 567eaaba-94cc-4a27-83f8-768e5638f4e1 3 | name: NMAP scan 4 | description: Scan an external host for open ports and services 5 | tactic: reconnaissance 6 | technique: 7 | name: Active Scanning 8 | attack_id: T1595 9 | platforms: 10 | darwin,linux: 11 | sh: 12 | command: | 13 | ./scanner.sh #{target.ip} 14 | timeout: 300 15 | payloads: 16 | - scanner.sh 17 | -------------------------------------------------------------------------------- /data/abilities/resource-development/bed8f28e-c0ed-463e-9e31-d5607e5473df.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - id: bed8f28e-c0ed-463e-9e31-d5607e5473df 3 | name: Load Metasploit Abilities 4 | description: Load Metasploit Abilities 5 | tactic: resource-development 6 | technique: 7 | name: "Obtain Capabilities: Exploits" 8 | attack_id: T1588.005 9 | platforms: 10 | darwin,linux: 11 | sh: 12 | command: | 13 | msfconsole -r msf_extract.rb #{app.contact.http} #{app.api_key.red} 14 | timeout: 1000 15 | payloads: 16 | - msf_extract.rb 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E Bug report" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: wbooth 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. Mac, Windows, Kali] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 2.8.0] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /hook.py: -------------------------------------------------------------------------------- 1 | from app.utility.base_world import BaseWorld 2 | from plugins.access.app.access_api import AccessApi 3 | 4 | name = 'Access' 5 | description = 'A toolkit containing initial access throwing modules' 6 | address = '/plugin/access/gui' 7 | access = BaseWorld.Access.RED 8 | 9 | 10 | async def enable(services): 11 | access_api = AccessApi(services=services) 12 | app = services.get('app_svc').application 13 | app.router.add_static('/access', 'plugins/access/static', append_version=True) 14 | app.router.add_route('GET', '/plugin/access/gui', access_api.landing) 15 | app.router.add_route('POST', '/plugin/access/exploit', access_api.exploit) 16 | app.router.add_route('POST', '/plugin/access/abilities', access_api.abilities) 17 | app.router.add_route('POST', '/plugin/access/executor', access_api.executor) 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | (insert summary) 4 | 5 | ## Type of change 6 | 7 | Please delete options that are not relevant. 8 | 9 | - [ ] Bug fix (non-breaking change which fixes an issue) 10 | - [ ] New feature (non-breaking change which adds functionality) 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] This change requires a documentation update 13 | 14 | ## How Has This Been Tested? 15 | 16 | Please describe the tests that you ran to verify your changes. 17 | 18 | 19 | ## Checklist: 20 | 21 | - [ ] My code follows the style guidelines of this project 22 | - [ ] I have performed a self-review of my own code 23 | - [ ] I have made corresponding changes to the documentation 24 | - [ ] I have added tests that prove my fix is effective or that my feature works 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 New Feature Request" 3 | about: Propose a new feature 4 | title: '' 5 | labels: feature 6 | assignees: 'wbooth' 7 | 8 | --- 9 | 10 | **What problem are you trying to solve? Please describe.** 11 | > Eg. I'm always frustrated when [...] 12 | 13 | 14 | **The ideal solution: What should the feature should do?** 15 | > a clear and concise description 16 | 17 | 18 | **What category of feature is this?** 19 | 20 | - [ ] UI/UX 21 | - [ ] API 22 | - [ ] Other 23 | 24 | **If you have code or pseudo-code please provide:** 25 | 26 | 27 | ```python 28 | 29 | ``` 30 | 31 | - [ ] Willing to submit a pull request to implement this feature? 32 | 33 | **Additional context** 34 | Add any other context or screenshots about the feature request here. 35 | 36 | Thank you for your contribution! 37 | -------------------------------------------------------------------------------- /stores/accessStore.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | 3 | export const useAccessStore = defineStore("accessStore", { 4 | state: () => { 5 | return {}; 6 | }, 7 | getters: { 8 | enabledPlugins: (state) => (state.mainConfig ? state.mainConfig.plugins : []) 9 | }, 10 | actions: { 11 | async restRequest(requestType, data, callback = (r) => { 12 | console.log('Fetch Success', r); 13 | }, endpoint = '/api/rest') { 14 | const requestData = requestType === 'GET' ? 15 | {method: requestType, headers: {'Content-Type': 'application/json'}} : 16 | {method: requestType, headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)} 17 | 18 | fetch(endpoint, requestData) 19 | .then((response) => { 20 | if (!response.ok) { 21 | throw (response.statusText); 22 | } 23 | return response.text(); 24 | }) 25 | .then((text) => { 26 | try { 27 | callback(JSON.parse(text)); 28 | } catch { 29 | callback(text); 30 | } 31 | }) 32 | .catch((error) => console.error(error)); 33 | }, 34 | 35 | async apiV2(requestType, endpoint, body = null, jsonRequest = true) { 36 | let requestBody = { method: requestType }; 37 | if (jsonRequest) { 38 | requestBody.headers = { 'Content-Type': 'application/json' }; 39 | if (body) { 40 | requestBody.body = JSON.stringify(body); 41 | } 42 | } else { 43 | if (body) { 44 | requestBody.body = body; 45 | } 46 | } 47 | 48 | return new Promise((resolve, reject) => { 49 | fetch(endpoint, requestBody) 50 | .then((response) => { 51 | if (!response.ok) { 52 | reject(response.statusText); 53 | } 54 | return response.text(); 55 | }) 56 | .then((text) => { 57 | try { 58 | resolve(JSON.parse(text)); 59 | } catch { 60 | resolve(text); 61 | } 62 | }); 63 | }); 64 | } 65 | }, 66 | }); 67 | -------------------------------------------------------------------------------- /app/access_api.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from aiohttp import web 4 | from aiohttp_jinja2 import template 5 | 6 | from app.objects.secondclass.c_fact import Fact 7 | from app.service.auth_svc import for_all_public_methods, check_authorization 8 | 9 | 10 | @for_all_public_methods(check_authorization) 11 | class AccessApi: 12 | 13 | def __init__(self, services): 14 | self.data_svc = services.get('data_svc') 15 | self.rest_svc = services.get('rest_svc') 16 | self.auth_svc = services.get('auth_svc') 17 | 18 | @template('access.html') 19 | async def landing(self, request): 20 | search = dict(access=tuple(await self.auth_svc.get_permissions(request))) 21 | abilities = await self.data_svc.locate('abilities', match=search) 22 | tactics = sorted(list(set(a.tactic.lower() for a in abilities))) 23 | obfuscators = [o.display for o in await self.data_svc.locate('obfuscators')] 24 | return dict(agents=[a.display for a in await self.data_svc.locate('agents', match=search)], 25 | abilities=[a.display for a in abilities], tactics=tactics, obfuscators=obfuscators) 26 | 27 | async def exploit(self, request): 28 | data = await request.json() 29 | converted_facts = [Fact(trait=f['trait'], value=f['value']) for f in data.get('facts', [])] 30 | await self.rest_svc.task_agent_with_ability(data['paw'], data['ability_id'], data['obfuscator'], converted_facts) 31 | return web.json_response('complete') 32 | 33 | async def abilities(self, request): 34 | data = await request.json() 35 | agent_search = dict(access=tuple(await self.auth_svc.get_permissions(request)), paw=data['paw']) 36 | agent = (await self.data_svc.locate('agents', match=agent_search))[0] 37 | ability_search = dict(access=tuple(await self.auth_svc.get_permissions(request))) 38 | abilities = await self.data_svc.locate('abilities', match=ability_search) 39 | capable_abilities = await agent.capabilities(list(abilities)) 40 | return web.json_response([a.display for a in capable_abilities]) 41 | 42 | async def executor(self, request): 43 | data = await request.json() 44 | agent_search = dict(access=tuple(await self.auth_svc.get_permissions(request)), paw=data['paw']) 45 | agent = (await self.data_svc.locate('agents', match=agent_search))[0] 46 | ability_search = dict(access=tuple(await self.auth_svc.get_permissions(request)), ability_id=data['ability_id']) 47 | ability = (await self.data_svc.locate('abilities', match=ability_search))[0] 48 | executor = await agent.get_preferred_executor(ability) 49 | if not executor: 50 | return web.json_response(dict(error='Executor not found for ability')) 51 | trimmed_ability = copy.deepcopy(ability) 52 | trimmed_ability.remove_all_executors() 53 | trimmed_ability.add_executor(executor) 54 | return web.json_response(trimmed_ability.display) 55 | -------------------------------------------------------------------------------- /data/payloads/msf_extract.rb: -------------------------------------------------------------------------------- 1 | 2 | 3 | require 'json' 4 | require 'net/http' 5 | require 'securerandom' 6 | require 'uri' 7 | 8 | def get_exploit_info(exploit_name) 9 | exploit = self.framework.modules.create(exploit_name) 10 | description = get_exploit_description(exploit) 11 | privileged = exploit.privileged ? "Elevated" : "" 12 | platforms = exploit.target_platform.platforms.map {|plat| plat.realname } 13 | 14 | params = [] 15 | for name, option in exploit.options 16 | if option.required && option.default.nil? 17 | params.push(name) 18 | end 19 | end 20 | 21 | data = { 22 | "name" => exploit.name, 23 | "description" => description, 24 | "platform" => platforms, 25 | "privilege" => exploit.privileged ? "Elevated" : "", 26 | "module" => exploit.realname, 27 | "params" => params 28 | } 29 | end 30 | 31 | def get_exploit_description(exploit) 32 | description = Rex::Text.compress(exploit.description) 33 | description << "\n\n" 34 | 35 | if (exploit.options.has_options?) 36 | description << "Basic options:\n\n" 37 | description << ::Msf::Serializer::ReadableText.dump_options(exploit, " ") 38 | end 39 | 40 | adv_opts = ::Msf::Serializer::ReadableText.dump_advanced_options(exploit, " ") 41 | description << "\nAdvanced options:\n\n" 42 | description << "#{adv_opts}\n" if (adv_opts and adv_opts.length > 0) 43 | 44 | description << ::Msf::Serializer::ReadableText.dump_references(exploit, " ") 45 | 46 | return description 47 | end 48 | 49 | def convert_to_ability(exploit) 50 | command = 'msfconsole -x "use ' + exploit['module'] + '; ' 51 | for param in exploit['params'] 52 | command += 'set ' + param + ' ' + '#{msf.' + param + '}; ' 53 | end 54 | command += 'run"' 55 | 56 | os = get_os 57 | executor = (os == "windows" ? "cmd" : "sh") 58 | 59 | ability = { 60 | "ability_id" => SecureRandom.uuid, 61 | "tactic" => "metasploit", 62 | "technique_name" => "metasploit", 63 | "technique_id" => "T1349", 64 | "name" => exploit['name'], 65 | "description" => exploit['description'], 66 | "privilege" => exploit['privilege'], 67 | "executors" => [{ 68 | "command" => command, 69 | "timeout" => 60, 70 | "platform" => os 71 | }] 72 | } 73 | return ability 74 | end 75 | 76 | def get_os 77 | if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil 78 | return "windows" 79 | elsif (/darwin/ =~ RUBY_PLATFORM) != nil 80 | return "darwin" 81 | else 82 | return "linux" 83 | end 84 | end 85 | 86 | def save_ability(ability, c2_uri, c2_key) 87 | uri = URI.parse(c2_uri + '/api/v2/abilities') 88 | header = { 89 | 'Content-Type' => 'text/json', 90 | 'KEY' => c2_key 91 | } 92 | http = Net::HTTP.new(uri.host, uri.port) 93 | request = Net::HTTP::Post.new(uri, header) 94 | request.body = ability.to_json 95 | response = http.request(request) 96 | end 97 | 98 | c2_uri = ARGV.shift || 'http://0.0.0.0:8888' 99 | c2_key = ARGV.shift || 'ADMIN123' 100 | 101 | for exploit_name in self.framework.exploits.keys 102 | exploit = get_exploit_info(exploit_name) 103 | ability = convert_to_ability(exploit) 104 | response = save_ability(ability, c2_uri, c2_key) 105 | end 106 | 107 | exit(true) 108 | 109 | 110 | 111 | quit 112 | -------------------------------------------------------------------------------- /gui/views/access.vue: -------------------------------------------------------------------------------- 1 | 202 | 203 | 367 | 368 | 389 | -------------------------------------------------------------------------------- /templates/access.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |

Access

7 |

8 | Here you can task any agent with any ability from the database - outside the scope of an operation. 9 | This is especially useful for conducting initial access attacks. To do this, deploy an agent locally 10 | and task it with either pre-ATT&CK or initial access tactics, pointed at any target. You can even deploy 11 | an agent remotely and use it as a proxy to conduct your initial access attacks. To the right, you'll 12 | see every ability directly tasked to an agent. 13 |

14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 |
22 | 23 |
24 |
25 | 31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 |

Select an agent to get started

39 |
40 | 41 |
42 |
43 | 47 | Agent Platform: 48 | Compatible Abilities: 49 |
50 |

No links to show

51 |
52 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 76 | 77 |
ordernametacticstatus
78 |
79 | 80 | 81 | 82 | 108 | 109 | 252 | 253 |
254 | 255 | 424 | 425 | 446 | --------------------------------------------------------------------------------