Exemplo n.º 1
0
def run(arguments=''):
    args = parse_args(arguments)

    if args.html:
        HTMLReport(args.output).save()
        if args.verbose:
            print(f'HTML report saved to {args.output}/report.html')
        exit(0)

    rmtree(args.output, ignore_errors=True)
    Path(args.output).mkdir(parents=True, exist_ok=True)
    configure_log(args.output)
    identity = STS(f'arn:aws:sts:{args.region}', args.profile, args.keys[0],
                   args.keys[1]).identity
    if args.verbose:
        print(header, end='\n\n')
        for _ in ['UserId', 'Account', 'Arn']:
            align(_, identity[_], orange)
        print('')

    statistics = Statistics(args.output)
    visibility = VisibilityReport(args.output)

    for arn in get_functions(args):
        if arnparse(arn):
            arn = arnparse(arn)
            if args.verbose:
                count = '[' + f'{statistics.statistics["lambdas"]+1}'.rjust(
                    4, ' ') + '] '
                print(f'\r{green}{count}{arn.resource}{nocolor}'.ljust(
                    100, ' '),
                      end='')
            lmbd = Lambda(arn.full, args)
            statistics.parse(lmbd.report())
            visibility.save(lmbd.report())

    SecurityReport(args.output).save()
    HTMLReport(args.output).save()

    if args.verbose:
        print('\r' + ' ' * 100, end='\r')  # clear
        align('Lambdas', statistics.statistics["lambdas"])
        align('Security', statistics.statistics["security"]["count"])
        align('Triggers', statistics.statistics["triggers"]["count"])
        align('Resources', statistics.statistics["resources"]["count"])
        align('Layers', statistics.statistics["layers"])
        align('Runtimes', len(statistics.statistics["runtimes"]["items"]))
        align('Regions', len(statistics.statistics["regions"]["items"]))
        align('Report', f'{args.output}/report.html')
        print('\n')
Exemplo n.º 2
0
    def get_triggers(self):
        '''
        Tracks events that trigger the Lambda function
        '''
        # Collect triggers from Event Sources
        try:
            eventSource = self.client.list_event_source_mappings(
                FunctionName=self.arn.resource)['EventSourceMappings']

            for event in eventSource:
                if event['State'] != 'Enabled':
                    continue

                self.triggers['items'][event['EventSourceArn']] = [
                    'lambda:InvokeFunction'
                ]

                # Track services
                self.triggers['services'].append(
                    arnparse(event['EventSourceArn']).service)
        except Exception:
            debug(self.arn.full)

        # Collect triggers from Function policy
        try:
            if self.policy:
                for statement in self.policy['Statement']:
                    if 'Condition' in statement:
                        if 'ArnLike' in statement['Condition']:
                            if 'AWS:SourceArn' in statement['Condition'][
                                    'ArnLike']:
                                arn = statement['Condition']['ArnLike'][
                                    'AWS:SourceArn']

                                if type(statement['Action']) == str:
                                    self.triggers['items'][arn] = [
                                        statement['Action']
                                    ]
                                else:
                                    self.triggers['items'][arn] = statement[
                                        'Action']

                                # Track services
                                self.triggers['services'].append(
                                    arnparse(arn).service)
        except Exception:
            debug(self.arn.full)

        self.triggers['services'] = list(set(self.triggers['services']))
Exemplo n.º 3
0
    def get_triggers(self):
        """
        Tracks events that trigger the Lambda function
        """
        # Collect triggers from Event Sources
        try:
            eventSource = self.client.list_event_source_mappings(
                FunctionName=self.arn.resource)["EventSourceMappings"]

            for event in eventSource:
                if event["State"] != "Enabled":
                    continue

                self.triggers["items"][event["EventSourceArn"]] = [
                    "lambda:InvokeFunction"
                ]

                # Track services
                self.triggers["services"].append(
                    arnparse(event["EventSourceArn"]).service)
        except Exception:
            debug(self.arn.full)

        # Collect triggers from Function policy
        try:
            if self.policy:
                for statement in self.policy["Statement"]:
                    if "Condition" in statement:
                        if "ArnLike" in statement["Condition"]:
                            if "AWS:SourceArn" in statement["Condition"][
                                    "ArnLike"]:
                                arn = statement["Condition"]["ArnLike"][
                                    "AWS:SourceArn"]

                                if type(statement["Action"]) == str:
                                    self.triggers["items"][arn] = [
                                        statement["Action"]
                                    ]
                                else:
                                    self.triggers["items"][arn] = statement[
                                        "Action"]

                                # Track services
                                self.triggers["services"].append(
                                    arnparse(arn).service)
        except Exception:
            debug(self.arn.full)

        self.triggers["services"] = list(set(self.triggers["services"]))
