diff --git a/analyzers/descriptor_analyzer.py b/analyzers/descriptor_analyzer.py index ad2ed89..d081f75 100644 --- a/analyzers/descriptor_analyzer.py +++ b/analyzers/descriptor_analyzer.py @@ -102,7 +102,7 @@ def _check_authn_authz(self) -> Tuple[bool, List[str], bool, List[str], bool, Li # We shouldn't be able to visit this link if the app uses authentication. if res_code >= 200 and res_code < 400 and not invalid_response: - if any(x in link for x in ('installed', 'uninstalled')): + if any(x in link for x in ('installed', 'install', 'uninstalled', 'uninstall')): signed_install_passed = False signed_install_proof_text = f"Lifecycle endpoint: {link} | Res Code: {res_code}" \ f" Auth Header: {auth_header}" diff --git a/reports/constants.py b/reports/constants.py index 48decc2..405fccf 100644 --- a/reports/constants.py +++ b/reports/constants.py @@ -30,10 +30,10 @@ MISSING_CACHE_HEADERS = 'We did not detect the correct Cache-Control header on one or more endpoints.' MISSING_REF_HEADERS = 'We did not detect the correct Referrer-Policy header on one or more endpoints.' MISSING_ATTRS_SESSION_COOKIE = 'We did not detect the "Secure" or "HttpOnly" attribute on one or more session cookies set by your app.' -MISSING_AUTHN = 'One or more endpoints returned a <400 status code without authentication information. This may indicate that your app is not performing authentication and authorization checks.\n* Note: If you think authentication is absolutely not required on reported endpoints, please raise a review on the vulnerability ticket raised and Atlassian EcoAppSec team will take a look.' +MISSING_AUTHN = 'One or more endpoints returned a <400 status code without authentication information. This may indicate that your app is not performing authentication and authorization checks.\n* JWT token used by the scanner is a fake JWT generated for testing purposes.\n* Note: If you think authentication is absolutely not required on reported endpoints, please raise a review on the vulnerability ticket raised and Atlassian EcoAppSec team will take a look.' BRANDING_ISSUE = 'Your app name or domain contained words that are not allowed. Please refer to our branding guidelines at: https://developer.atlassian.com/platform/marketplace/atlassian-brand-guidelines-for-marketplace-vendors/#app-names for more information.' -MISSING_SIGNED_INSTALL_AUTHN = 'One or more lifecycle endpoints returned a <400 status code with an invalid JWT token. This may indicate that your app is not performing authentication checks on lifecycle endpoints.' -MISSING_AUTHZ = 'One or more endpoints returned a <400 status code with a user JWT token while accessing admin restricted resources. This may indicate that your app is not performing authorization check on admin endpoints.' +MISSING_SIGNED_INSTALL_AUTHN = 'One or more lifecycle endpoints returned a <400 status code with an invalid JWT token. This may indicate that your app is not performing authentication checks on lifecycle endpoints.\n* Note: JWT token used by the scanner is a fake JWT generated for testing purposes.' +MISSING_AUTHZ = 'One or more endpoints returned a <400 status code with a user JWT token while accessing admin restricted resources. This may indicate that your app is not performing authorization check on admin endpoints.\n* Note: JWT token used by the scanner belongs to a test instance used sololey for testing purposes.' REQ_RECOMMENDATION = { '1.1': 'Refer to https://developer.atlassian.com/platform/marketplace/security-requirements-more-info/#authentication-and-authorization-of-application-resources for more information.', diff --git a/scans/descriptor_scan.py b/scans/descriptor_scan.py index 922c430..e5f8327 100644 --- a/scans/descriptor_scan.py +++ b/scans/descriptor_scan.py @@ -319,8 +319,9 @@ def _visit_link(self, link: str) -> Optional[requests.Response]: # Gracefully handle links that result in an exception, report them via warning, and skip any further tests try: # If we are requesting a lifecycle event, ensure we perform signed-install authentication check - if self._is_lifecycle_link(link) and any(x in link for x in ('installed', 'uninstalled')): - event_type = 'uninstalled' if 'uninstalled' in link else 'installed' + if self._is_lifecycle_link(link) and any(x in link for x in ('installed', 'install', 'uninstalled', + 'uninstall')): + event_type = 'uninstalled' if any(x in link for x in ('uninstalled', 'uninstall')) else 'installed' rs256_jwt = self._generate_fake_signed_install_jwt(link, 'POST') task['headers']['Content-Type'] = 'application/json' task['headers']['Authorization'] = f"JWT {rs256_jwt}" diff --git a/tests/test_descriptor_scan.py b/tests/test_descriptor_scan.py index 36dc95b..ce82239 100644 --- a/tests/test_descriptor_scan.py +++ b/tests/test_descriptor_scan.py @@ -39,7 +39,7 @@ def create_scan_results(links): 'referrer_header': 'Header missing', 'session_cookies': [], 'auth_header': None, - 'req_method': 'POST' if any(x in link for x in ['installed', 'uninstalled']) else 'GET', + 'req_method': 'POST' if any(x in link for x in ['installed', 'install', 'uninstalled', 'uninstall']) else 'GET', 'res_code': '200' if '?' in link else '204', 'response': str(response.text), 'authz_req_method': None, @@ -71,7 +71,7 @@ def test_scan_valid_app(): res['links'].sort() # Replace auth header to None for signed install/uninstall events for link in res['scan_results']: - if any(x in link for x in ['installed', 'uninstalled']): + if any(x in link for x in ['installed', 'install', 'uninstalled', 'uninstall']): res['scan_results'][link]['auth_header'] = None res = json.dumps(res, sort_keys=True)