Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.restassured.response.Response;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -154,14 +155,22 @@ private static String userInfoIdentity(Map<String, String> cookies, String origi
}

/**
* Flips the final character of the sealed value, which sits inside the authentication tag.
* Flips a bit inside the trailing 16-byte GCM authentication tag.
* <p>
* The mutation is applied to the <em>decoded</em> bytes rather than to the final base64url
* character, because editing that character is not reliably a mutation at all: when the sealed
* length is not a multiple of three, the final character carries 2 or 4 unused padding bits that
* {@link Base64#getUrlDecoder()} discards without validating. Flipping only those bits — which is
* what {@code 'A' -> 'B'} does — re-decodes to the identical ciphertext and tag, so the peer
* unseals it successfully and answers 200. Mutating a decoded tag byte is unambiguous.
*
* @param sealed the sealed cookie value
* @return the tampered value, still in the base64url alphabet
*/
private static String tamper(String sealed) {
assertNotNull(sealed, "the login must establish a sealed session cookie");
char last = sealed.charAt(sealed.length() - 1);
return sealed.substring(0, sealed.length() - 1) + (last == 'A' ? 'B' : 'A');
byte[] raw = Base64.getUrlDecoder().decode(sealed);
raw[raw.length - 1] ^= 0x01;
return Base64.getUrlEncoder().withoutPadding().encodeToString(raw);
}
}
Loading