Exemplo n.º 4
0
    def __init__(self,
                 arn,
                 profile=None,
                 access_key_id=None,
                 secret_access_key=None):
        # AWS ARN
        self.arn = arnparse(arn)

        # AWS Profile and Keys
        self.profile = profile
        self.access_key_id = access_key_id
        self.secret_access_key = secret_access_key

        # AWS Resource-based policy
        self.policy = {}

        # Additional service information
        self.info = ''

        # AWS connection
        session = boto3.Session(profile_name=self.profile)
        self.client = session.client(self.arn.service,
                                     region_name=self.arn.region,
                                     aws_access_key_id=access_key_id,
                                     aws_secret_access_key=secret_access_key)
Exemplo n.º 5
0
    def test_arnparse_apigateway(self):
        arn_str = 'arn:aws:execute-api:us-east-1:0:api-id/stage/verb/path'
        arn = arnparse(arn_str)
        self.assertEqual(arn.service, 'apigateway')
        self.assertEqual(arn.resource_type, 'api-id')
        self.assertEqual(arn.resource, 'stage/verb/path')

        arn_str = 'arn:aws:execute-api:*:*:*'
        arn = arnparse(arn_str)
        self.assertEqual(arn.resource_type, '*')
        self.assertEqual(arn.resource, '')

        arn_str = 'arn:aws:execute-api:us-east-1:*:api-id/stage/*'
        arn = arnparse(arn_str)
        self.assertEqual(arn.resource_type, 'api-id')
        self.assertEqual(arn.resource, 'stage/*')
Exemplo n.º 6
0
    def test_arnparse_apigateway(self):
        arn_str = "arn:aws:execute-api:us-east-1:0:api-id/stage/verb/path"
        arn = arnparse(arn_str)
        self.assertEqual(arn.service, "apigateway")
        self.assertEqual(arn.resource_type, "api-id")
        self.assertEqual(arn.resource, "stage/verb/path")

        arn_str = "arn:aws:execute-api:*:*:*"
        arn = arnparse(arn_str)
        self.assertEqual(arn.resource_type, "*")
        self.assertEqual(arn.resource, "")

        arn_str = "arn:aws:execute-api:us-east-1:*:api-id/stage/*"
        arn = arnparse(arn_str)
        self.assertEqual(arn.resource_type, "api-id")
        self.assertEqual(arn.resource, "stage/*")
Exemplo n.º 7
0
 def __init__(self,
              arn,
              profile=None,
              access_key_id=None,
              secret_access_key=None):
     super().__init__(arn, profile, access_key_id, secret_access_key)
     self.caller = self.get_caller_identity()
     self.arn = arnparse(self.caller['Arn'])
     self.acl = ACL(self.caller['Arn'])
Exemplo n.º 8
0
 def test_arnparse_lambda(self):
     arn_str = "arn:aws:lambda:eu-west-1:0:function:function-name"
     arn = arnparse(arn_str)
     self.assertEqual(arn.full, arn_str)
     self.assertEqual(arn.partition, "aws")
     self.assertEqual(arn.service, "lambda")
     self.assertEqual(arn.region, "eu-west-1")
     self.assertEqual(arn.account_id, "0")
     self.assertEqual(arn.resource_type, "function")
     self.assertEqual(arn.resource, "function-name")
Exemplo n.º 9
0
 def test_arnparse_lambda(self):
     arn_str = 'arn:aws:lambda:eu-west-1:0:function:function-name'
     arn = arnparse(arn_str)
     self.assertEqual(arn.full, arn_str)
     self.assertEqual(arn.partition, 'aws')
     self.assertEqual(arn.service, 'lambda')
     self.assertEqual(arn.region, 'eu-west-1')
     self.assertEqual(arn.account_id, '0')
     self.assertEqual(arn.resource_type, 'function')
     self.assertEqual(arn.resource, 'function-name')
Exemplo n.º 10
0
    def __init__(self, arn, profile=None, access_key_id=None, secret_access_key=None):
        # Make sure we use IAM client
        super().__init__("arn:aws:iam::", profile, access_key_id, secret_access_key)
        self.arn = arnparse(arn)
        self.policy_documents = []
        self.permissions = []

        if self.arn.resource_type == "user":
            self.get_user_permissions()
        elif self.arn.resource_type == "assumed-role":
            self.get_role_permissions()
