Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
AdnaneKhan committed Apr 24, 2024
1 parent d491508 commit bbb3a90
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 72 deletions.
144 changes: 74 additions & 70 deletions gato/enumerate/recommender.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,79 @@

class Recommender:

@staticmethod
def __check_pwn_entry(repository, entry):
designation = 'UNKNOWN'
pwn_req = entry['details']
if entry['environments']:
for candidate, details in pwn_req['candidates'].items():
for step in details['steps']:
# If we have a sha, it's immutable so skip.
if 'github.event.pull_request.head.sha' in step['ref'].lower():
repository.clear_pwn_request(entry['workflow_name'])
return
Output.result(
f"The workflow {Output.bright(entry['workflow_name'])} contains an"
f" environment protection rule"
f" but the workflow uses a mutable reference to checkout PR code."
f" This could be exploited via a race condition!"
)
else:
default = True

if len(pwn_req['triggers']) == 1 and pwn_req['triggers'][0] == 'pull_request_target:labeled':
for candidate, details in pwn_req['candidates'].items():
for step in details['steps']:
# If we have a sha, it's immutable so skip.
if 'github.event.pull_request.head.sha' in step['ref'].lower():
repository.clear_pwn_request(entry['workflow_name'])
return
Output.result(
f"The workflow {Output.bright(entry['workflow_name'])} contains "
f"label-based gating but the workflow uses a mutable reference "
f"to checkout PR code. \n This could be exploited via a race condition!"
)
else:
default = True

if default:
Output.result(
f"The workflow {Output.bright(entry['workflow_name'])} runs on a risky trigger "
f"and might check out the PR code, see if it runs it!"
)
Output.tabbed(f'Trigger(s): {Output.bright(",".join(pwn_req["triggers"]))}')

for candidate, details in pwn_req['candidates'].items():
if details['gated']:
for step in details['steps']:
# If we have a sha, it's immutable so skip.
if 'github.event.pull_request.head.sha' in step['ref'].lower():
repository.clear_pwn_request(entry['workflow_name'])
return
Output.result(
f"The workflow {Output.bright(entry['workflow_name'])} contains"
f" a permission check but the workflow uses a mutable reference to"
" checkout PR code.")

if details['confidence'] and details['confidence'] == 'MEDIUM' and designation in ['UNKNOWN','LOW']:
designation = details['confidence']

Output.info(f'Job: {candidate}')

if details.get('if_check', ''):
Output.info(f'Job if check: {details["if_check"]}')
for step in details['steps']:
Output.tabbed(f'Checkout Step Ref: {step["ref"]}')
if 'if_check' in step and step['if_check']:
Output.tabbed(f'If check: {step["if_check"]}')

Output.owned(f'Confidence: {Output.red(designation)}')
Output.info(f"You can access the workflow at: "
f"{repository.repo_data['html_url']}/blob/"
f"{repository.repo_data['default_branch']}/"
f".github/workflows/{entry['workflow_name']}"
)

