Skip to content

Commit bc60490

Browse files
authored
Merge pull request #7 from brightcove/Update
Update 20231211
2 parents e35fccd + a16d380 commit bc60490

33 files changed

+156
-120
lines changed

.github/workflows/codeql.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818

1919
steps:
2020
- name: Checkout repository
21-
uses: actions/checkout@v3
21+
uses: actions/checkout@v4
2222

2323
- name: Initialize CodeQL
2424
uses: github/codeql-action/init@v2

.github/workflows/integration_tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
pull-requests: write # Required for Publish Test Results
1212
steps:
1313
- name: Checkout
14-
uses: actions/checkout@v3
14+
uses: actions/checkout@v4
1515

1616
- name: Set up Python 3.11
1717
uses: actions/setup-python@v4

.github/workflows/unit_tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
pull-requests: write # Required for Publish Test Results
1212
steps:
1313
- name: Checkout
14-
uses: actions/checkout@v3
14+
uses: actions/checkout@v4
1515

1616
- name: Set up Python 3.11
1717
uses: actions/setup-python@v4

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# OWASP Domain Protect
2-
![Release version](https://img.shields.io/badge/release-v0.4.2-blue.svg)
2+
![Release version](https://img.shields.io/badge/release-v0.4.4-blue.svg)
33
[![Python 3.x](https://img.shields.io/badge/Python-3.x-blue.svg)](https://www.python.org/)
44
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
55
![OWASP Maturity](https://img.shields.io/badge/owasp-incubator%20project-53AAE5.svg)

aws-iam-policies/domain-protect-deploy.json

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"iam:CreateRole",
4646
"iam:CreateServiceLinkedRole",
4747
"iam:DeleteRole",
48+
"iam:DeleteRolePermissionsBoundary",
4849
"iam:DeleteServiceLinkedRole",
4950
"iam:DetachRolePolicy",
5051
"iam:DeleteRolePolicy",
@@ -53,6 +54,7 @@
5354
"iam:ListAttachedRolePolicies",
5455
"iam:ListInstanceProfilesForRole",
5556
"iam:ListRolePolicies",
57+
"iam:PutRolePermissionsBoundary",
5658
"iam:PutRolePolicy",
5759
"iam:PassRole"
5860
],

docs/integration-tests.md

-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ We are using the moto python module for mocking out AWS, and setting these up us
5252

5353
Then in the test you require the mock you can use the function name (e.g. `moto_route53`) as the parameter. You can then use the mock as if it was the boto3 library to create the resources you need for testing, which will be created in a mocked out aws account.
5454

55-
Because we use aws profiles we require the profile name to be set up in boto3 for testing. A "mocked" aws profile is set up in `integration_tests/conftest.py` in the `aws_credentials` fixture (this is why the other moto fixtures have the `aws_credentials` parameter). If the code under test needs an aws profile name, please use "mocked".
5655

5756
[back to Automated Tests](automated-tests.md)<br>
5857
[back to README](../README.md)

integration_tests/manual_scans/aws/test_aws_alias_s3.py

-8
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ def test_main_detects_vulnerable_domains(arg_parse_mock, print_list_mock, moto_r
4141

4242
requests_mock.get("http://vulnerable.domain-protect.com.", status_code=404, text="Code: NoSuchBucket")
4343

44-
arg_parse_mock.return_value.parse_args.return_value.profile = "mocked"
45-
4644
main()
4745

4846
expected_vulnerable_call = call(["vulnerable.domain-protect.com."], "INSECURE_WS")
@@ -57,8 +55,6 @@ def test_main_ignores_non_vulnerable_domains(arg_parse_mock, print_list_mock, mo
5755

5856
requests_mock.get("http://vulnerable.domain-protect.com.", status_code=200, text="All good here")
5957

60-
arg_parse_mock.return_value.parse_args.return_value.profile = "mocked"
61-
6258
main()
6359

6460
print_list_mock.assert_not_called()
@@ -71,8 +67,6 @@ def test_main_ignores_non_s3_domains(arg_parse_mock, print_list_mock, moto_route
7167

7268
requests_mock.get("http://vulnerable.domain-protect.com.", status_code=404, text="Code: NoSuchBucket")
7369

74-
arg_parse_mock.return_value.parse_args.return_value.profile = "mocked"
75-
7670
main()
7771

7872
print_list_mock.assert_not_called()
@@ -85,8 +79,6 @@ def test_main_ignores_domains_with_connection_error(arg_parse_mock, print_list_m
8579

8680
requests_mock.get("http://vulnerable.domain-protect.com.", exc=requests.exceptions.ConnectionError)
8781

88-
arg_parse_mock.return_value.parse_args.return_value.profile = "mocked"
89-
9082
main()
9183

9284
print_list_mock.assert_not_called()

lambda_code/cloudflare_scan/cloudflare_scan.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
import os
44

5+
from utils.utils_aws import eb_susceptible
56
from utils.utils_aws import publish_to_sns
67
from utils.utils_bugcrowd import bugcrowd_create_issue
78
from utils.utils_cloudflare import list_cloudflare_records
@@ -182,14 +183,7 @@ def cf_s3(account_name, zone_name, records):
182183

183184

184185
def cf_eb(account_name, zone_name, records):
185-
186-
vulnerability_list = [".elasticbeanstalk.com"]
187-
188-
records_filtered = [
189-
r
190-
for r in records
191-
if r["Type"] in ["CNAME"] and any(vulnerability in r["Value"] for vulnerability in vulnerability_list)
192-
]
186+
records_filtered = [r for r in records if r["Type"] in ["CNAME"] and eb_susceptible(r["Value"])]
193187

194188
for record in records_filtered:
195189

lambda_code/scan/scan.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
import os
44

5+
from utils.utils_aws import eb_susceptible
56
from utils.utils_aws import get_cloudfront_origin
67
from utils.utils_aws import list_domains
78
from utils.utils_aws import list_hosted_zones
@@ -120,7 +121,7 @@ def alias_cloudfront_s3(account_name, record_sets, account_id):
120121
def alias_eb(account_name, record_sets):
121122

122123
record_sets_filtered = [
123-
r for r in record_sets if "AliasTarget" in r and "elasticbeanstalk.com" in r["AliasTarget"]["DNSName"]
124+
r for r in record_sets if "AliasTarget" in r and eb_susceptible(r["AliasTarget"]["DNSName"])
124125
]
125126

126127
for record in record_sets_filtered:
@@ -192,9 +193,7 @@ def cname_eb(account_name, record_sets):
192193
record_sets_filtered = [
193194
r
194195
for r in record_sets
195-
if r["Type"] in ["CNAME"]
196-
and "ResourceRecords" in r
197-
and "elasticbeanstalk.com" in r["ResourceRecords"][0]["Value"]
196+
if r["Type"] in ["CNAME"] and "ResourceRecords" in r and eb_susceptible(r["ResourceRecords"][0]["Value"])
198197
]
199198

200199
for record in record_sets_filtered:

main.tf

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module "lambda-role" {
1010
region = var.region
1111
security_audit_role_name = var.security_audit_role_name
1212
kms_arn = module.kms.kms_arn
13+
permissions_boundary_arn = var.permissions_boundary_arn
1314
}
1415

1516
module "lambda-slack" {
@@ -72,6 +73,7 @@ module "accounts-role" {
7273
kms_arn = module.kms.kms_arn
7374
state_machine_arn = module.step-function.state_machine_arn
7475
policy = "accounts"
76+
permissions_boundary_arn = var.permissions_boundary_arn
7577
}
7678

7779
module "lambda-scan" {
@@ -118,6 +120,7 @@ module "takeover-role" {
118120
kms_arn = module.kms.kms_arn
119121
takeover = local.takeover
120122
policy = "takeover"
123+
permissions_boundary_arn = var.permissions_boundary_arn
121124
}
122125

123126
module "lambda-resources" {
@@ -141,6 +144,7 @@ module "resources-role" {
141144
security_audit_role_name = var.security_audit_role_name
142145
kms_arn = module.kms.kms_arn
143146
policy = "resources"
147+
permissions_boundary_arn = var.permissions_boundary_arn
144148
}
145149

146150
module "cloudwatch-event" {
@@ -248,6 +252,7 @@ module "step-function-role" {
248252
kms_arn = module.kms.kms_arn
249253
policy = "state"
250254
assume_role_policy = "state"
255+
permissions_boundary_arn = var.permissions_boundary_arn
251256
}
252257

253258
module "step-function" {
@@ -284,6 +289,7 @@ module "lambda-role-ips" {
284289
kms_arn = module.kms.kms_arn
285290
policy = "lambda"
286291
role_name = "lambda-ips"
292+
permissions_boundary_arn = var.permissions_boundary_arn
287293
}
288294

289295
module "lambda-scan-ips" {
@@ -321,6 +327,7 @@ module "accounts-role-ips" {
321327
state_machine_arn = module.step-function-ips[0].state_machine_arn
322328
policy = "accounts"
323329
role_name = "accounts-ips"
330+
permissions_boundary_arn = var.permissions_boundary_arn
324331
}
325332

326333
module "lambda-accounts-ips" {

manual_scans/aws/README.md

+25-32
Original file line numberDiff line numberDiff line change
@@ -32,65 +32,66 @@ $ export PYTHONPATH="${PYTHONPATH}:/Users/paul/src/github.com/domain-protect/dom
3232
* run manual scans from root of domain-protect folder
3333

3434
## CloudFront Alias with missing S3 origin
35-
* replace PROFILE_NAME by your AWS CLI profile name
35+
36+
3637
```
37-
python manual_scans/aws/aws-alias-cloudfront-s3.py --profile PROFILE_NAME
38+
python manual_scans/aws/aws-alias-cloudfront-s3.py
3839
```
3940

4041
![Alt text](images/aws-cloudfront-s3-alias.png?raw=true "CloudFront Alias with missing S3 origin")
4142

4243
## CloudFront CNAME with missing S3 origin
43-
* replace PROFILE_NAME by your AWS CLI profile name
44+
4445
```
45-
python manual_scans/aws/aws-cname-cloudfront-s3.py --profile PROFILE_NAME
46+
python manual_scans/aws/aws-cname-cloudfront-s3.py
4647
```
4748

4849
![Alt text](images/aws-cloudfront-s3-cname.png?raw=true "CloudFront CNAME with missing S3 origin")
4950

5051
## ElasticBeanstalk Alias
51-
* replace PROFILE_NAME by your AWS CLI profile name
52+
5253
```
53-
python manual_scans/aws/aws-alias-eb.py --profile PROFILE_NAME
54+
python manual_scans/aws/aws-alias-eb.py
5455
```
5556

5657
![Alt text](images/aws-eb-alias.png?raw=true "Detect vulnerable S3 Aliases")
5758

5859
## ElasticBeanstalk CNAMES
59-
* replace PROFILE_NAME by your AWS CLI profile name
60+
6061
```
61-
python manual_scans/aws/aws-cname-eb.py --profile PROFILE_NAME
62+
python manual_scans/aws/aws-cname-eb.py
6263
```
6364

6465
![Alt text](images/aws-eb-cnames.png?raw=true "Detect vulnerable ElasticBeanstalk CNAMEs")
6566

6667
## S3 Alias
67-
* replace PROFILE_NAME by your AWS CLI profile name
68+
6869
```
69-
python manual_scans/aws/aws_alias_s3.py --profile PROFILE_NAME
70+
python manual_scans/aws/aws_alias_s3.py
7071
```
7172

7273
![Alt text](images/aws-s3-alias.png?raw=true "Detect vulnerable S3 Aliases")
7374

7475
## S3 CNAMES
75-
* replace PROFILE_NAME by your AWS CLI profile name
76+
7677
```
77-
python manual_scans/aws/aws-cname-s3.py --profile PROFILE_NAME
78+
python manual_scans/aws/aws-cname-s3.py
7879
```
7980

8081
![Alt text](images/aws-s3-cnames.png?raw=true "Detect vulnerable S3 CNAMEs")
8182

8283
## registered domains with missing hosted zone
83-
* replace PROFILE_NAME by your AWS CLI profile name
84+
8485
```
85-
python manual_scans/aws/aws-ns-domain.py --profile PROFILE_NAME
86+
python manual_scans/aws/aws-ns-domain.py
8687
```
8788

8889
![Alt text](images/aws-ns-domain.png?raw=true "Detect vulnerable subdomains")
8990

9091
## subdomain NS delegations
91-
* replace PROFILE_NAME by your AWS CLI profile name
92+
9293
```
93-
python manual_scans/aws/aws-ns-subdomain.py --profile PROFILE_NAME
94+
python manual_scans/aws/aws-ns-subdomain.py
9495
```
9596

9697
![Alt text](images/aws-ns-subdomain.png?raw=true "Detect vulnerable subdomains")
@@ -103,26 +104,18 @@ python manual_scans/aws/aws-ns-subdomain.py --profile PROFILE_NAME
103104
```
104105
aws sts assume-role --role-arn arn:aws:iam::012345678901:role/securityaudit --role-session-name domainprotect
105106
```
106-
* copy and paste the returned temporary credentials to your desktop
107-
* create AWS cli credentials in CloudShell
108-
```
109-
vi .aws/credentials
110-
```
111-
* enter details in the following format
112-
```
113-
[profile_name]
114-
aws_access_key_id = XXXXXXXXXXXXXXXXXXXXXX
115-
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
116-
aws_session_token = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
117-
```
118-
* save and exit vi
119-
```
120-
:wq!
107+
* set the returned temporary credentials in the environmebt variables of your local machine:
108+
109+
```bash
110+
export AWS_ACCESS_KEY_ID=...
111+
export AWS_SECRET_ACCESS_KEY=...
112+
export AWS_SESSION_TOKEN=...
121113
```
114+
122115
* install dependencies and proceed with the scans, e.g.
123116
```
124117
sudo pip3 install dnspython
125-
python3 manual_scans/aws/aws-ns-domain.py --profile profile_name
118+
python3 manual_scans/aws/aws-ns-domain.py
126119
```
127120

128121
[back to README](../../README.md)

manual_scans/aws/aws-alias-cloudfront-s3.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ def vulnerable_alias_cloudfront_s3(domain_name):
2727
return False
2828

2929

30-
def route53(profile):
30+
def route53():
3131

3232
print("Searching for Route53 hosted zones")
3333

34-
session = boto3.Session(profile_name=profile)
34+
session = boto3.Session()
3535
route53 = session.client("route53")
3636

37-
hosted_zones = list_hosted_zones_manual_scan(profile)
37+
hosted_zones = list_hosted_zones_manual_scan()
3838
for hosted_zone in hosted_zones:
3939
print(f"Searching for CloudFront Alias records in {hosted_zone['Name']}")
4040
paginator_records = route53.get_paginator("list_resource_record_sets")
@@ -64,11 +64,8 @@ def route53(profile):
6464
if __name__ == "__main__":
6565

6666
parser = argparse.ArgumentParser(description="Prevent Subdomain Takeover")
67-
parser.add_argument("--profile", required=True)
68-
args = parser.parse_args()
69-
profile = args.profile
7067

71-
route53(profile)
68+
route53()
7269

7370
count = len(vulnerable_domains)
7471
my_print(f"\nTotal Vulnerable Domains Found: {str(count)}", "INFOB")

manual_scans/aws/aws-alias-eb.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import boto3
55

6+
from utils.utils_aws import eb_susceptible
67
from utils.utils_aws_manual import list_hosted_zones_manual_scan
78
from utils.utils_dns import firewall_test
89
from utils.utils_dns import vulnerable_alias
@@ -13,14 +14,14 @@
1314
missing_resources = []
1415

1516

16-
def route53(profile):
17+
def route53():
1718

1819
print("Searching for Route53 hosted zones")
1920

20-
session = boto3.Session(profile_name=profile)
21+
session = boto3.Session()
2122
route53 = session.client("route53")
2223

23-
hosted_zones = list_hosted_zones_manual_scan(profile)
24+
hosted_zones = list_hosted_zones_manual_scan()
2425
for hosted_zone in hosted_zones:
2526
print(f"Searching for ElasticBeanststalk Alias records in hosted zone {hosted_zone['Name']}")
2627
paginator_records = route53.get_paginator("list_resource_record_sets")
@@ -34,7 +35,7 @@ def route53(profile):
3435
record_sets = [
3536
r
3637
for r in page_records["ResourceRecordSets"]
37-
if "AliasTarget" in r and "elasticbeanstalk.com" in r["AliasTarget"]["DNSName"]
38+
if "AliasTarget" in r and eb_susceptible(r["AliasTarget"]["DNSName"])
3839
]
3940

4041
for record in record_sets:
@@ -52,12 +53,9 @@ def route53(profile):
5253
if __name__ == "__main__":
5354

5455
parser = argparse.ArgumentParser(description="Prevent Subdomain Takeover")
55-
parser.add_argument("--profile", required=True)
56-
args = parser.parse_args()
57-
profile = args.profile
5856

5957
firewall_test()
60-
route53(profile)
58+
route53()
6159

6260
count = len(vulnerable_domains)
6361
my_print("\nTotal Vulnerable Domains Found: " + str(count), "INFOB")

0 commit comments

Comments
 (0)