Exemplo n.º 11
0
    def test_arnparse_iam(self):
        arn_str = "arn:aws:iam::0:role/service-role/role-name"
        arn = arnparse(arn_str)
        self.assertEqual(arn.full, arn_str)
        self.assertEqual(arn.partition, "aws")
        self.assertEqual(arn.service, "iam")
        self.assertEqual(arn.region, None)
        self.assertEqual(arn.account_id, "0")
        self.assertEqual(arn.resource_type, "role/service-role")
        self.assertEqual(arn.resource, "role-name")

        arn_str = "arn:aws:iam::0:policy/service-role/role-name"
        arn = arnparse(arn_str)
        self.assertEqual(arn.resource_type, "policy/service-role")
        self.assertEqual(arn.resource, "role-name")

        arn_str = "arn:aws:iam::0:user/user-name"
        arn = arnparse(arn_str)
        self.assertEqual(arn.resource_type, "user")
        self.assertEqual(arn.resource, "user-name")
Exemplo n.º 12
0
    def test_arnparse_iam(self):
        arn_str = 'arn:aws:iam::0:role/service-role/role-name'
        arn = arnparse(arn_str)
        self.assertEqual(arn.full, arn_str)
        self.assertEqual(arn.partition, 'aws')
        self.assertEqual(arn.service, 'iam')
        self.assertEqual(arn.region, None)
        self.assertEqual(arn.account_id, '0')
        self.assertEqual(arn.resource_type, 'role/service-role')
        self.assertEqual(arn.resource, 'role-name')

        arn_str = 'arn:aws:iam::0:policy/service-role/role-name'
        arn = arnparse(arn_str)
        self.assertEqual(arn.resource_type, 'policy/service-role')
        self.assertEqual(arn.resource, 'role-name')

        arn_str = 'arn:aws:iam::0:user/user-name'
        arn = arnparse(arn_str)
        self.assertEqual(arn.resource_type, 'user')
        self.assertEqual(arn.resource, 'user-name')
Exemplo n.º 13
0
 def test_arnparse_to_dict(self):
     arn_str = 'arn:aws:lambda:eu-west-1:0:function:function-name'
     arn = arnparse(arn_str)
     self.assertEqual(arn.to_dict(), {
         'full': arn_str,
         'partition': 'aws',
         'service': 'lambda',
         'region': 'eu-west-1',
         'account_id': '0',
         'resource_type': 'function',
         'resource': 'function-name'
     })
Exemplo n.º 14
0
 def test_arnparse_to_dict(self):
     arn_str = "arn:aws:lambda:eu-west-1:0:function:function-name"
     arn = arnparse(arn_str)
     self.assertEqual(
         arn.to_dict(),
         {
             "full": arn_str,
             "partition": "aws",
             "service": "lambda",
             "region": "eu-west-1",
             "account_id": "0",
             "resource_type": "function",
             "resource": "function-name",
         },
     )
Exemplo n.º 15
0
    def parse(self, report, verbose=False):
        '''
        Parses Lambda report and automatically extracts and
        tracks statistics.
        '''
        self.statistics['lambdas'] += 1
        self.track('regions', report['region'])
        self.track('runtimes', report['runtime'])
        for idx in ['triggers', 'resources']:
            for arn in report[idx]['items']:
                if arn == '*':
                    continue
                self.track(idx, arnparse(arn).service)
        if 'count' in report['security']:
            for level, level_count in report['security']['count'].items():
                self.track('security', level, level_count)

        self.save(verbose=verbose)
Exemplo n.º 16
0
    def parse(self, report, verbose=False):
        """
        Parses Lambda report and automatically extracts and
        tracks statistics.
        """
        self.statistics["lambdas"] += 1
        self.statistics["layers"] += len(report["layers"])
        self.track("regions", report["region"])
        self.track("runtimes", report["runtime"])
        for idx in ["triggers", "resources"]:
            for arn in report[idx]["items"]:
                if arn == "*":
                    continue
                self.track(idx, arnparse(arn).service)
        if "count" in report["security"]:
            for level, level_count in report["security"]["count"].items():
                self.track("security", level, level_count)

        self.save(verbose=verbose)
