From 0369024089bf6cbaef88d6a003aa5a42b5b8ae20 Mon Sep 17 00:00:00 2001 From: Henry Yamauchi Date: Wed, 10 Feb 2016 16:10:22 -0600 Subject: [PATCH] Check if user A can access user B's resource This test uses two users, generates their tokens and tries to use token B to access the user A's profile. If it succeeds this shows a security vulnerability exists. Change-Id: I67c89f74985e598999080f6bd89b55934df686ef Implements: blueprint test-unauthed --- examples/configs/keystone.config | 7 + examples/payloads/keystone/user_xauth_get.txt | 3 + syntribos/config.py | 4 + syntribos/tests/auth/__init__.py | 15 +++ syntribos/tests/auth/base_auth.py | 122 ++++++++++++++++++ syntribos/tests/auth/datagen.py | 58 +++++++++ .../tests/auth/user1_tries_user2s_token.py | 27 ++++ 7 files changed, 236 insertions(+) create mode 100644 examples/payloads/keystone/user_xauth_get.txt create mode 100644 syntribos/tests/auth/__init__.py create mode 100644 syntribos/tests/auth/base_auth.py create mode 100644 syntribos/tests/auth/datagen.py create mode 100644 syntribos/tests/auth/user1_tries_user2s_token.py diff --git a/examples/configs/keystone.config b/examples/configs/keystone.config index 42bd3488..856129f2 100644 --- a/examples/configs/keystone.config +++ b/examples/configs/keystone.config @@ -1,10 +1,17 @@ [syntribos] endpoint= +#version=v2 # used for cross auth tests (-t AUTH_WITH_SOMEONE_ELSE_TOKEN) [user] username= password= user_id= +# used for cross auth tests (-t AUTH_WITH_SOMEONE_ELSE_TOKEN) +#[alt_user] +#username= +#password= +#user_id= + [auth] endpoint= diff --git a/examples/payloads/keystone/user_xauth_get.txt b/examples/payloads/keystone/user_xauth_get.txt new file mode 100644 index 00000000..9fd1f83b --- /dev/null +++ b/examples/payloads/keystone/user_xauth_get.txt @@ -0,0 +1,3 @@ +GET /v2.0/users/USER_ID HTTP/1.1 +Accept: application/json +X-Auth-Token: CALL_EXTERNAL|syntribos.extensions.identity.client:get_token_v2:["user"]| diff --git a/syntribos/config.py b/syntribos/config.py index 8c49fb80..604e81c7 100644 --- a/syntribos/config.py +++ b/syntribos/config.py @@ -23,3 +23,7 @@ class MainConfig(data_interfaces.ConfigSectionInterface): @property def endpoint(self): return self.get("endpoint") + + @property + def version(self): + return self.get("version") diff --git a/syntribos/tests/auth/__init__.py b/syntribos/tests/auth/__init__.py new file mode 100644 index 00000000..71a5888d --- /dev/null +++ b/syntribos/tests/auth/__init__.py @@ -0,0 +1,15 @@ +""" +Copyright 2015 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/syntribos/tests/auth/base_auth.py b/syntribos/tests/auth/base_auth.py new file mode 100644 index 00000000..7c39de06 --- /dev/null +++ b/syntribos/tests/auth/base_auth.py @@ -0,0 +1,122 @@ +""" +Copyright 2016 Rackspace +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os + +from syntribos.clients.http import client +from syntribos.issue import Issue +import syntribos.tests.auth.datagen +from syntribos.tests import base + +data_dir = os.environ.get("CAFE_DATA_DIR_PATH") + + +class BaseAuthTestCase(base.BaseTestCase): + client = client() + failure_keys = None + success_keys = None + + @classmethod + def data_driven_failure_cases(cls): + failure_assertions = [] + if cls.failure_keys is None: + return [] + for line in cls.failure_keys: + failure_assertions.append((cls.assertNotIn, + line, cls.resp.content)) + return failure_assertions + + @classmethod + def data_driven_pass_cases(cls): + if cls.success_keys is None: + return True + for s in cls.success_keys: + if s in cls.resp.content: + return True + return False + + @classmethod + def setUpClass(cls): + super(BaseAuthTestCase, cls).setUpClass() + cls.issues = [] + cls.failures = [] + cls.resp = cls.client.request( + method=cls.request.method, url=cls.request.url, + headers=cls.request.headers, params=cls.request.params, + data=cls.request.data) + + @classmethod + def tearDownClass(cls): + super(BaseAuthTestCase, cls).tearDownClass() + for issue in cls.issues: + if issue.failure: + cls.failures.append(issue.as_dict()) + + def test_case(self): + text = ("This request did not fail with 404 (User not found)" + " therefore it indicates that authentication with" + " another user's token was successful.") + self.register_issue( + Issue(test="try_alt_user_token", + severity="High", + text=text, + assertions=[(self.assertTrue, self.resp.status_code == 404)]) + ) + self.test_issues() + + @classmethod + def get_test_cases(cls, filename, file_content): + """Generates the test cases + + For this particular test, only a single test + is created (in addition to the base case, that is) + """ + + alt_user_config = syntribos.extensions.identity.config.UserConfig( + section_name='alt_user') + alt_user_id = alt_user_config.user_id + if alt_user_id is None: + return + + request_obj = syntribos.tests.auth.datagen.AuthParser.create_request( + file_content, os.environ.get("SYNTRIBOS_ENDPOINT")) + + prepared_copy = request_obj.get_prepared_copy() + cls.init_response = cls.client.send_request(prepared_copy) + + prefix_name = "{filename}_{test_name}_{fuzz_file}_".format( + filename=filename, test_name=cls.test_name, fuzz_file='auth') + + main_config = syntribos.config.MainConfig() + version = main_config.version + + if version is None or version == 'v2': + alt_token = syntribos.extensions.identity.client.get_token_v2( + 'alt_user', 'auth') + else: + alt_token = syntribos.extensions.identity.client.get_token_v3( + 'alt_user', 'auth') + alt_user_token_request = request_obj.get_prepared_copy() + for h in alt_user_token_request.headers: + if 'x-auth-token' == h.lower(): + alt_user_token_request.headers[h] = alt_token + + test_name = prefix_name + 'another_users_token' + + def test_gen(test_name, request): + yield (test_name, request) + + for name, req in test_gen(test_name, alt_user_token_request): + c = cls.extend_class(test_name, + {"request": alt_user_token_request}) + yield c diff --git a/syntribos/tests/auth/datagen.py b/syntribos/tests/auth/datagen.py new file mode 100644 index 00000000..b713cb47 --- /dev/null +++ b/syntribos/tests/auth/datagen.py @@ -0,0 +1,58 @@ +""" +Copyright 2016 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from syntribos.clients.http.models import RequestHelperMixin +from syntribos.clients.http.models import RequestObject +from syntribos.clients.http import parser +from syntribos.extensions.identity.config import UserConfig + + +class AuthMixin(object): + """AuthMixin Class + + AuthBehavior provides utility methods to manipulate data before + a request object is created. + """ + + @staticmethod + def remove_braces(string): + return string.replace("}", "").replace("{", "") + + +class AuthRequest(RequestObject, AuthMixin, RequestHelperMixin): + """AuthRequest Class + + This class specializes the generic RequestObject to + create an auth test specific class. + """ + + def prepare_request(self, auth_type=None): + super(AuthRequest, self).prepare_request() + if auth_type != "url": + self.url = self.remove_braces(self.url) + user_config = UserConfig(section_name='user') + user_id = user_config.user_id + self.url = self.url.replace('USER_ID', user_id) + + +class AuthParser(parser): + """AuthParser Class + + This class is a container class to hold + an auth request object type. + """ + + request_model_type = AuthRequest diff --git a/syntribos/tests/auth/user1_tries_user2s_token.py b/syntribos/tests/auth/user1_tries_user2s_token.py new file mode 100644 index 00000000..5eed6695 --- /dev/null +++ b/syntribos/tests/auth/user1_tries_user2s_token.py @@ -0,0 +1,27 @@ +""" +Copyright 2016 Rackspace + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from syntribos.tests.auth import base_auth + + +class AuthWithSomeoneElsesToken(base_auth.BaseAuthTestCase): + """AuthWithSomeoneElsesToken Class + + This is just a specialization of the base auth test class + which supplies the test name and type. + """ + test_name = "AUTH_WITH_SOMEONE_ELSE_TOKEN" + test_type = "headers"