CVE-2021-37705: Incorrect Authorization (CWE-863)
value: OneFuzz is an open source self-hosted Fuzzing-As-A-Service platform. Starting with OneFuzz 2.12.0 or greater, an incomplete authorization check allows an authenticated user from any Azure Active Directory tenant to make authorized API calls to a vulnerable OneFuzz instance. To be vulnerable, a OneFuzz deployment must be both version 2.12.0 or greater and deployed with the non-default --multi_tenant_domain option. This can result in read/write access to private data such as software vulnerability and crash information, security testing tools and proprietary code and symbols. Via authorized API calls, this also enables tampering with existing data and unauthorized code execution on Azure compute resources. This issue is resolved starting in release 2.31.0, via the addition of application-level check of the bearer token's `issuer` against an administrator-configured allowlist. As a workaround users can restrict access to the tenant of a deployed OneFuzz instance < 2.31.0 by redeploying in the default configuration, which omits the `--multi_tenant_domain` option.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 10
Published: 2021-08-13T21:15Z
Repository: https://github.com/microsoft/onefuzz
Language: Python
Files changed: docs/webhook_events.md, src/api-service/__app__/info/__init__.py, src/api-service/__app__/negotiate/__init__.py, src/api-service/__app__/onefuzzlib/config.py, src/api-service/__app__/onefuzzlib/user_credentials.py, src/api-service/tests/test_auth_check.py, src/deployment/azuredeploy.json, src/deployment/deploy.py, src/deployment/set_admins.py, src/pytypes/extra/generate-docs.py, src/pytypes/onefuzztypes/models.py, src/pytypes/tests/test_instance_config_update.py
Commit date: 2021-08-13 14:50:54 -0400
Commit message: Merge pull request from GHSA-q5vh-6whw-x745

* verify aad tenants, primarily needed in multi-tenant deployments

* add logging and fix trailing slash for issuer

* handle call_if* not supporting additional argument callbacks

* add logging

* include new datatype in webhook docs

* fix pytypes unit tests