Exemplo n.º 17
0
def get_regions(args):
    """
    Valid region specification:
        Single:     eu-west-1
        Multiple:   eu-west-1,ap-south-1,us-east-2
        All:        all
    Returns regions as a Python list
    """
    if not isinstance(args.region, str):
        raise ValueError("No region specified")
    if args.function:
        return [arnparse(args.function).region]
    available = boto3.Session().get_available_regions("lambda")
    if args.region == "all":
        return available
    regions = args.region.split(",")
    if not regions:
        raise ValueError("No region specified")
    for region in regions:
        if region not in available:
            raise ValueError(f'Invalid region "{region}"')
    return regions
Exemplo n.º 18
0
 def test_arnparse_kms(self):
     arn_str = 'arn:aws:kms:eu-west-1:0:key/key-id'
     arn = arnparse(arn_str)
     self.assertEqual(arn.resource_type, 'key')
     self.assertEqual(arn.resource, 'key-id')
Exemplo n.º 19
0
 def test_arnparse_s3(self):
     arn_str = 'arn:aws:s3:::bucket-name'
     arn = arnparse(arn_str)
     self.assertEqual(arn.resource_type, None)
     self.assertEqual(arn.resource, 'bucket-name')
Exemplo n.º 20
0
 def test_arnparse_invalid(self):
     with self.assertRaises(ValueError):
         arnparse('')
     with self.assertRaises(ValueError):
         arnparse('arn:aws:service')
Exemplo n.º 21
0
 def test_arnparse_logs(self):
     arn_str = 'arn:aws:logs:eu-west-1:0:log-group:/aws/lambda/function:*'
     arn = arnparse(arn_str)
     self.assertEqual(arn.resource_type, 'log-group')
     self.assertEqual(arn.resource, '/aws/lambda/function:*')
Exemplo n.º 22
0
 def test_arnparse_dynamodb(self):
     arn_str = 'arn:aws:dynamodb:us-west-2:0:table/table-name'
     arn = arnparse(arn_str)
     self.assertEqual(arn.resource_type, 'table')
     self.assertEqual(arn.resource, 'table-name')
Exemplo n.º 23
0
    def audit(self):
        if type(self.statement) != dict:
            return None
        if self.statement["Effect"] != "Allow":
            return None

        # Where
        where = "in Policy Statement"
        if self.policy:
            where = f'in Role Policy {self.policy["name"]}'
        elif "Sid" in self.statement:
            where = f'{where} Sid {self.statement["Sid"]}'

        # Principal
        if self.is_unrestricted("Principal"):
            if self.is_undefined("Condition"):
                yield {
                    "level":
                    "high",
                    "text":
                    ("Service is publicly accessible due to unrestricted "
                     f"Principal and undefined Condition {where}\n"
                     "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html"
                     ),
                }

        # NotPrincipal
        if self.get("NotPrincipal"):
            yield {
                "level":
                "high",
                "text":
                ("Anonymous or unauthenticated users may have access to this "
                 f"service due to using NotPrincipal with Allow {where}\n"
                 "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notprincipal.html"
                 ),
            }
            yield {
                "level": "info",
                "text": f"Use NotPrincipal with Deny {where}"
            }

        # Resource
        if self.is_unrestricted("Resource"):
            if self.is_unrestricted("Action"):
                if self.is_undefined("Condition"):
                    yield {
                        "level":
                        "info",
                        "text":
                        ("Service can perform any operation on any service due "
                         f"to unrestricted Resource, unrestricted Action and undefined Condition {where}"
                         ),
                    }
            elif self.is_undefined("Condition"):
                operations = list(
                    set([x.split(":")[0] for x in self.get("Action")]))
                for _ in operations:
                    if _ not in [
                            "sqs",
                            "s3",
                            "iam",
                            "sns",
                            "vpc",
                            "ec2",
                            "ecs",
                            "es",
                            "logs",
                            "dynamodb",
                            "kms",
                            "cloudformation",
                    ]:
                        operations.pop(operations.index(_))
                if len(operations):
                    yield {
                        "level":
                        "info",
                        "text":
                        (f'Service can perform {", ".join(operations)} operations on '
                         f"any service due to unrestricted Resource and undefined Condition {where}"
                         ),
                    }
        else:
            for resource in self.get("Resource"):
                arn = arnparse(resource)
                if arn.account_id == "*":
                    yield {
                        "level":
                        "low",
                        "text":
                        f"Unrestricted AWS Account ID in {arn.service} {resource} {where}",
                    }

        # Action
        for action in self.get("Action"):
            if action == "*" or action.split(":")[1] == "*":
                yield {
                    "level":
                    "low",
                    "text":
                    (f"Unrestricted Action {action} {where}\n"
                     "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege"
                     ),
                }

        # NotAction
        if self.get("NotAction"):
            yield {
                "level":
                "medium",
                "text":
                (f"Users may have more permissions than intended due to using NotAction with Allow {where}\n"
                 "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notaction.html"
                 ),
            }
            yield {"level": "info", "text": f"Use NotAction with Deny {where}"}

        # Condition
        if self.is_undefined("Condition"):
            yield {
                "level": "info",
                "text": f"Use policy Conditions for extra security {where}",
            }

        # Managed vs Inline policies
        if self.policy and self.policy["type"] == "inline":
            yield {
                "level":
                "info",
                "text":
                f"Use Customer Managed policies instead of Inline policies {where}",
            }

        # Privilege Escalation via IAM permissions
        yield from PrivilegeEscalation(self.get("Action")).audit()