@staticmethod
def print_repo_attack_recommendations(
scopes: list, repository: Repository
Expand All @@ -27,76 +100,7 @@ def print_repo_attack_recommendations(
if repository.has_pwn_request():
risks = repository.pwn_req_risk
for entry in risks:
designation = 'UNKNOWN'
pwn_req = entry['details']
if entry['environments']:
for candidate, details in pwn_req['candidates'].items():
for step in details['steps']:
# If we have a sha, it's immutable so skip.
if 'github.event.pull_request.head.sha' in step['ref'].lower():
repository.clear_pwn_request(entry['workflow_name'])
return
Output.result(
f"The workflow {Output.bright(entry['workflow_name'])} contains an"
f" evironment protection rule"
f" but the workflow uses a mutable reference to checkout PR code."
f" This could be exploited via a race condition!"
)
else:
default = True

if len(pwn_req['triggers']) == 1 and pwn_req['triggers'][0] == 'pull_request_target:labeled':
for candidate, details in pwn_req['candidates'].items():
for step in details['steps']:
# If we have a sha, it's immutable so skip.
if 'github.event.pull_request.head.sha' in step['ref'].lower():
repository.clear_pwn_request(entry['workflow_name'])
return
Output.result(
f"The workflow {Output.bright(entry['workflow_name'])} contains "
f"label-based gating but the workflow uses a mutable reference "
f"to checkout PR code. \n This could be exploited via a race condition!"
)
else:
default = True

if default:
Output.result(
f"The workflow {Output.bright(entry['workflow_name'])} runs on a risky trigger "
f"and might check out the PR code, see if it runs it!"
)
Output.tabbed(f'Trigger(s): {Output.bright(",".join(pwn_req["triggers"]))}')

for candidate, details in pwn_req['candidates'].items():
if details['gated']:
for step in details['steps']:
# If we have a sha, it's immutable so skip.
if 'github.event.pull_request.head.sha' in step['ref'].lower():
repository.clear_pwn_request(entry['workflow_name'])
return
Output.result(
f"The workflow {Output.bright(entry['workflow_name'])} contains"
f" a permission check but the workflow uses a mutable reference to"
" checkout PR code.")

if details['confidence'] and details['confidence'] == 'MEDIUM' and designation in ['UNKNOWN','LOW']:
designation = details['confidence']

Output.info(f'Job: {candidate}')

if details.get('if_check', ''):
Output.info(f'Job if check: {details["if_check"]}')
for step in details['steps']:
Output.tabbed(f'Checkout Step Ref: {step["ref"]}')
if 'if_check' in step and step['if_check']:
Output.tabbed(f'If check: {step["if_check"]}')

Output.owned(f'Confidence: {Output.red(designation)}')
Output.info(f"You can access the workflow at: "
f"{repository.repo_data['html_url']}/blob/"
f"{repository.repo_data['default_branch']}/"
f".github/workflows/{entry['workflow_name']}"
)
Recommender.__check_pwn_entry(repository, entry)

if repository.has_injection():
risks = repository.injection_risk
Expand Down
5 changes: 3 additions & 2 deletions gato/enumerate/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,19 @@ def __perform_yml_enumeration(self, repository: Repository):
# a direct query.
if (wf_injection or pwn_reqs) and 'environment:' in workflow.workflow_contents:
rules = self.api.get_all_environment_protection_rules(repository.name)
if parsed_yml.check_rules(rules):
# If the rules we actually use have no requirements, skip
rules = []
else:
rules = []

# Checks any local workflows referenced by this
self.__check_callees(parsed_yml, repository, rules)

if wf_injection and not skip_checks and not rules:

injection_package = self.__create_info_package(parsed_yml.wf_name,workflow_url, wf_injection, rules)
repository.set_injection(injection_package)
if pwn_reqs and not skip_checks:

pwn_request_package = self.__create_info_package(parsed_yml.wf_name,workflow_url, pwn_reqs, rules)
repository.set_pwn_request(pwn_request_package)

Expand Down
8 changes: 8 additions & 0 deletions gato/workflow_parser/components/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ def __init__(self, job_data: dict, job_name: str):
self.steps = []
self.env = {}
self.permissions = []
self.deployments = []
self.if_condition = None
self.uses = None
self.caller = False
self.has_gate = False
self.needs = None


if 'environment' in self.job_data:
if type (self.job_data['environment']) == list:
self.deployments.extend(self.job_data['environment'])
else:
self.deployments.append(self.job_data['environment'])

if 'env' in self.job_data:
self.env = self.job_data['env']

Expand Down
11 changes: 11 additions & 0 deletions gato/workflow_parser/workflow_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,17 @@ def check_pwn_request(self, bypass=False):
checkout_risk['triggers'] = vulnerable_triggers

return checkout_risk

def check_rules(self, gate_rules):
"""
"""
for rule in gate_rules:
for job in self.jobs:
for deploy_rule in job.deployments:
if deploy_rule == rule:
return False
return True


def check_injection(self, bypass=False):
"""Check for potential script injection vulnerabilities.
Expand Down

0 comments on commit bbb3a90

Please sign in to comment.