Esempio n. 1
0
def process(ctx, directory, arn):
    """Process cloudtrail files"""
    log.info('Processing CloudTrail...')

    if not os.path.exists(directory):
        log.fatal('Invalid Directory Path')

    files = []
    for cloudtrail_file in os.listdir(directory):
        files.append(os.path.join(directory, cloudtrail_file))

    api_calls_logged = process_cloudtrail(arn, files)
Esempio n. 2
0
def cloudtrail_calls(ctx, services):
    """Enumerate all calls for AWS Services to determine what shows up in CloudTrail"""
    log.info('Starting enumeration for CloudTrail...')

    session = boto3.Session()

    if not services:
        services = session.get_available_services()

    enumerate_services(ctx.config, services, dry_run=ctx.dry_run)

    log.info('Enumeration complete')
Esempio n. 3
0
def attack(ctx):
    """Simulate an attack by making the calls described in the config"""
    if not ctx.config.get('attack_chain', None):
        log.fatal('attack_chain not found in config file')

    log.info('Starting attack simulation...')

    attack_commands = ctx.config['attack_chain']

    simulate_attack(ctx.config, attack_commands, dry_run=ctx.dry_run)

    log.info('Attack simulation complete')
Esempio n. 4
0
def record_cloudtrail(arn, files):

    api_calls = []

    for file in files:
        f = None
        log.info('Processing file: {}'.format(file))

        if file.endswith('.gz'):
            f = gzip.open(file, 'r')
        else:
            f = open(file, 'r')

        try:
            cloudtrail = json.load(f)
        except Exception as e:
            log.error('Invalid JSON File: {} - {}'.format(file, e))
            continue

        records = sorted(cloudtrail['Records'],
                         key=lambda x: datetime.strptime(
                             x['eventTime'], '%Y-%m-%dT%H:%M:%SZ'),
                         reverse=False)

        for record, next_record in pairwise(records):
            if record.get('userIdentity', {}).get('arn', '').startswith(arn):
                event_source = record['eventSource'].split('.')[0]
                event_name = record['eventName']

                time_delay = 0
                if next_record:
                    time_delta = datetime.strptime(
                        next_record['eventTime'],
                        '%Y-%m-%dT%H:%M:%SZ') - datetime.strptime(
                            record['eventTime'], '%Y-%m-%dT%H:%M:%SZ')
                    time_delay = time_delta.seconds

                call = '{}.{}'.format(event_source, event_name)

                log.info('{}.{} - {}'.format(
                    record['eventSource'].split('.')[0], record['eventName'],
                    record['userIdentity']['arn']))

                api_calls.append({
                    'call':
                    '{}.{}'.format(event_source, event_name),
                    'time_delay':
                    time_delay
                })

        f.close()

    return api_calls
Esempio n. 5
0
def record(ctx, directory, arn, output):
    """Create attack simulation from CloudTrail"""
    log.info('Recording CloudTrail...')

    if not os.path.exists(directory):
        log.fatal('Invalid Directory Path')

    files = []
    for cloudtrail_file in os.listdir(directory):
        files.append(os.path.join(directory, cloudtrail_file))

    api_calls_recorded = record_cloudtrail(arn, files)

    if output:
        with open(output, 'w') as yaml_file:
            yaml.dump({'attack_chain': api_calls_recorded},
                      yaml_file,
                      default_flow_style=False)
Esempio n. 6
0
def simulate_attack(config, commands, dry_run=False):
    log.debug('Attack chain to be executed:')
    log.debug(json.dumps(commands, indent=4))

    session = boto3.Session()

    for command in commands:
        service_event = command['call'].split('.')

        service = service_event[0]
        api_call = service_event[1]

        delay = command.get('time_delay', 0)
        region = command.get('region', None)

        log.info('Making call - {}.{}'.format(service, api_call))
        if not dry_run:
            make_call(config, session, service, api_call, region)
        log.info('Sleeping {} until next call'.format(delay))
        if not dry_run:
            time.sleep(delay)
