diff --git a/.gitignore b/.gitignore index 7be5d50..8f7bc2b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ ansible/vars/vars.yml *.idea # conf -cloud_attack_range.conf +attack_range_cloud.conf *.key *.pem diff --git a/README.md b/README.md index 70389b7..f19d8a2 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ -# Splunk Cloud Attack Range ⚔️ +# Splunk Attack Range Cloud ⚔️ ## Purpose 🛡 -The Cloud Attack Range is a detection development platform, which solves three main challenges in detection engineering. First, the user is able to build quickly a small lab infrastructure as close as possible to a cloud environment. Second, the Attack Range performs attack simulation using different engines such as [Atomic Red Team](https://github.com/redcanaryco/atomic-red-team) in order to generate real attack data. +The Attack Range Cloud is a detection development platform, which solves three main challenges in detection engineering. First, the user is able to build quickly a small lab infrastructure as close as possible to a cloud environment. Second, the Attack Range performs attack simulation using different engines such as [Atomic Red Team](https://github.com/redcanaryco/atomic-red-team) in order to generate real attack data. ## Building 👷‍♂️ -Cloud Attack Range can be built is currently: +Attack Range Cloud can be built is currently: - **cloud-only**, Specifically simulate attacks against AWS ## Architecture 🏯 -The Cloud Attack Range consists of: +The Attack Range Cloud consists of: - pre-configured Splunk server with AWS Cloudtrail logs and Kubernetes logs - pre-configured Phantom server - AWS Elastic Kubernetes Service with a Wordpress app and [Splunk Connect for Kubernetes](https://github.com/splunk/splunk-connect-for-kubernetes) - integrated [Atomic Red Team](https://github.com/redcanaryco/atomic-red-team) cloud attacks -![Architecture](docs/cloud_attack_range_architecture.png) +![Architecture](docs/attack_range_cloud_architecture.png) ### Logging The following log sources are collected from the machines: @@ -27,50 +27,50 @@ The following log sources are collected from the machines: - AWS Elastic Kubernetes Service logs (```index=aws sourcetype=aws:cloudwatchlogs```) ## Running 🏃‍♀️ -Follow [Getting Started](https://github.com/splunk/attack_range_cloud/wiki/Configure-Cloud-Attack-Range) to configure Cloud Attack Range. -Cloud Attack Range supports different actions: -- Build Cloud Attack Range +Follow [Getting Started](https://github.com/splunk/attack_range_cloud/wiki/Configure-Cloud-Attack-Range) to configure Attack Range Cloud. +Attack Range Cloud supports different actions: +- Build Attack Range Cloud - Perform Cloud Attack Simulation -- Destroy Cloud Attack Range -- Stop Cloud Attack Range -- Resume Cloud Attack Range +- Destroy Attack Range Cloud +- Stop Attack Range Cloud +- Resume Attack Range Cloud -### Cloud Attack Range Commands -- Configure your Cloud Attack Range .conf +### Attack Range Cloud Commands +- Configure your Attack Range Cloud .conf ``` -- [x] python cloud_attack_range.py configure +- [x] python attack_range_cloud.py configure ``` -### Cloud Attack Range Commands -- Build Cloud Attack Range +### Attack Range Cloud Commands +- Build Attack Range Cloud ``` -- [x] python cloud_attack_range.py build +- [x] python attack_range_cloud.py build ``` ### Perform Cloud Attack Simulation -[Work in Progress] - Perform Cloud Attack Simulation by Mitre technique ``` -python cloud_attack_range.py simulate -st T1136.003 +python attack_range_cloud.py simulate -st T1098 --clean_up yes +python attack_range_cloud.py simulate -st T1098 --clean_up no ``` -### Destroy Cloud Attack Range -- Destroy Cloud Attack Range +### Destroy Attack Range Cloud +- Destroy Attack Range Cloud ``` -python cloud_attack_range.py destroy +python attack_range_cloud.py destroy ``` -### Stop Cloud Attack Range -- Stop Cloud Attack Range +### Stop Attack Range Cloud +- Stop Attack Range Cloud ``` -python cloud_attack_range.py stop +python attack_range_cloud.py stop ``` -### Resume Cloud Attack Range -- Resume Cloud Attack Range +### Resume Attack Range Cloud +- Resume Attack Range Cloud ``` -python cloud_attack_range.py resume +python attack_range_cloud.py resume ``` ## Features 💍 @@ -83,13 +83,13 @@ python cloud_attack_range.py resume - [Splunk Enterprise Security](https://splunkbase.splunk.com/app/263/) * [Splunk Enterprise Security](https://splunkbase.splunk.com/app/263/) is a premium security solution requiring a paid license. - * Enable or disable [Splunk Enterprise Security](https://splunkbase.splunk.com/app/263/) in [cloud_attack_range.conf](cloud_attack_range.conf) + * Enable or disable [Splunk Enterprise Security](https://splunkbase.splunk.com/app/263/) in [attack_range_cloud.conf](attack_range_cloud.conf) * Purchase a license, download it and store it in the apps folder to use it. - [Splunk Phantom](https://www.splunk.com/en_us/software/splunk-security-orchestration-and-automation.html) * [Splunk Phantom](https://www.splunk.com/en_us/software/splunk-security-orchestration-and-automation.html) is a Security Orchestration and Automation platform * For a free development license (100 actions per day) register [here](https://my.phantom.us/login/?next=/) - * Enable or disable [Splunk Phantom](https://www.splunk.com/en_us/software/splunk-security-orchestration-and-automation.html) in [cloud_attack_range.conf](cloud_attack_range.conf) + * Enable or disable [Splunk Phantom](https://www.splunk.com/en_us/software/splunk-security-orchestration-and-automation.html) in [attack_range_cloud.conf](attack_range_cloud.conf) - [Atomic Red Team](https://github.com/redcanaryco/atomic-red-team) * Attack Simulation with [Atomic Red Team](https://github.com/redcanaryco/atomic-red-team) @@ -103,7 +103,7 @@ If you have questions or need support, you can: * Post a question to [Splunk Answers](http://answers.splunk.com) * Join the [#security-research](https://splunk-usergroups.slack.com/archives/C1S5BEF38) room in the [Splunk Slack channel](http://splunk-usergroups.slack.com) -* If you are a Splunk Enterprise customer with a valid support entitlement contract and have a Splunk-related question, you can also open a support case on the https://www.splunk.com/ support portal +* If you are a Splunk Enterprise customer with a valid support entitlement contract and have a Splunk-related question, you can also open a support case on the https://www.splunk.com/ support portalxx` ## Author diff --git a/attack_chain/privilege_escalation/createpolicyversion.yaml b/attack_chain/privilege_escalation/createpolicyversion.yaml deleted file mode 100644 index fc23511..0000000 --- a/attack_chain/privilege_escalation/createpolicyversion.yaml +++ /dev/null @@ -1,26 +0,0 @@ -attack_tactic: TA0004 -display_name: AWS Privilege Escalation using CreatePolicyVersion -id: 8822c3b0-d9f9-4daf-a043-49f460a31111 -description: chain atomics together to do Privilege Escalation using CreatePolicyVersion -atomic_tests_chain: -- atomic_test_id: T1136.003 - name: create 2 users -- atomic_test_id: T1098 - name: add one to group - - - -cleanup_command: | - access_key=`cat $PathToAtomicsFolder/TA0004.001/bin/aws_secret.creds| jq -r '.AccessKey.AccessKeyId'` - aws iam delete-access-key --access-key-id $access_key --user-name #{username} - aws iam remove-user-from-group --user-name #{username} --group-name #{username} - aws iam remove-user-from-group --user-name #{test_username} --group-name #{username} - aws iam detach-group-policy --group-name #{username} --policy-arn arn:aws:iam::#{aws_account_id}:policy/#{username} - aws iam delete-user --user-name #{username} - aws iam delete-user --user-name #{test_username} - aws iam delete-group --group-name #{username} - aws iam delete-policy-version --policy-arn arn:aws:iam::#{aws_account_id}:policy/#{username} --version-id v1 - aws iam delete-policy --policy-arn arn:aws:iam::#{aws_account_id}:policy/#{username} - rm $PathToAtomicsFolder/TA0004.001/bin/aws_secret.creds - -elevation_required: false diff --git a/cloud_attack_range.conf.template b/attack_range_cloud.conf.template similarity index 96% rename from cloud_attack_range.conf.template rename to attack_range_cloud.conf.template index c5c053f..4c546e1 100644 --- a/cloud_attack_range.conf.template +++ b/attack_range_cloud.conf.template @@ -22,7 +22,7 @@ instance_type_ec2 = t2.2xlarge # instance type for the aws ec2 instances [range_settings] -key_name = cloud-attack-range +key_name = attack-range-cloud # Specify the name of the EC2 key pair name # This is only needed for modes: terraform and packer @@ -89,7 +89,7 @@ splunk_security_essentials_app = splunk-security-essentials_312.tgz splunk_aws_app = splunk-add-on-for-amazon-web-services_500.tgz # Specify the Splunk AWS App -# Will be only installed when cloud_attack_range=1 +# Will be only installed when attack_range_cloud=1 [phantom_settings] @@ -140,7 +140,6 @@ splunk_server_private_ip = 10.0.1.12 # for mode terraform should be in subnet: 10.0.1.0/24 - [phantom_server] # customize the phantom server @@ -150,7 +149,7 @@ phantom_server_private_ip = 10.0.1.13 [cloudtrail] -sqs_queue_url = https://sqs.us-west-2.amazonaws.com/591511147606/cloudtrail-cloud-attack-range +sqs_queue_url = # specify the sqs queue for the cloudtrail logs. Cloudtrail needs to be enabled and configured manually. # more information can be found here: https://docs.splunk.com/Documentation/AddOns/released/AWS/CloudTrail diff --git a/cloud_attack_range.py b/attack_range_cloud.py similarity index 73% rename from cloud_attack_range.py rename to attack_range_cloud.py index 4d1311a..a545996 100644 --- a/cloud_attack_range.py +++ b/attack_range_cloud.py @@ -22,14 +22,14 @@ def init(args): .-~~~-. .- ~ ~-( )_ _ / ~ -. -| Cloud Attack Range \ - \ .' - ~- . _____________ . -~ +| Attack Range Cloud | + ' , + ` ~- . _____________ . ` ||/__'`. |//()'-.: |-.|| |o(o) - |||\\\ .==._ + ||| .==._ |||(o)==::' `|T "" () @@ -60,9 +60,6 @@ def init(args): log = logger.setup_logging(config['log_path'], config['log_level']) log.info("INIT - attack_range v" + str(VERSION)) - # if ARG_VERSION: - # log.info("version: {0}".format(VERSION)) - # sys.exit(0) return TerraformController(config, log), config, log @@ -77,18 +74,16 @@ def show(args): def simulate(args): controller, config, _ = init(args) simulation_techniques = args.simulation_technique - attack_chain_file = args.attack_chain_file clean_up = args.clean_up # lets give CLI priority over config file for pre-configured techniques if not simulation_techniques: simulation_techniques = 'no' - if not attack_chain_file: - attack_chain_file = 'no' + if not clean_up: clean_up = 'no' - return controller.simulate(simulation_techniques,attack_chain_file,clean_up) + return controller.simulate(simulation_techniques,clean_up) def dump(args): @@ -128,8 +123,8 @@ def test(args): def main(args): # grab arguments parser = argparse.ArgumentParser( - description="Use `cloud_attack_range.py action -h` to get help with any Attack Range action") - parser.add_argument("-c", "--config", required=False, default="cloud_attack_range.conf", + description="Use `attack_range_cloud.py action -h` to get help with any Attack Range action") + parser.add_argument("-c", "--config", required=False, default="attack_range_cloud.conf", help="path to the configuration file of the attack range") parser.add_argument("-v", "--version", default=False, action="version", version="version: {0}".format(VERSION), help="shows current attack_range version") @@ -138,11 +133,13 @@ def main(args): actions_parser = parser.add_subparsers(title="Attack Range actions", dest="action") configure_parser = actions_parser.add_parser("configure", help="configure a new attack range") build_parser = actions_parser.add_parser("build", help="Builds attack range instances") - #simulate_parser = actions_parser.add_parser("simulate", help="Simulates attack techniques") + simulate_parser = actions_parser.add_parser("simulate", help="Simulates attack techniques") destroy_parser = actions_parser.add_parser("destroy", help="destroy attack range instances") stop_parser = actions_parser.add_parser("stop", help="stops attack range instances") resume_parser = actions_parser.add_parser("resume", help="resumes previously stopped attack range instances") show_parser = actions_parser.add_parser("show", help="list machines") + + # Use attack range to use these functions # test_parser = actions_parser.add_parser("test") # dump_parser = actions_parser.add_parser("dump", help="dump locally logs from attack range instances") # replay_parser = actions_parser.add_parser("replay", help="replay dumps into the Splunk Enterprise server") @@ -160,40 +157,27 @@ def main(args): resume_parser.set_defaults(func=resume) # Configure arguments - configure_parser.add_argument("-c", "--config", required=False, type=str, default='cloud_attack_range.conf', + configure_parser.add_argument("-c", "--config", required=False, type=str, default='attack_range_cloud.conf', help="provide path to write configuration to") configure_parser.set_defaults(func=configure) # Simulation arguments - # simulate_parser.add_argument("-st", "--simulation_technique", required=False, type=str, default="", - # help="Specify an single atomic for AWS " - # "attack_range, example: T1136.003, requires --simulation flag") - # simulate_parser.add_argument("-acf", "--attack_chain_file", required=False, - # help="attack chain file") + simulate_parser.add_argument("-st", "--simulation_technique", required=False, type=str, default="", + help="Specify an single atomic for AWS " + "attack_range, example: T1098, requires --simulation flag") - # simulate_parser.add_argument("-cu", "--clean_up", required=False, type=str, default="", - # help="cleanup simulations") - # simulate_parser.set_defaults(func=simulate) + simulate_parser.add_argument("-cu", "--clean_up", required=False, type=str, default="", + help="cleanup simulations") + + simulate_parser.set_defaults(func=simulate) - # # Dump Arguments + # Dump Arguments # dump_parser.add_argument("-dn", "--dump_name", required=True, # help="name for the dumped attack data") # dump_parser.add_argument("--last-sim", required=False, action='store_true', # help="overrides dumps.yml time and dumps from the start of previous simulation") # dump_parser.set_defaults(func=dump) - # # Replay Arguments - # replay_parser.add_argument("-dn", "--dump_name", required=True, - # help="name for the dumped attack data") - # replay_parser.add_argument("--dump", required=False, - # help="name of the dump as defined in attack_data/dumps.yml") - # replay_parser.set_defaults(func=replay) - - # # Test Arguments - # test_parser.add_argument("-tf", "--test_file", required=True, - # type=str, default="", help='test file for test command') - # test_parser.set_defaults(func=test) - # Show arguments show_parser.add_argument("-m", "--machines", required=False, default=False, action="store_true", help="prints out all available machines") diff --git a/docs/attack_range_cloud_architecture.png b/docs/attack_range_cloud_architecture.png new file mode 100644 index 0000000..8422126 Binary files /dev/null and b/docs/attack_range_cloud_architecture.png differ diff --git a/docs/cloud_attack_range_architecture.png b/docs/cloud_attack_range_architecture.png deleted file mode 100644 index 09c132a..0000000 Binary files a/docs/cloud_attack_range_architecture.png and /dev/null differ diff --git a/modules/IEnvironmentController.py b/modules/IEnvironmentController.py index f94493e..b50a601 100644 --- a/modules/IEnvironmentController.py +++ b/modules/IEnvironmentController.py @@ -26,7 +26,7 @@ def resume(self): pass @abstractmethod - def simulate(self, simulation_techniques,attack_chain_file,clean_up): + def simulate(self, simulation_techniques,clean_up): pass @abstractmethod diff --git a/modules/TerraformController.py b/modules/TerraformController.py index 9f4b3c2..7aa2b3a 100644 --- a/modules/TerraformController.py +++ b/modules/TerraformController.py @@ -190,7 +190,7 @@ def find_attack_yaml(self,path,simulation_techniques): file = file.replace('.yaml','') if simulation_techniques == file: - print("helllos") + filename = file + ".yaml" filepath = os.path.join(root,filename) object = self.load_file(filepath) @@ -224,33 +224,30 @@ def replace_simulation_vars(self,atomic_tests,clean_up): #This Function is to simulate specific techniques def simulate_techniques(self,simulation_techniques,clean_up, var_str='no'): - - + path = self.config['atomic_red_team_path'] new_commands=[] objects = self.find_attack_yaml(path,simulation_techniques) if simulation_techniques and clean_up == 'no': - for object in objects: data = dict() for atomic_tests in object['atomic_tests']: - if 'iaas:aws' in (atomic_tests['supported_platforms']): - + if 'iaas:aws' not in (atomic_tests['supported_platforms']): + print("WARNING - NOT an AWS Atomic test:",atomic_tests['name']) + + if 'iaas:aws' in (atomic_tests['supported_platforms']): new_command = self.replace_simulation_vars(atomic_tests,clean_up) - - print("Execute - AWS technique {0}:\n {1}".format(object['attack_technique'], new_command)) - + print("Simulating Atomic {0}:\n{1}".format(object['attack_technique'], atomic_tests['name'])) rtemplate = Environment(loader=BaseLoader()).from_string(new_command) - function_call = rtemplate.render(**data) stream = os.popen(function_call) output = stream.read() print(output) - - + print("Finished Simulating\n") + if simulation_techniques and clean_up == 'yes': @@ -259,64 +256,34 @@ def simulate_techniques(self,simulation_techniques,clean_up, var_str='no'): data = dict() for atomic_tests in object['atomic_tests']: + if 'iaas:aws' not in (atomic_tests['supported_platforms']): + print("WARNING - NOT an AWS Atomic test:",atomic_tests['name']) + if 'iaas:aws' in (atomic_tests['supported_platforms']): - new_command = self.replace_simulation_vars(atomic_tests,clean_up) - print("Clean up - AWS technique {0}:\n {1}".format(object['attack_technique'], new_command)) + new_command = self.replace_simulation_vars(atomic_tests,clean_up) + print("Clean up {0}:\n{1}".format(object['attack_technique'], atomic_tests['name'])) rtemplate = Environment(loader=BaseLoader()).from_string(new_command) - function_call = rtemplate.render(**data) stream = os.popen(function_call) output = stream.read() - + print(output) + print("Finished Clean up\n") + # Main function :To be tested and refactored - def simulate(self, simulation_techniques,attack_chain_file,clean_up, var_str='no'): - + def simulate(self, simulation_techniques,clean_up, var_str='no'): - attack_chain_techniques="" - clean_up_atomics=[] - - if self.config['atomic_red_team_path'] == '': - print(" ERROR: Atomic Red Team file path is not set") + if os.path.isdir(self.config['atomic_red_team_path']) == False: + print(" ERROR: Atomic Red Team file path is not set or the path is incorrect in the conf file: ", self.config['atomic_red_team_path']) sys.exit(1) - if attack_chain_file and simulation_techniques =='no': - attack_chain_path = "attack_chain" - - for root, dirs, files in os.walk(attack_chain_path): - for file in files: - if os.path.splitext(file)[1] == ".yaml": - - if attack_chain_file in file: - filepath = os.path.join(root,file) - object = self.load_file(filepath) - - if clean_up == 'no': - - for atomics in object['atomic_tests_chain']: - attack_chain_techniques+=((atomics['atomic_test_id'])+",") - attack_chain_techniques=(attack_chain_techniques[:-1]) - - self.simulate_techniques(attack_chain_techniques,clean_up) - - if clean_up == 'yes': - - for atomics in object['atomic_tests_chain']: - - clean_up_atomics.append(atomics['atomic_test_id']) - clean_up_atomics.reverse() - attack_chain_techniques = str(clean_up_atomics).replace('[\'', '').replace('\']', '').replace('\', \'', ',') - - self.simulate_techniques(attack_chain_techniques, clean_up) - - if simulation_techniques and attack_chain_file =='no' and clean_up == 'no': - print("Simuating- cloud atomic test",simulation_techniques) + if simulation_techniques and clean_up == 'no': self.simulate_techniques(simulation_techniques,clean_up ) - if simulation_techniques and attack_chain_file =='no' and clean_up == 'yes': + if simulation_techniques and clean_up == 'yes': self.simulate_techniques(simulation_techniques,clean_up ) diff --git a/modules/configuration.py b/modules/configuration.py index 73a9154..192d842 100644 --- a/modules/configuration.py +++ b/modules/configuration.py @@ -21,7 +21,7 @@ import os -CONFIG_TEMPLATE = 'cloud_attack_range.conf.template' +CONFIG_TEMPLATE = 'attack_range_cloud.conf.template' def load_config_template(CONFIG_TEMPLATE): settings = {} @@ -94,13 +94,13 @@ def check_reuse_keys(answers): return True def new(config): - cloud_attack_range_config = Path(config) + attack_range_cloud_config = Path(config) print(config) - if cloud_attack_range_config.is_file(): + if attack_range_cloud_config.is_file(): questions = [ { 'type': 'confirm', - 'message': 'File {0} already exist, are you sure you want to continue?\nTHIS WILL OVERWRITE YOUR CURRENT CONFIG!'.format(cloud_attack_range_config), + 'message': 'File {0} already exist, are you sure you want to continue?\nTHIS WILL OVERWRITE YOUR CURRENT CONFIG!'.format(attack_range_cloud_config), 'name': 'continue', 'default': True, }, @@ -113,7 +113,7 @@ def new(config): print("> exiting, to create a unique configuration file in another location use the --config flag") sys.exit(0) - configpath = str(cloud_attack_range_config) + configpath = str(attack_range_cloud_config) print(""" ________________ @@ -346,16 +346,16 @@ def new(config): # write config file - with open(cloud_attack_range_config, 'w') as configfile: + with open(attack_range_cloud_config, 'w') as configfile: configuration.write(configfile) print("\n--------------------------PLEASE NOTE-------------------------------\n") - print("In order to have a fully functional Cloud Attack Range, you will need to set additional parameters in the cloud_attack_range.conf file\n") - print("- Collect CloudTrail logs:\nsqs_queue_url = \n") - print("- Simulate cloud atomics from Atomic Red Team:\natomic_red_team_path = \n") + print("In order to have a fully functional Attack Range Cloud, you will need to set additional parameters in the attack_range_cloud.conf file\n") + print("- Collect CloudTrail logs:\nsqs_queue_url = \n") + print("- Simulate cloud atomics from Atomic Red Team:\natomic_red_team_path = \n") print("--------------------------------------------------------------------------------\n") - print("> configuration file was written to: {0}, run `python cloud_attack_range.py build` to create a new cloud_attack_range\nyou can also edit this file to configure advance parameters".format(Path(cloud_attack_range_config).resolve())) + print("> configuration file was written to: {0}, run `python attack_range_cloud.py build` to create a new attack_range_cloud\nyou can also edit this file to configure advance parameters".format(Path(attack_range_cloud_config).resolve())) print("> setup has finished successfully ... exiting\n\n") sys.exit(0)