Exemplo n.º 24
0
def run(arguments=""):
    """
    Main routine
    """
    args = parse_args(arguments)

    verbose(args, header, end="\n\n")

    if args.html:
        HTMLReport(args.output).save()
        verbose(args, f"Generated {args.output}/report.html", end="\n\n")
        exit(0)

    rmtree(args.output, ignore_errors=True)
    Path(args.output).mkdir(parents=True, exist_ok=True)
    configure_log(args.output)
    usage = get_usage(args)
    verbose(args, "Loading identity")
    region = list(usage.keys())[0]
    sts_arn = f"arn:aws:sts:{region}"
    identity = STS(sts_arn, args.profile, args.keys[0], args.keys[1])
    if args.verbose:
        for _ in ["UserId", "Account", "Arn"]:
            align(_, identity.caller[_], orange)
        print("")

    statistics = Statistics(args.output)
    visibility = VisibilityReport(args.output)
    writes = LambdaWrite(args)
    total_count = 0
    for region_count in usage.values():
        total_count += region_count

    for region in usage.keys():
        args.region = region
        for arn_str in get_functions(args):
            try:
                arn = arnparse(arn_str)
                counter = f'[ {statistics.statistics["lambdas"]+1}/{total_count} ] '
                verbose(args, f"{counter}{arn.resource}")
                lmbd = Lambda(arn.full, args, identity)
                for w in writes.get_for_lambda(arn.full):
                    lmbd.set_writes(w)
                statistics.parse(lmbd.report())
                visibility.save(lmbd.report())
            except Exception:
                debug(arn_str)

    SecurityReport(args.output).save()
    HTMLReport(args.output).save()

    if args.verbose:
        print("\r" + " " * 100, end="\r")  # clear
        align("Lambdas", statistics.statistics["lambdas"])
        align("Security", statistics.statistics["security"]["count"])
        align("Triggers", statistics.statistics["triggers"]["count"])
        align("Resources", statistics.statistics["resources"]["count"])
        align("Layers", statistics.statistics["layers"])
        align("Runtimes", len(statistics.statistics["runtimes"]["items"]))
        align("Regions", len(statistics.statistics["regions"]["items"]))
        print("")
        align("Report", f"{args.output}/report.html")
        align("Log", f"{args.output}/lambdaguard.log")
        print("")
