Patient Exporters is one of the challenges that was not solved by a lot of teams, so let’s explore the challenge.
*This blog will not be published in Dutch
Let's start at one of the hints that we got. One of the attachments for the challenge was that we got an old expired token from the V1. Let’s take a look at the token and the way this token was generated and signed:
eyJwaWQiOiAiUDAwNDIiLCJleHBpcmF0aW9uIjogMTc1NzQyMTQ0OTExN30=.9o16oqcvWYatAwLF5gf0dsPW3hke4fAC6mH/G9IBwKE=
Base64 decoding the first part before the . gives us:
and the valid (but expired) signature:
9o16oqcvWYatAwLF5gf0dsPW3hke4fAC6mH/G9IBwKE=
Now, let's look at the way the Auth V1 checks if it is valid:
What we know now, is that the Token/Payload[Tp] is encrypted by a PartnerKey/SharedKey [Pk] which creates a Signature [S].
Let's write that down so we don't forget:
HMAC SHA256(Pk + Tp) = S
Now, we take a look at the V2 authentication and the token that it uses. Let's start at the JSON_Payload in the module to see if anything has changed.
OK, so we have a new parameter called requestid that has a string value. The other parameters have remained the same.
Now, we look at the way it validates the signature:
So, first we create a sessionKey and then we create a signature. Let's explore what is used to create both.
In order for us to create a session key [S1], we need a PartnerKey/SharedKey[Pk] and a Payload/RequestId [Pr].. Hmm…
Let's look at the Signature creation:
Alright, so we use this newly created sessionkey [S1] to encrypt the token payload [Tp] to obtain the fresh signature. [S2]
Let's write all of this in the same way we did V1.
HMAC SHA256(Pk + Pr) = S1
HMAC SHA256(S1 + Tp) = S2
We'll start at the end of V2.
HMAC SHA256(S1 + Tp) = S2
In order for us to create a valid Signature [S2] we need to know the Token Payload and the Sessionkey [S1]. If we explore the microflow, we see that the Token Payload is the first part of the Bearer token (the part before the .) So we can influence this. If we know the Session Key, then we can create a valid signature.
Let's take a look at the Session Key.
HMAC SHA256(Pk + Pr) = S1
In order for us to create the Sessionkey [S1], we need to encrypt the Payload RequestId [Pr] with the partner Key [Pk]. The Payload Request Id, we can change, because it's in the payload of the token. The partner key however, is a mystery. All we know is that it was also used in the creation of the V1 token. Let's take a look the V1 signature again:
HMAC SHA256(Pk + Tp) = S
So, in the V1 token, the Token payload is encrypted with the partner key to generate a signature. If only we could reuse this token payload, we would be able to know exactly which signature it would generate and we would not need the partner key….
Oh wait, we can put any value in the Request id, right? So if we make Tp = Pr, then we know what the signature S1 will be. Therefore we know what the session key is going to be and we would be able to create S2 using that information.
Important note: the token we got in the challenge is expired because the exp value is too low, but the signature itself is still valid for the token! It's important here to distinguish between an expired token and an invalid token.
Creating the bearer token, we start with the payload part:
We use the payload of the expired token as the request ID. We then Base64 encode this entire payload and we get:
Now, if we simply use the HMAC SHA256 hashing algorithm and use the signature of the expired token to encrypt the payload. We get the following value:
Putting this together makes:
So, now we call:
https://patientportalctf2025.apps.eu-1c.mendixcloud.com/rest/patientexport/v2/patient/export?patient=31337
And we use the above as the authorization header, we get the following response:
Et voilà!