Co-authored-by: Brian Caswell <bmc@shmoo.com>
Diff: diff --git a/docs/webhook_events.md b/docs/webhook_events.md
index 9dc79c7..967e94a 100644
--- a/docs/webhook_events.md
+++ b/docs/webhook_events.md
@@ -639,11 +639,14 @@ Each event will be submitted via HTTP POST to the user provided URL.
 {
     "config": {
         "admins": [
             "00000000-0000-0000-0000-000000000000"
         ],
-        "allow_pool_management": true
+        "allow_pool_management": true,
+        "allowed_aad_tenants": [
+            "00000000-0000-0000-0000-000000000000"
+        ]
     }
 }
 ```
 
 #### Schema
@@ -663,12 +666,23 @@ Each event will be submitted via HTTP POST to the user provided URL.
                 },
                 "allow_pool_management": {
                     "default": true,
                     "title": "Allow Pool Management",
                     "type": "boolean"
+                },
+                "allowed_aad_tenants": {
+                    "items": {
+                        "format": "uuid",
+                        "type": "string"
+                    },
+                    "title": "Allowed Aad Tenants",
+                    "type": "array"
                 }
             },
+            "required": [
+                "allowed_aad_tenants"
+            ],
             "title": "InstanceConfig",
             "type": "object"
         }
     },
     "properties": {
@@ -5597,12 +5611,23 @@ Each event will be submitted via HTTP POST to the user provided URL.
                 },
                 "allow_pool_management": {
                     "default": true,
                     "title": "Allow Pool Management",
                     "type": "boolean"
+                },
+                "allowed_aad_tenants": {
+                    "items": {
+                        "format": "uuid",
+                        "type": "string"
+                    },
+                    "title": "Allowed Aad Tenants",
+                    "type": "array"
                 }
             },
+            "required": [
+                "allowed_aad_tenants"
+            ],
             "title": "InstanceConfig",
             "type": "object"
         },
         "JobConfig": {
             "properties": {
diff --git a/src/api-service/__app__/info/__init__.py b/src/api-service/__app__/info/__init__.py
index 61bf62c..bdd9ce8 100644
--- a/src/api-service/__app__/info/__init__.py
+++ b/src/api-service/__app__/info/__init__.py
@@ -12,15 +12,16 @@ from ..onefuzzlib.azure.creds import (
     get_insights_appid,
     get_insights_instrumentation_key,
     get_instance_id,
     get_subscription,
 )
+from ..onefuzzlib.endpoint_authorization import call_if_user
 from ..onefuzzlib.request import ok
 from ..onefuzzlib.versions import versions
 
 
-def main(req: func.HttpRequest) -> func.HttpResponse:
+def get(req: func.HttpRequest) -> func.HttpResponse:
     response = ok(
         Info(
             resource_group=get_base_resource_group(),
             region=get_base_region(),
             subscription=get_subscription(),
@@ -30,5 +31,13 @@ def main(req: func.HttpRequest) -> func.HttpResponse:
             insights_instrumentation_key=get_insights_instrumentation_key(),
         )
     )
 
     return response
+
+
+def main(req: func.HttpRequest) -> func.HttpResponse:
+    methods = {"GET": get}
+    method = methods[req.method]
+    result = call_if_user(req, method)
+
+    return result
diff --git a/src/api-service/__app__/negotiate/__init__.py b/src/api-service/__app__/negotiate/__init__.py
index f6898ca..e1a6a5f 100644
--- a/src/api-service/__app__/negotiate/__init__.py
+++ b/src/api-service/__app__/negotiate/__init__.py
@@ -3,19 +3,32 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
 import azure.functions as func
 
+from ..onefuzzlib.endpoint_authorization import call_if_user
+
 # This endpoint handles the signalr negotation
 # As we do not differentiate from clients at this time, we pass the Functions runtime
 # provided connection straight to the client
 #
 # For more info:
 # https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-concept-internals
 
 
 def main(req: func.HttpRequest, connectionInfoJson: str) -> func.HttpResponse:
-    return func.HttpResponse(
-        connectionInfoJson,
-        status_code=200,
-        headers={"Content-type": "application/json"},
-    )
+    # NOTE: this is a sub-method because the call_if* do not support callbacks with
+    # additional arguments at this time.  Once call_if* supports additional arguments,
+    # this should be made a generic function
+    def post(req: func.HttpRequest) -> func.HttpResponse:
+        return func.HttpResponse(
+            connectionInfoJson,
+            status_code=200,
+            headers={"Content-type": "application/json"},
+        )
+
+    methods = {"POST": post}
+    method = methods[req.method]
+
+    result = call_if_user(req, method)
+
+    return result
diff --git a/src/api-service/__app__/onefuzzlib/config.py b/src/api-service/__app__/onefuzzlib/config.py
index c3f124d..c58131d 100644
--- a/src/api-service/__app__/onefuzzlib/config.py
+++ b/src/api-service/__app__/onefuzzlib/config.py
@@ -23,11 +23,11 @@ class InstanceConfig(BASE_CONFIG, ORMMixin):
 
     @classmethod
     def fetch(cls) -> "InstanceConfig":
         entry = cls.get(get_instance_name())
         if entry is None:
-            entry = cls()
+            entry = cls(allowed_aad_tenants=[])
             entry.save()
         return entry
 
     def save(self, new: bool = False, require_etag: bool = False) -> None:
         super().save(new=new, require_etag=require_etag)
diff --git a/src/api-service/__app__/onefuzzlib/user_credentials.py b/src/api-service/__app__/onefuzzlib/user_credentials.py
index 71c648c..ca92220 100644
--- a/src/api-service/__app__/onefuzzlib/user_credentials.py
+++ b/src/api-service/__app__/onefuzzlib/user_credentials.py
@@ -1,18 +1,22 @@
 #!/usr/bin/env python
 #
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from typing import Optional
+import logging
+from typing import List, Optional
 from uuid import UUID
 
 import azure.functions as func
 import jwt
+from memoization import cached
 from onefuzztypes.enums import ErrorCode
 from onefuzztypes.models import Error, Result, UserInfo
 
+from .config import InstanceConfig
+
 
 def get_bearer_token(request: func.HttpRequest) -> Optional[str]:
     auth: str = request.headers.get("Authorization", None)
     if not auth:
         return None
@@ -37,21 +41,39 @@ def get_auth_token(request: func.HttpRequest) -> Optional[str]:
     if token_header is None:
         return None
     return str(token_header)
 
 
+@cached(ttl=60)
+def get_allowed_tenants() -> List[str]:
+    config = InstanceConfig.fetch()
+    entries = [f"https://sts.windows.net/{x}/" for x in config.allowed_aad_tenants]
+    return entries
+
+
 def parse_jwt_token(request: func.HttpRequest) -> Result[UserInfo]:
     """Obtains the Access Token from the Authorization Header"""
     token_str = get_auth_token(request)
     if token_str is None:
         return Error(
             code=ErrorCode.INVALID_REQUEST,
             errors=["unable to find authorization token"],
         )
 
-    # This token has already been verified by the azure authentication layer
+    # The JWT token has already been verified by the azure authentication layer,
+    # but we need to verify the tenant is as we expect.
     token = jwt.decode(token_str, options={"verify_signature": False})
 
+    if "iss" not in token:
+        return Error(
+            code=ErrorCode.INVALID_REQUEST, errors=["missing issuer from token"]
+        )
+
+    tenants = get_allowed_tenants()
+    if token["iss"] not in tenants:
+        logging.error("issuer not from allowed tenant: %s - %s", token["iss"], tenants)
+        return Error(code=ErrorCode.INVALID_REQUEST, errors=["unauthorized AAD issuer"])
+
     application_id = UUID(token["appid"]) if "appid" in token else None
     object_id = UUID(token["oid"]) if "oid" in token else None
     upn = token.get("upn")
     return UserInfo(application_id=application_id, object_id=object_id, upn=upn)
diff --git a/src/api-service/tests/test_auth_check.py b/src/api-service/tests/test_auth_check.py
index 18028af..1391142 100644
--- a/src/api-service/tests/test_auth_check.py
+++ b/src/api-service/tests/test_auth_check.py
@@ -3,11 +3,11 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
 import os
 import unittest
-from uuid import uuid4
+from uuid import UUID, uuid4
 
 from onefuzztypes.models import UserInfo
 
 from __app__.onefuzzlib.config import InstanceConfig
 from __app__.onefuzzlib.endpoint_authorization import (
@@ -23,73 +23,104 @@ class TestAdmin(unittest.TestCase):
     def test_modify_config(self) -> None:
         user1 = uuid4()
         user2 = uuid4()
 
         # no admins set
-        self.assertTrue(can_modify_config_impl(InstanceConfig(), UserInfo()))
+        self.assertTrue(
+            can_modify_config_impl(
+                InstanceConfig(allowed_aad_tenants=[UUID(int=0)]), UserInfo()
+            )
+        )
 
         # with oid, but no admin
         self.assertTrue(
-            can_modify_config_impl(InstanceConfig(), UserInfo(object_id=user1))
+            can_modify_config_impl(
+                InstanceConfig(allowed_aad_tenants=[UUID(int=0)]),
+                UserInfo(object_id=user1),
+            )
         )
 
         # is admin
         self.assertTrue(
             can_modify_config_impl(
-                InstanceConfig(admins=[user1]), UserInfo(object_id=user1)
+                InstanceConfig(allowed_aad_tenants=[UUID(int=0)], admins=[user1]),
+                UserInfo(object_id=user1),
             )
         )
 
         # no user oid set
         self.assertFalse(
-            can_modify_config_impl(InstanceConfig(admins=[user1]), UserInfo())
+            can_modify_config_impl(
+                InstanceConfig(allowed_aad_tenants=[UUID(int=0)], admins=[user1]),
+                UserInfo(),
+            )
         )
 
         # not an admin
         self.assertFalse(
             can_modify_config_impl(
-                InstanceConfig(admins=[user1]), UserInfo(object_id=user2)
+                InstanceConfig(allowed_aad_tenants=[UUID(int=0)], admins=[user1]),
+                UserInfo(object_id=user2),
             )
         )
 
     def test_manage_pools(self) -> None:
         user1 = uuid4()
         user2 = uuid4()
 
         # by default, any can modify
         self.assertIsNone(
             check_can_manage_pools_impl(
-                InstanceConfig(allow_pool_management=True), UserInfo()
+                InstanceConfig(
+                    allowed_aad_tenants=[UUID(int=0)], allow_pool_management=True
+                ),
+                UserInfo(),
             )
         )
 
         # with oid, but no admin
         self.assertIsNone(
             check_can_manage_pools_impl(
-                InstanceConfig(allow_pool_management=True), UserInfo(object_id=user1)
+                InstanceConfig(
+                    allowed_aad_tenants=[UUID(int=0)], allow_pool_management=True
+                ),
+                UserInfo(object_id=user1),
             )
         )
 
         # is admin
         self.assertIsNone(
             check_can_manage_pools_impl(
-                InstanceConfig(allow_pool_management=False, admins=[user1]),
+                InstanceConfig(
+                    allowed_aad_tenants=[UUID(int=0)],
+                    allow_pool_management=False,
+                    admins=[user1],
+                ),
                 UserInfo(object_id=user1),
             )
         )
 
         # no user oid set
         self.assertIsNotNone(
             check_can_manage_pools_impl(
-                InstanceConfig(allow_pool_management=False, admins=[user1]), UserInfo()
+                InstanceConfig(
+                    allowed_aad_tenants=[UUID(int=0)],
+                    allow_pool_management=False,
+                    admins=[user1],
+                ),
+                UserInfo(),
             )
         )
 
         # not an admin
         self.assertIsNotNone(
             check_can_manage_pools_impl(
-                InstanceConfig(allow_pool_management=False, admins=[user1]),
+                InstanceConfig(
+                    allowed_aad_tenants=[UUID(int=0)],
+                    allow_pool_management=False,
+                    admins=[user1],
+                ),
                 UserInfo(object_id=user2),
             )
         )
 
 
diff --git a/src/deployment/azuredeploy.json b/src/deployment/azuredeploy.json
index 127fd33..bf2eaf7 100644
--- a/src/deployment/azuredeploy.json
+++ b/src/deployment/azuredeploy.json
@@ -819,8 +819,12 @@
             "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountNameFunc')), '2019-06-01').keys[0].value]"
         },
         "scaleset-identity": {
             "type": "string",
             "value": "[variables('scaleset_identity')]"
+        },
+        "tenant_id": {
+            "type": "string",
+            "value": "[subscription().tenantId]"
         }
     }
 }
diff --git a/src/deployment/deploy.py b/src/deployment/deploy.py
index 8ea9324..559f5da 100644
--- a/src/deployment/deploy.py
+++ b/src/deployment/deploy.py
@@ -70,11 +70,11 @@ from registration import (
     get_graph_client,
     register_application,
     set_app_audience,
     update_pool_registration,
 )
-from set_admins import update_admins
+from set_admins import update_admins, update_allowed_aad_tenants
 
 # Found by manually assigning the User.Read permission to application
 # registration in the admin portal. The values are in the manifest under
 # the section "requiredResourceAccess"
 USER_READ_PERMISSION = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
@@ -128,11 +128,12 @@ class Client:
         export_appinsights: bool,
         log_service_principal: bool,
         multi_tenant_domain: str,
         upgrade: bool,
         subscription_id: Optional[str],
-        admins: List[UUID]
+        admins: List[UUID],
+        allowed_aad_tenants: List[UUID],
     ):
         self.subscription_id = subscription_id
         self.resource_group = resource_group
         self.arm_template = arm_template
         self.location = location
@@ -159,10 +160,11 @@ class Client:
         }
         self.migrations = migrations
         self.export_appinsights = export_appinsights
         self.log_service_principal = log_service_principal
         self.admins = admins
+        self.allowed_aad_tenants = allowed_aad_tenants
 
         machine = platform.machine()
         system = platform.system()
 
         if system == "Linux" and machine == "x86_64":
@@ -558,17 +560,24 @@ class Client:
         name = self.results["deploy"]["func-name"]["value"]
         key = self.results["deploy"]["func-key"]["value"]
         table_service = TableService(account_name=name, account_key=key)
         migrate(table_service, self.migrations)
 
-    def set_admins(self) -> None:
+    def set_instance_config(self) -> None:
         name = self.results["deploy"]["func-name"]["value"]
         key = self.results["deploy"]["func-key"]["value"]
+        tenant = UUID(self.results["deploy"]["tenant_id"]["value"])
         table_service = TableService(account_name=name, account_key=key)
+
         if self.admins:
             update_admins(table_service, self.application_name, self.admins)
 
+        tenants = self.allowed_aad_tenants
+        if tenant not in tenants:
+            tenants.append(tenant)
+        update_allowed_aad_tenants(table_service, self.application_name, tenants)
+
     def create_queues(self) -> None:
         logger.info("creating eventgrid destination queue")
 
         name = self.results["deploy"]["func-name"]["value"]
         key = self.results["deploy"]["func-key"]["value"]
@@ -924,11 +933,11 @@ def main() -> None:
         ("assign_scaleset_identity_role", Client.assign_scaleset_identity_role),
     ]
 
     full_deployment_states = rbac_only_states + [
         ("apply_migrations", Client.apply_migrations),
-        ("set_admins", Client.set_admins),
+        ("set_instance_config", Client.set_instance_config),
         ("queues", Client.create_queues),
         ("eventgrid", Client.create_eventgrid),
         ("tools", Client.upload_tools),
         ("add_instance_id", Client.add_instance_id),
         ("instance-specific-setup", Client.upload_instance_setup),
@@ -1036,10 +1045,16 @@ def main() -> None:
         "--set_admins",
         type=UUID,
         nargs="*",
         help="set the list of administrators (by OID in AAD)",
     )
+    parser.add_argument(
+        "--allowed_aad_tenants",
+        type=UUID,
+        nargs="*",
+        help="Set additional AAD tenants beyond the tenant the app is deployed in",
+    )
 
     args = parser.parse_args()
 
     if shutil.which("func") is None:
         logger.error(FUNC_TOOLS_ERROR)
@@ -1064,10 +1079,11 @@ def main() -> None:
         log_service_principal=args.log_service_principal,
         multi_tenant_domain=args.multi_tenant_domain,
         upgrade=args.upgrade,
         subscription_id=args.subscription_id,
         admins=args.set_admins,
+        allowed_aad_tenants=args.allowed_aad_tenants or [],
     )
     if args.verbose:
         level = logging.DEBUG
     else:
         level = logging.WARN
diff --git a/src/deployment/set_admins.py b/src/deployment/set_admins.py
index c733c70..7b4e318 100644
--- a/src/deployment/set_admins.py
+++ b/src/deployment/set_admins.py
@@ -18,10 +18,25 @@ TABLE_NAME = "InstanceConfig"
 def create_if_missing(table_service: TableService) -> None:
     if not table_service.exists(TABLE_NAME):
         table_service.create_table(TABLE_NAME)
 
 
+def update_allowed_aad_tenants(
+    table_service: TableService, resource_group: str, tenants: List[UUID]
+) -> None:
+    create_if_missing(table_service)
+    as_str = [str(x) for x in tenants]
+    table_service.insert_or_merge_entity(
+        TABLE_NAME,
+        {
+            "PartitionKey": resource_group,
+            "RowKey": resource_group,
+            "allowed_aad_tenants": json.dumps(as_str),
+        },
+    )
+
+
 def update_admins(
     table_service: TableService, resource_group: str, admins: List[UUID]
 ) -> None:
     create_if_missing(table_service)
     admins_as_str: Optional[List[str]] = None
@@ -41,20 +56,26 @@ def update_admins(
 def main() -> None:
     formatter = argparse.ArgumentDefaultsHelpFormatter
     parser = argparse.ArgumentParser(formatter_class=formatter)
     parser.add_argument("resource_group")
     parser.add_argument("storage_account")
-    parser.add_argument("admins", type=UUID, nargs="*")
+    parser.add_argument("--admins", type=UUID, nargs="*")
+    parser.add_argument("--allowed_aad_tenants", type=UUID, nargs="*")
     args = parser.parse_args()
 
     client = get_client_from_cli_profile(StorageManagementClient)
     storage_keys = client.storage_accounts.list_keys(
         args.resource_group, args.storage_account
     )
     table_service = TableService(
         account_name=args.storage_account, account_key=storage_keys.keys[0].value
     )
-    update_admins(table_service, args.resource_group, args.admins)
+    if args.admins:
+        update_admins(table_service, args.resource_group, args.admins)
+    if args.allowed_aad_tenants:
+        update_allowed_aad_tenants(
+            table_service, args.resource_group, args.allowed_aad_tenants
+        )
 
 
 if __name__ == "__main__":
     main()
diff --git a/src/pytypes/extra/generate-docs.py b/src/pytypes/extra/generate-docs.py
index 260a686..f49e5a8 100755
--- a/src/pytypes/extra/generate-docs.py
+++ b/src/pytypes/extra/generate-docs.py
@@ -256,11 +256,15 @@ def main() -> None:
             report=report,
         ),
         EventFileAdded(container=Container("container-name"), filename="example.txt"),
         EventNodeHeartbeat(machine_id=UUID(int=0), pool_name=PoolName("example")),
         EventTaskHeartbeat(task_id=UUID(int=0), job_id=UUID(int=0), config=task_config),
-        EventInstanceConfigUpdated(config=InstanceConfig(admins=[UUID(int=0)])),
+        EventInstanceConfigUpdated(
+            config=InstanceConfig(
+                admins=[UUID(int=0)], allowed_aad_tenants=[UUID(int=0)]
+            )
+        ),
     ]
 
     # works around `mypy` not handling that Union has `__args__`
     for event in getattr(Event, "__args__", []):
         seen = False
diff --git a/src/pytypes/onefuzztypes/models.py b/src/pytypes/onefuzztypes/models.py
index cc46913..8d4d4d8 100644
--- a/src/pytypes/onefuzztypes/models.py
+++ b/src/pytypes/onefuzztypes/models.py
@@ -800,10 +800,12 @@ class InstanceConfig(BaseModel):
     admins: Optional[List[UUID]] = None
 
     # if set, only admins can manage pools or scalesets
     allow_pool_management: bool = Field(default=True)
 
+    allowed_aad_tenants: List[UUID]
+
     def update(self, config: "InstanceConfig") -> None:
         for field in config.__fields__:
             # If no admins are set, then ignore setting admins
             if field == "admins" and self.admins is None:
                 continue
@@ -815,7 +817,18 @@ class InstanceConfig(BaseModel):
     def check_admins(cls, value: Optional[List[UUID]]) -> Optional[List[UUID]]:
         if value is not None and len(value) == 0:
             raise ValueError("admins must be None or contain at least one UUID")
         return value
 
+    # At the moment, this only checks allowed_aad_tenants, however adding
+    # support for 3rd party JWT validation is anticipated in a future release.
+    @root_validator()
+    def check_instance_config(cls, values: Any) -> Any:
+        if "allowed_aad_tenants" not in values:
+            raise ValueError("missing allowed_aad_tenants")
+
+        if not len(values["allowed_aad_tenants"]):
+            raise ValueError("allowed_aad_tenants must not be empty")
+        return values
+
 
 _check_hotfix()
diff --git a/src/pytypes/tests/test_instance_config_update.py b/src/pytypes/tests/test_instance_config_update.py
index 8532c9a..31abba0 100644
--- a/src/pytypes/tests/test_instance_config_update.py
+++ b/src/pytypes/tests/test_instance_config_update.py
@@ -9,13 +9,17 @@ import unittest
 from onefuzztypes.models import InstanceConfig
 
 
 class TestInstanceConfig(unittest.TestCase):
     def test_with_admins(self) -> None:
-        no_admins = InstanceConfig(admins=None)
-        with_admins = InstanceConfig(admins=[UUID(int=0)])
-        with_admins_2 = InstanceConfig(admins=[UUID(int=1)])
+        no_admins = InstanceConfig(admins=None, allowed_aad_tenants=[UUID(int=0)])
+        with_admins = InstanceConfig(
+            admins=[UUID(int=0)], allowed_aad_tenants=[UUID(int=0)]
+        )
+        with_admins_2 = InstanceConfig(
+            admins=[UUID(int=1)], allowed_aad_tenants=[UUID(int=0)]
+        )
 
         no_admins.update(with_admins)
         self.assertEqual(no_admins.admins, None)
 
         with_admins.update(with_admins_2)

Vulnerable code:         "allow_pool_management": true
def main(req: func.HttpRequest) -> func.HttpResponse:
    return func.HttpResponse(
        connectionInfoJson,
        status_code=200,
        headers={"Content-type": "application/json"},
    )
            entry = cls()
from typing import Optional
    # This token has already been verified by the azure authentication layer
from uuid import uuid4
        self.assertTrue(can_modify_config_impl(InstanceConfig(), UserInfo()))
            can_modify_config_impl(InstanceConfig(), UserInfo(object_id=user1))
                InstanceConfig(admins=[user1]), UserInfo(object_id=user1)
            can_modify_config_impl(InstanceConfig(admins=[user1]), UserInfo())
                InstanceConfig(admins=[user1]), UserInfo(object_id=user2)
                InstanceConfig(allow_pool_management=True), UserInfo()
                InstanceConfig(allow_pool_management=True), UserInfo(object_id=user1)
                InstanceConfig(allow_pool_management=False, admins=[user1]),
                InstanceConfig(allow_pool_management=False, admins=[user1]), UserInfo()
                InstanceConfig(allow_pool_management=False, admins=[user1]),
from set_admins import update_admins
        admins: List[UUID]
    def set_admins(self) -> None:
        ("set_admins", Client.set_admins),
    parser.add_argument("admins", type=UUID, nargs="*")
    update_admins(table_service, args.resource_group, args.admins)
        EventInstanceConfigUpdated(config=InstanceConfig(admins=[UUID(int=0)])),
        no_admins = InstanceConfig(admins=None)
        with_admins = InstanceConfig(admins=[UUID(int=0)])
        with_admins_2 = InstanceConfig(admins=[UUID(int=1)])
Fixed code:         "allow_pool_management": true,
        "allowed_aad_tenants": [
            "00000000-0000-0000-0000-000000000000"
        ]
                },
                "allowed_aad_tenants": {
                    "items": {
                        "format": "uuid",
                        "type": "string"
                    },
                    "title": "Allowed Aad Tenants",
                    "type": "array"
            "required": [
                "allowed_aad_tenants"
            ],
                },
                "allowed_aad_tenants": {
                    "items": {
                        "format": "uuid",
                        "type": "string"
                    },
                    "title": "Allowed Aad Tenants",
                    "type": "array"
            "required": [
                "allowed_aad_tenants"
            ],
from ..onefuzzlib.endpoint_authorization import call_if_user
def get(req: func.HttpRequest) -> func.HttpResponse:


def main(req: func.HttpRequest) -> func.HttpResponse:
    methods = {"GET": get}
    method = methods[req.method]
    result = call_if_user(req, method)

    return result
from ..onefuzzlib.endpoint_authorization import call_if_user

    # NOTE: this is a sub-method because the call_if* do not support callbacks with
    # additional arguments at this time.  Once call_if* supports additional arguments,
    # this should be made a generic function
    def post(req: func.HttpRequest) -> func.HttpResponse:
        return func.HttpResponse(
            connectionInfoJson,
            status_code=200,
            headers={"Content-type": "application/json"},
        )

    methods = {"POST": post}
    method = methods[req.method]

    result = call_if_user(req, method)

    return result
            entry = cls(allowed_aad_tenants=[])
import logging
from typing import List, Optional
from memoization import cached
from .config import InstanceConfig

@cached(ttl=60)
def get_allowed_tenants() -> List[str]:
    config = InstanceConfig.fetch()
    entries = [f"https://sts.windows.net/{x}/" for x in config.allowed_aad_tenants]
    return entries


    # The JWT token has already been verified by the azure authentication layer,
    # but we need to verify the tenant is as we expect.
    if "iss" not in token:
        return Error(
            code=ErrorCode.INVALID_REQUEST, errors=["missing issuer from token"]
        )

    tenants = get_allowed_tenants()
    if token["iss"] not in tenants:
        logging.error("issuer not from allowed tenant: %s - %s", token["iss"], tenants)
        return Error(code=ErrorCode.INVALID_REQUEST, errors=["unauthorized AAD issuer"])

from uuid import UUID, uuid4
        self.assertTrue(
            can_modify_config_impl(
                InstanceConfig(allowed_aad_tenants=[UUID(int=0)]), UserInfo()
            )
        )
            can_modify_config_impl(
                InstanceConfig(allowed_aad_tenants=[UUID(int=0)]),
                UserInfo(object_id=user1),
            )
                InstanceConfig(allowed_aad_tenants=[UUID(int=0)], admins=[user1]),
                UserInfo(object_id=user1),
            can_modify_config_impl(
                InstanceConfig(allowed_aad_tenants=[UUID(int=0)], admins=[user1]),
                UserInfo(),
            )
                InstanceConfig(allowed_aad_tenants=[UUID(int=0)], admins=[user1]),
                UserInfo(object_id=user2),
                InstanceConfig(
                    allowed_aad_tenants=[UUID(int=0)], allow_pool_management=True
                ),
                UserInfo(),
                InstanceConfig(
                    allowed_aad_tenants=[UUID(int=0)], allow_pool_management=True
                ),
                UserInfo(object_id=user1),
                InstanceConfig(
                    allowed_aad_tenants=[UUID(int=0)],
                    allow_pool_management=False,
                    admins=[user1],
                ),
                InstanceConfig(
                    allowed_aad_tenants=[UUID(int=0)],
                    allow_pool_management=False,
                    admins=[user1],
                ),
                UserInfo(),
                InstanceConfig(
                    allowed_aad_tenants=[UUID(int=0)],
                    allow_pool_management=False,
                    admins=[user1],
                ),
        },
        "tenant_id": {
            "type": "string",
            "value": "[subscription().tenantId]"
from set_admins import update_admins, update_allowed_aad_tenants
        admins: List[UUID],
        allowed_aad_tenants: List[UUID],
        self.allowed_aad_tenants = allowed_aad_tenants
    def set_instance_config(self) -> None:
        tenant = UUID(self.results["deploy"]["tenant_id"]["value"])

        tenants = self.allowed_aad_tenants
        if tenant not in tenants:
            tenants.append(tenant)
        update_allowed_aad_tenants(table_service, self.application_name, tenants)

        ("set_instance_config", Client.set_instance_config),
    parser.add_argument(
        "--allowed_aad_tenants",
        type=UUID,
        nargs="*",
        help="Set additional AAD tenants beyond the tenant the app is deployed in",
    )
        allowed_aad_tenants=args.allowed_aad_tenants or [],
def update_allowed_aad_tenants(
    table_service: TableService, resource_group: str, tenants: List[UUID]
) -> None:
    create_if_missing(table_service)
    as_str = [str(x) for x in tenants]
    table_service.insert_or_merge_entity(
        TABLE_NAME,
        {
            "PartitionKey": resource_group,
            "RowKey": resource_group,
            "allowed_aad_tenants": json.dumps(as_str),
        },
    )


    parser.add_argument("--admins", type=UUID, nargs="*")
    parser.add_argument("--allowed_aad_tenants", type=UUID, nargs="*")
    if args.admins:
        update_admins(table_service, args.resource_group, args.admins)
    if args.allowed_aad_tenants:
        update_allowed_aad_tenants(
            table_service, args.resource_group, args.allowed_aad_tenants
        )
        EventInstanceConfigUpdated(
            config=InstanceConfig(
                admins=[UUID(int=0)], allowed_aad_tenants=[UUID(int=0)]
            )
        ),
    allowed_aad_tenants: List[UUID]

    # At the moment, this only checks allowed_aad_tenants, however adding
    # support for 3rd party JWT validation is anticipated in a future release.
    @root_validator()
    def check_instance_config(cls, values: Any) -> Any:
        if "allowed_aad_tenants" not in values:
            raise ValueError("missing allowed_aad_tenants")

        if not len(values["allowed_aad_tenants"]):
            raise ValueError("allowed_aad_tenants must not be empty")
        return values

        no_admins = InstanceConfig(admins=None, allowed_aad_tenants=[UUID(int=0)])
        with_admins = InstanceConfig(
            admins=[UUID(int=0)], allowed_aad_tenants=[UUID(int=0)]
        )
        with_admins_2 = InstanceConfig(
            admins=[UUID(int=1)], allowed_aad_tenants=[UUID(int=0)]
        )