Exemplo n.º 25
0
    def scan(self):
        '''
        Scan Lambda report for vulnerabilities
        and provide recommendations.
        '''
        # Audit Function policy
        if not self.report['policy']['function']:
            self.track(self.report['arn'], {
                'level': 'info',
                'text': 'Function policy is not defined'
            })
        else:
            if 'Statement' in self.report['policy']['function']:
                for statement in self.report['policy']['function'][
                        'Statement']:
                    for _ in PolicyStatement(statement).audit():
                        self.track(self.report['arn'], _)

        # Audit Execution role policy
        if not len(self.report['policy']['role']['policies']):
            self.track(self.report['arn'], {
                'level': 'info',
                'text': 'Execution Role policy is not defined'
            })
        else:
            for policy in self.report['policy']['role']['policies']:
                if 'Statement' in policy['document']:
                    for statement in policy['document']['Statement']:
                        for _ in PolicyStatement(statement,
                                                 policy=policy).audit():
                            self.track(self.report['role'], _)

        # Audit Resources
        if 'logs' not in self.report['resources']['services']:
            self.track(
                self.report['arn'], {
                    'level':
                    'low',
                    'text':
                    'Function activity is not monitored by CloudWatch due to missing logs permissions'
                })

        # Audit Triggers and Resources
        items = list(
            set(
                list(self.report['triggers']['items'].keys()) +
                list(self.report['resources']['items'].keys())))
        for arn in items:
            if arn == '*':
                continue

            arn = arnparse(arn)
            if arn.resource in ['*', '']:
                continue

            self.item = None
            if arn.service == 's3':
                if arn.resource_type:  # S3 object path
                    continue
                self.item = S3(arn.full,
                               profile=self.profile,
                               access_key_id=self.access_key_id,
                               secret_access_key=self.secret_access_key)
                for _ in AccessControlList(self.item.acl).audit():
                    self.track(arn.full, _)
                for _ in Encryption(self.item).audit():
                    self.track(arn.full, _)
            elif arn.service == 'sqs':
                self.item = SQS(arn.full,
                                profile=self.profile,
                                access_key_id=self.access_key_id,
                                secret_access_key=self.secret_access_key)
            elif arn.service == 'sns':
                self.item = SNS(arn.full,
                                profile=self.profile,
                                access_key_id=self.access_key_id,
                                secret_access_key=self.secret_access_key)
            elif arn.service == 'apigateway':
                self.item = APIGateway(
                    arn.full,
                    profile=self.profile,
                    access_key_id=self.access_key_id,
                    secret_access_key=self.secret_access_key)
            elif arn.service == 'dynamodb':
                self.item = DynamoDB(arn.full,
                                     profile=self.profile,
                                     access_key_id=self.access_key_id,
                                     secret_access_key=self.secret_access_key)

            if self.item:
                # Audit item Resource-based Policy
                if 'Statement' in self.item.policy:
                    for statement in self.item.policy['Statement']:
                        for _ in PolicyStatement(statement).audit():
                            self.track(arn.full, _)
                # If policy is missing, the the service is public
                else:
                    for _ in Public(self.item).audit():
                        self.track(arn.full, _)

        # SonarQube
        if self.args.sonarqube:
            self.scan_sonarqube(arn, self.report['codeURL'],
                                self.report['runtime'])
            for layer in self.report['layers']:
                self.scan_sonarqube(arn, layer['codeURL'],
                                    self.report['runtime'])

        # Sort findings by level
        sorted_items = []
        for sort in ['high', 'medium', 'low', 'info']:
            for item in self.security['items']:
                if item['level'] == sort:
                    if item not in sorted_items:
                        sorted_items.append(item)
        self.security['items'] = sorted_items
Exemplo n.º 26
0
 def test_arnparse_sqs(self):
     arn_str = 'arn:aws:sqs:eu-west-1:0:queue-name'
     arn = arnparse(arn_str)
     self.assertEqual(arn.resource_type, None)
     self.assertEqual(arn.resource, 'queue-name')
Exemplo n.º 27
0
 def test_arnparse_sns(self):
     arn_str = 'arn:aws:sns:us-east-1:0:topic-name'
     arn = arnparse(arn_str)
     self.assertEqual(arn.resource_type, None)
     self.assertEqual(arn.resource, 'topic-name')