Esempio n. 7
0
def process_cloudtrail(arn, files):

    api_calls = []

    log.info('EventSource, EventName, Recorded Name, Match')

    for file in files:
        f = None
        log.debug('Processing file: {}'.format(file))

        if file.endswith('.gz'):
            f = gzip.open(file, 'r')
        else:
            f = open(file, 'r')

        try:
            cloudtrail = json.load(f)
        except Exception as e:
            log.error('Invalid JSON File: {} - {}'.format(file, e))
            continue

        for record in cloudtrail['Records']:
            if record.get('userIdentity', {}).get('arn', '').startswith(arn):
                event_source = record['eventSource'].split('.')[0]
                event_name = record['eventName']

                call = '{}.{}'.format(event_source, event_name)

                if call not in api_calls:
                    session = record['userIdentity']['arn'].split('/')[-1]
                    match = (record['eventName'].lower() == session)
                    log.info('{}, {}, {}, {}'.format(
                        record['eventSource'].split('.')[0],
                        record['eventName'], session, match))
                    api_calls.append(call)

        f.close()

    return api_calls
Esempio n. 8
0
def enumerate_services(config, services, dry_run=False):

    # Create a boto3 session to use for enumeration
    session = boto3.Session()

    authorized_calls = []

    for service in services:

        if service == 's3control':
            log.info(
                'Skipping {} - End-points do not seem to be working'.format(
                    service))
            continue

        if len(session.get_available_regions(service)) == 0:
            if service in [
                    'budgets', 'ce', 'chime', 'cloudfront', 'iam',
                    'importexport', 'organizations', 'route53', 'sts', 'waf'
            ]:
                region = 'us-east-1'
            else:
                log.info(
                    'Skipping {} - No regions exist for this service'.format(
                        service))
                continue
        else:
            if 'us-east-1' in session.get_available_regions(service):
                region = 'us-east-1'
            else:
                log.info('Skipping {} - Only available in {}'.format(
                    service, session.get_available_regions(service)))
                continue

        # Create a service client
        log.info('Creating {} client...'.format(service))

        # Set the user-agent if specified in the config
        if config.get('user_agent', None):
            botocore_config.user_agent = config['user_agent']

        # Create a client with parameter validation off
        client = session.client(service,
                                region_name=region,
                                config=botocore_config)

        # Get the functions that you can call
        functions_list = get_boto_functions(client)

        # Get the service file
        service_file_json = get_service_json_files(config)

        # Get a list of params needed to make the serialization pass in botocore
        service_call_params = get_service_call_params(
            service_file_json[service])

        # Loop through all the functions and call them
        for function in functions_list:

            # The service_file_json doesn't have underscores in names so let's remove them
            function_key = function[0].replace('_', '')

            # Session Name Can only be 64 characters long
            if len(function_key) > 64:
                session_name = function_key[:63]
                log.info('Session Name {} is for {}'.format(
                    session_name, function_key))
            else:
                session_name = function_key

            # Set the session to the name of the API call we are making
            session = get_assume_role_session(
                account_number=config['account_number'],
                role=config['account_role'],
                session_id=session_name)

            new_client = session.client(service,
                                        region_name=region,
                                        config=botocore_config)
            new_functions_list = get_boto_functions(new_client)

            for new_func in new_functions_list:
                if new_func[0] == function[0]:

                    # We need to pull out the parameters needed in the requestUri, ex. /{Bucket}/{Key+} -> ['Bucket', 'Key']
                    params = re.findall(
                        '\{(.*?)\}',
                        service_call_params.get(function_key, '/'))
                    params = [p.strip('+') for p in params]

                    try:
                        func_params = {}

                        for param in params:
                            # Set something because we have to
                            func_params[param] = 'testparameter'

                        log.info('Calling {}.{} with params {} in {}'.format(
                            service, new_func[0], func_params, region))

                        if not dry_run:
                            make_api_call(service, new_func, region,
                                          func_params)

                    except ClientError as e:
                        if "ValidationError" in str(e):
                            log.error(e)
                        else:
                            log.debug(e)
                    except boto3.exceptions.S3UploadFailedError as e:
                        log.debug(e)
                    except TypeError as e:
                        log.debug(e)
                    except KeyError as e:
                        log.debug('Unknown Exception: {}.{} - {}'.format(
                            service, new_func[0], e))
