[
https://issues.apache.org/jira/browse/KNOX-3413?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Tamás Hanicz updated KNOX-3413:
-------------------------------
Description:
Reported by n0mi1k Security
When KnoxToken passcodes are in use, AbstractJWTFilter validates a presented
(tokenId, passcode) pair, and at AbstractJWTFilter.java:504 the guard is
`hasSignatureBeenVerified(passcode) || validatePasscode(tokenId, passcode)`;
validatePasscode (:544-553) is the only check that binds the passcode to the
token identifier — it compares tokenMAC.hash(tokenId, issueTime, userName,
passcode) against the passcode stored for that specific tokenId — and on a
successful check it records the passcode STRING in a shared cache via
recordSignatureVerification(passcode). Because the cache is keyed on the
passcode string alone (SignatureVerificationCache.hasSignatureBeenVerified /
verifiedTokens.getIfPresent at :96, populated by verifiedTokens.put(passcode,
true) at :105), a client that has once verified its own passcode can then
present that same passcode paired with a different, victim token identifier:
hasSignatureBeenVerified(passcode) returns true, the tokenId-binding MAC check
is skipped entirely, and the authenticated subject is built from the victim
tokenId via createSubjectFromTokenIdentifier (:361). Token identifiers are not
secret — they are returned in token-generation responses, are enumerable via
the getUserTokens endpoint, and equal the public client_id in the
client-credentials flow — so a low-privilege KnoxToken holder can authenticate
as any other still-valid token's principal, which is an identity-assertion /
authentication bypass, the keystone property a perimeter gateway is meant to
uphold. The attached PoC drives the real AbstractJWTFilter.validateToken + real
SignatureVerificationCache + real TokenMAC: after seeding the cache with
(tokenA, PASSCODE_A), the call validateToken(tokenB, PASSCODE_A) returns true
and createSubjectFromTokenIdentifier(tokenB) yields "bob" (tokenB's owner),
while the same call against an empty cache returns false (the real MAC check
rejects it).
> KnoxToken passcode verification accepts a valid passcode for a different
> token
> -------------------------------------------------------------------------------
>
> Key: KNOX-3413
> URL: https://issues.apache.org/jira/browse/KNOX-3413
> Project: Apache Knox
> Issue Type: Bug
> Components: Server
> Affects Versions: 2.1.0, 3.0.0
> Reporter: Tamás Hanicz
> Assignee: Tamás Hanicz
> Priority: Blocker
> Fix For: 3.0.0, 3.1.0
>
> Time Spent: 0.5h
> Remaining Estimate: 0h
>
> Reported by n0mi1k Security
>
> When KnoxToken passcodes are in use, AbstractJWTFilter validates a presented
> (tokenId, passcode) pair, and at AbstractJWTFilter.java:504 the guard is
> `hasSignatureBeenVerified(passcode) || validatePasscode(tokenId, passcode)`;
> validatePasscode (:544-553) is the only check that binds the passcode to the
> token identifier — it compares tokenMAC.hash(tokenId, issueTime, userName,
> passcode) against the passcode stored for that specific tokenId — and on a
> successful check it records the passcode STRING in a shared cache via
> recordSignatureVerification(passcode). Because the cache is keyed on the
> passcode string alone (SignatureVerificationCache.hasSignatureBeenVerified /
> verifiedTokens.getIfPresent at :96, populated by verifiedTokens.put(passcode,
> true) at :105), a client that has once verified its own passcode can then
> present that same passcode paired with a different, victim token identifier:
> hasSignatureBeenVerified(passcode) returns true, the tokenId-binding MAC
> check is skipped entirely, and the authenticated subject is built from the
> victim tokenId via createSubjectFromTokenIdentifier (:361). Token identifiers
> are not secret — they are returned in token-generation responses, are
> enumerable via the getUserTokens endpoint, and equal the public client_id in
> the client-credentials flow — so a low-privilege KnoxToken holder can
> authenticate as any other still-valid token's principal, which is an
> identity-assertion / authentication bypass, the keystone property a perimeter
> gateway is meant to uphold. The attached PoC drives the real
> AbstractJWTFilter.validateToken + real SignatureVerificationCache + real
> TokenMAC: after seeding the cache with (tokenA, PASSCODE_A), the call
> validateToken(tokenB, PASSCODE_A) returns true and
> createSubjectFromTokenIdentifier(tokenB) yields "bob" (tokenB's owner), while
> the same call against an empty cache returns false (the real MAC check
> rejects it).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)