Exemplo n.º 28
0
    def scan(self):
        """
        Scan Lambda report for vulnerabilities
        and provide recommendations.
        """
        # Audit Function policy
        if not self.report["policy"]["function"]:
            self.track(
                self.report["arn"],
                {
                    "level": "info",
                    "text": "Function policy is not defined"
                },
            )
        else:
            if "Statement" in self.report["policy"]["function"]:
                for statement in self.report["policy"]["function"][
                        "Statement"]:
                    for _ in PolicyStatement(statement).audit():
                        self.track(self.report["arn"], _)

        # Audit Execution role policy
        if not len(self.report["policy"]["role"]["policies"]):
            self.track(
                self.report["arn"],
                {
                    "level": "info",
                    "text": "Execution Role policy is not defined"
                },
            )
        else:
            for policy in self.report["policy"]["role"]["policies"]:
                if "Statement" in policy["document"]:
                    for statement in policy["document"]["Statement"]:
                        for _ in PolicyStatement(statement,
                                                 policy=policy).audit():
                            self.track(self.report["role"], _)

        # Audit KMS
        if "kms" in self.report:
            self.item = KMS(
                self.report["kms"],
                profile=self.profile,
                access_key_id=self.access_key_id,
                secret_access_key=self.secret_access_key,
            )
            if not self.item.rotation:
                self.track(
                    self.report["kms"],
                    {
                        "level":
                        "medium",
                        "text":
                        ("Automatic rotation of key material is disabled\n"
                         "https://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html"
                         ),
                    },
                )
            for _, policy in self.item.policies.items():
                self.audit_policy_statements(self.item.arn, policy)

        # Audit Resources
        if "logs" not in self.report["resources"]["services"]:
            self.track(
                self.report["arn"],
                {
                    "level":
                    "low",
                    "text":
                    "Function activity is not monitored by CloudWatch due to missing logs permissions",
                },
            )

        # Audit Triggers and Resources
        items = list(
            set(
                list(self.report["triggers"]["items"].keys()) +
                list(self.report["resources"]["items"].keys())))
        for arn in items:
            if arn == "*":
                continue

            arn = arnparse(arn)
            if arn.resource in ["*", ""]:
                continue

            self.item = None
            if arn.service == "s3":
                if arn.resource_type:  # S3 object path
                    continue
                self.item = S3(
                    arn.full,
                    profile=self.profile,
                    access_key_id=self.access_key_id,
                    secret_access_key=self.secret_access_key,
                )
                for _ in AccessControlList(self.item.acl).audit():
                    self.track(arn.full, _)
                for _ in Encryption(self.item).audit():
                    self.track(arn.full, _)
            elif arn.service == "sqs":
                self.item = SQS(
                    arn.full,
                    profile=self.profile,
                    access_key_id=self.access_key_id,
                    secret_access_key=self.secret_access_key,
                )
            elif arn.service == "sns":
                self.item = SNS(
                    arn.full,
                    profile=self.profile,
                    access_key_id=self.access_key_id,
                    secret_access_key=self.secret_access_key,
                )
            elif arn.service == "apigateway":
                self.item = APIGateway(
                    arn.full,
                    profile=self.profile,
                    access_key_id=self.access_key_id,
                    secret_access_key=self.secret_access_key,
                )
            elif arn.service == "dynamodb":
                self.item = DynamoDB(
                    arn.full,
                    profile=self.profile,
                    access_key_id=self.access_key_id,
                    secret_access_key=self.secret_access_key,
                )
            elif arn.service == "kms":
                self.item = KMS(
                    arn.full,
                    profile=self.profile,
                    access_key_id=self.access_key_id,
                    secret_access_key=self.secret_access_key,
                )

            if self.item:
                if type(self.item) == KMS:
                    # Audit KMS Policies
                    for _, policy in self.item.policies.items():
                        self.audit_policy_statements(self.item.arn, policy)
                else:
                    # Audit item Resource-based Policy
                    self.audit_policy_statements(self.item.arn,
                                                 self.item.policy)
                    # If policy is missing, then the service is public
                    for _ in Public(self.item).audit():
                        self.track(arn.full, _)

        # SonarQube
        if self.args.sonarqube:
            self.scan_sonarqube(arn, self.report["codeURL"],
                                self.report["runtime"])
            for layer in self.report["layers"]:
                self.scan_sonarqube(arn, layer["codeURL"],
                                    self.report["runtime"])

        # Sort findings by level
        sorted_items = []
        for sort in ["high", "medium", "low", "info"]:
            for item in self.security["items"]:
                if item["level"] == sort:
                    if item not in sorted_items:
                        sorted_items.append(item)
        self.security["items"] = sorted_items
