Repository navigation
Keep ROPC password hashes out of the persisted http_cache - #977
Open
rumaisaaaaa263 wants to merge 1 commit into
Open
rumaisaaaaa263 wants to merge 1 commit into
rumaisaaaaa263 wants to merge 1 commit into
Conversation
ThrottledHttpClient keys cached HTTP 400 token responses by an unsalted SHA-256 of the request body. For the password grant, the password is the only input in that body an attacker does not already know, so a persisted http_cache allows an offline dictionary attack on it. ROPC returns 400 with the correct password whenever MFA or consent is required. Skip the 400 cache for the password grant, as is already done for device flow. 429/5xx/Retry-After throttling for ROPC is unchanged.
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The review identifies no active issues with the change excluding password grants from HTTP 400 response caching.
No open issues 🎉
What changed in this PR
This PR prevents password-derived hashes from being persisted in MSAL’s HTTP cache by excluding resource owner password credentials (ROPC) grants from HTTP 400 response caching.
Changes:
- Skips HTTP 400 caching for password grants while leaving other throttling behavior unchanged.
- Adds a regression test verifying that repeated ROPC failures return fresh responses and leave the cache empty.
| File | Description |
|---|---|
tests/test_throttled_http_client.py |
Adds regression coverage for uncached ROPC failures. |
msal/throttled_http_client.py |
Excludes password grants from HTTP 400 response caching. |
Review effort: Balanced
Author
|
@microsoft-github-policy-service agree company="Digiscrypt Technologies" |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ThrottledHttpClientcaches HTTP 400 token responses for 60 seconds underPOST <token_endpoint> hash=<sha256> 400, where the hash is an unsalted SHA-256 overstr(params) + str(data). Foracquire_token_by_username_password()on a public client,dataisclient_id,grant_type,client_info,username,passwordandscope, so the password is the only input an attacker does not already know. ROPC gets a 400 with the correct password whenever the user needs MFA or consent (AADSTS50076, AADSTS65001). Thehttp_cachedocstring says its content holds no tokens or PII and needs no encryption, so apps persist it as-is: the docstring recipe pickles it next to the script, and Azure CLI pickles it to~/.azure/msal_http_cache.binon every write and uses this API foraz login -u/-p. Anyone who can read that file can recover the password with an offline dictionary attack at SHA-256 speed. Ondev, a scratch script that calls the public API against a fake token endpoint returning AADSTS50076, pickles thehttp_cache, and then hashes candidate passwords against the stored key recovers the password; with this change the cache stays empty for the password grant.The 400 cache now skips the password grant the same way it already skips device flow. Hashing the body without the password would also close the leak, but a retry with a corrected password would then get the cached failure for 60 seconds. 429/5xx/Retry-After throttling for ROPC is unchanged because that key only hashes the username, and federated ROPC is not affected because its WS-Trust body carries a random MessageID.
test_ropc_error_should_not_be_cachedfails before the change and passes after, and the unit test command from.Pipelines/template-pipeline-stages.ymlpasses on Python 3.9 (456 passed, 20 skipped).