Esempio n. 9
0
def enumerate_services(config, services, dry_run=False):
    # read a CSV file for seen functions
    apis = defaultdict(list)
    with open('botocore_api_2_event_names.csv', 'rb') as csvfile:
        reader = csv.reader(csvfile)
        for row in reader:
            x = row[1].split('_')
            if len(x) > 1:
                # service_name without '-'
                apis[x[0]].append(row[1])
            else:
                # event_source as a service_name without '-'
                apis[row[0].replace('-', '')].append(row[1])

    # Create a boto3 session to use for enumeration
    session = boto3.Session()

    authorized_calls = []

    for service in services:

        if len(session.get_available_regions(service)) == 0:
            log.debug('Skipping {} - No regions exist for this service'.format(
                service))
            continue

        # Create a service client
        log.info('Creating {} client...'.format(service))

        # Grab a region to use for the calls.  This should be us-west-2
        region = session.get_available_regions(service)[-1]

        # Set the user-agent if specified in the config
        if config.get('user_agent', None):
            botocore_config.user_agent = config['user_agent']

        # Create a client with parameter validation off
        client = session.client(service,
                                region_name=region,
                                config=botocore_config)

        # Get the functions that you can call
        functions_list = get_boto_functions(client)

        # Get the service file
        service_file_json = get_service_json_files(config)

        # Get a list of params needed to make the serialization pass in botocore
        service_call_params = get_service_call_params(
            service_file_json[service])

        # Loop through all the functions and call them
        for function in functions_list:

            # The service_file_json doesn't have underscores in names so let's remove them
            function_key = function[0].replace('_', '')

            ## Session Name Can only be 64 characters long
            srv_len = len(service)
            session_name = service.replace('-', '')
            if srv_len > 20:
                session_name = service[:19]
                srv_len = 20
            func_key_limit = 64 - srv_len - 1
            if len(function_key) > func_key_limit:
                session_name += '_' + function_key[:func_key_limit - 1]
                log.info('Session Name {} is for {}:{}'.format(
                    session_name, service, function_key))
            else:
                session_name += "_" + function_key

            # check session_name in the seen functions
            if session_name in apis[service.replace('-', '')]:
                log.info('found {}:{}, skipping'.format(service, session_name))
                continue

            # Set the session to the name of the API call we are making
            session = get_assume_role_session(
                account_number=config['account_number'],
                role=config['account_role'],
                session_id=session_name)

            new_client = session.client(service,
                                        region_name=region,
                                        config=botocore_config)
            new_functions_list = get_boto_functions(new_client)

            for new_func in new_functions_list:
                if new_func[0] == function[0]:

                    # We need to pull out the parameters needed in the requestUri, ex. /{Bucket}/{Key+} -> ['Bucket', 'Key']
                    params = re.findall(
                        '\{(.*?)\}',
                        service_call_params.get(function_key, '/'))
                    params = [p.strip('+') for p in params]

                    try:
                        func_params = {}

                        for param in params:
                            # Set something because we have to
                            func_params[param] = 'testparameter'

                        log.info('Calling {}:{} with params {} in {}'.format(
                            service, new_func[0], func_params, region))

                        # fill values for required members
                        operation_name = new_client._PY_TO_OP_NAME[new_func[0]]
                        operation_model = new_client._service_model.operation_model(
                            operation_name)
                        input_shape = operation_model.input_shape
                        if service == 'sts' and operation_name == 'AssumeRole':
                            log.info(
                                'skipped sts:AssumeRole for required member')
                        elif input_shape.type_name == 'structure':
                            # find the required members
                            extra_params = _fillvalue(input_shape)
                            if extra_params:
                                func_params.update(extra_params)
                            else:
                                log.info('skipped this input_shape: {}'.format(
                                    input_shape))

                        if not dry_run:
                            make_api_call(service, new_func, region,
                                          func_params)

                    except ClientError as e:
                        log.error('ClientError: {}:{} - {}'.format(
                            service, new_func[0], e))
                    except EndpointConnectionError as e:
                        log.error('EndpointConnectionError: {}:{} - {}'.format(
                            service, new_func[0], e))
                    except boto3.exceptions.S3UploadFailedError as e:
                        log.error('S3UploadFailedError: {}:{} - {}'.format(
                            service, new_func[0], e))
                    except TypeError as e:
                        log.error('TypeError: {}:{} - {}'.format(
                            service, new_func[0], e))
                    except KeyError as e:
                        log.error('Unknown Exception: {}:{} - {}'.format(
                            service, new_func[0], e))