Exemplo n.º 29
0
    def audit(self):
        if self.statement['Effect'] != 'Allow':
            return

        # Where
        where = 'in Policy Statement'
        if self.policy:
            where = f'in Role Policy {self.policy["name"]}'
        elif 'Sid' in self.statement:
            where = f'{where} Sid {self.statement["Sid"]}'

        # Principal
        if self.is_unrestricted('Principal'):
            if self.is_undefined('Condition'):
                yield {
                    'level':
                    'high',
                    'text':
                    f'Service is publicly accessible due to unrestricted Principal and undefined Condition {where}\nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html'
                }

        # NotPrincipal
        if self.get('NotPrincipal'):
            yield {
                'level':
                'high',
                'text':
                f'Anonymous or unauthenticated users may have access to this service due to using NotPrincipal with Allow {where}\nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notprincipal.html'
            }
            yield {
                'level': 'info',
                'text': f'Use NotPrincipal with Deny {where}'
            }

        # Resource
        if self.is_unrestricted('Resource'):
            if self.is_unrestricted('Action'):
                if self.is_undefined('Condition'):
                    yield {
                        'level':
                        'info',
                        'text':
                        f'Service can perform any operation on any service due to unrestricted Resource, unrestricted Action and undefined Condition {where}'
                    }
            elif self.is_undefined('Condition'):
                operations = list(
                    set([x.split(':')[0] for x in self.get('Action')]))
                for _ in operations:
                    if _ not in [
                            'sqs', 's3', 'iam', 'sns', 'vpc', 'ec2', 'ecs',
                            'es', 'logs', 'dynamodb', 'kms', 'cloudformation'
                    ]:
                        operations.pop(operations.index(_))
                if len(operations):
                    yield {
                        'level':
                        'info',
                        'text':
                        f'Service can perform {", ".join(operations)} operations on any service due to unrestricted Resource and undefined Condition {where}'
                    }
        else:
            for resource in self.get('Resource'):
                arn = arnparse(resource)
                if arn.account_id == '*':
                    yield {
                        'level':
                        'low',
                        'text':
                        f'Unrestricted AWS Account ID in {arn.service} {resource} {where}'
                    }

        # Action
        for action in self.get('Action'):
            if '*' in action:
                yield {
                    'level':
                    'low',
                    'text':
                    f'Unrestricted Action {action} {where}\nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege'
                }

        # NotAction
        if self.get('NotAction'):
            yield {
                'level':
                'medium',
                'text':
                f'Users may have more permissions than intended due to using NotAction with Allow {where}\nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notaction.html'
            }
            yield {'level': 'info', 'text': f'Use NotAction with Deny {where}'}

        # Condition
        if self.is_undefined('Condition'):
            yield {
                'level': 'info',
                'text': f'Use policy Conditions for extra security {where}'
            }

        # Managed vs Inline policies
        if self.policy and self.policy['type'] == 'inline':
            yield {
                'level':
                'info',
                'text':
                f'Use Customer Managed policies instead of Inline policies {where}'
            }

        # Privilege Escalation via IAM permissions
        yield from PrivilegeEscalation(self.get('Action')).audit()
Exemplo n.º 30
0
def run(arguments=''):
    '''
    Main routine
    '''
    args = parse_args(arguments)

    verbose(args, header, end='\n\n')

    if args.html:
        HTMLReport(args.output).save()
        verbose(args, f'Generated {args.output}/report.html', end='\n\n')
        exit(0)

    rmtree(args.output, ignore_errors=True)
    Path(args.output).mkdir(parents=True, exist_ok=True)
    configure_log(args.output)
    usage = get_usage(args)
    verbose(args, f'Loading identity')
    region = list(usage.keys())[0]
    sts_arn = f'arn:aws:sts:{region}'
    identity = STS(sts_arn, args.profile, args.keys[0], args.keys[1])
    if args.verbose:
        for _ in ['UserId', 'Account', 'Arn']:
            align(_, identity.caller[_], orange)
        print('')

    statistics = Statistics(args.output)
    visibility = VisibilityReport(args.output)
    writes = LambdaWrite(args)
    total_count = 0
    for region_count in usage.values():
        total_count += region_count

    for region in usage.keys():
        args.region = region
        for arn_str in get_functions(args):
            try:
                arn = arnparse(arn_str)
                counter = f'[ {statistics.statistics["lambdas"]+1}/{total_count} ] '
                verbose(args, f'{counter}{arn.resource}')
                lmbd = Lambda(arn.full, args, identity)
                for w in writes.get_for_lambda(arn.full):
                    lmbd.set_writes(w)
                statistics.parse(lmbd.report())
                visibility.save(lmbd.report())
            except Exception:
                debug(arn_str)

    SecurityReport(args.output).save()
    HTMLReport(args.output).save()

    if args.verbose:
        print('\r' + ' ' * 100, end='\r')  # clear
        align('Lambdas', statistics.statistics["lambdas"])
        align('Security', statistics.statistics["security"]["count"])
        align('Triggers', statistics.statistics["triggers"]["count"])
        align('Resources', statistics.statistics["resources"]["count"])
        align('Layers', statistics.statistics["layers"])
        align('Runtimes', len(statistics.statistics["runtimes"]["items"]))
        align('Regions', len(statistics.statistics["regions"]["items"]))
        print('')
        align('Report', f'{args.output}/report.html')
        align('Log', f'{args.output}/lambdaguard.log')
        print('')