Cracking the Picnic Easter Egg
It started, as all good treasure hunts do, with a clue.
Step 1: The first clue
Not behind lock and key,
an easter egg cloaked in mystery.
Go global and you shall see,
behind the picture of our company,
set the Picnic easter egg free!
A few things jumped out: "lock and key", "cloaked" - this must mean Keycloak. "Go global" pointed to the global environment we have. And "behind the picture of our company" — the Picnic logo shown on the login page.
Step 2: Finding the image
I headed to the login page expecting to find an easter-themed logo. Instead, I saw the same old normal Picnic logo. Nothing in the source code, bummer. I tried other environments, nothing. Got frustrated and gave up.
Several days later I thought for sure all the prizes were already claimed, and then here's my team lead arriving with the first prize. What?! Nobody claimed the prizes yet? Okay, let's give it one more shot.
I took a look at the login page once again, still nothing. Okay, I'm 99% sure it's got something to do with the keycloak. Let's cheat. I started fishing through Picnic's Keycloak pull requests on GitHub. Sure enough, there it was — a PR that swapped in an easter-themed version of the logo PNG into the login form header. I grabbed the image directly from there: picnic-logo-easter-egg.png, a 240x240 RGB PNG of the Picnic logo.
Turns out the old login page was cached in my browser. Hard refresh revealed the picture there. 🤦♂️
Step 3: Poking at the pixels
The image looked innocent enough — the familiar red-and-white Picnic logo. But zooming in, the first three columns had subtle irregularities in their color.
Inspecting the pixels, last 2 bits from each RGB channel were all over the place. So I extracted those. Reading the pixels column by column: all 240 pixels of column 1, then column 2, then column 3, packed the bits into bytes, and voila, readable ASCII started pouring out.
The data was framed by a repeating marker string and contained two distinct sections.
Step 4: The plaintext
The first section was immediately readable:
115.249.186.21-2026-03-27T15:19:24+01:00
An IP address and an ISO 8601 timestamp. Tried opening the address in browser, it was some kind of a network device. Looked too real to continue poking around. Always gotta have a red herring in a treasure hunt.
Step 5: The actual payload
The second section started with 78 DA — the magic bytes for zlib-compressed data. Decompressing it revealed:
Hint: Open(my)ID
MIcUOTWSoru0G6/8zUPgyOx9HpKtfvD7irB/qoFv6aTCr1YRM7CxP9Rf5xrYD9l7y/YVDYSdfoeXvmhaX1iRexSFEhEDKDC9w5WVFBLThkFKNNEqmC554hE5HpDE4IAxaP+48dVeMdWs+24UR9B7gFCrTdxIjM1BczSW3PJVVa6C0t3W74vat45c17WoJETjoEeqrXy7BP8stZyYCPeM2cRc3ywOnqJmWoRFZSC0xM/0wOxQMpOGgEf33tfGlNzvOdXLAxDC0ji99LbwM3FUgigWsA5+M9M5zlW/109mMxayZADUNlE+isrY7WZQ5ARTCAIyl5hE7ePvb7gqTltDQw==
A hint and a large base64 blob. The blob decoded to exactly 256 bytes: 2048 bits, the size of an RSA-2048 block. Now to find the key.
Step 6: The key
The hint — "Open(my)ID" — with its deliberate parentheses, pointed toward OpenID Connect. Keycloak again? Tried Picnic's Keycloak JWKS endpoint.
This endpoint exposes two RSA public keys. Tried running through RSA with each and one resulted in readable output. The blob was signed with the corresponding private key — the inverse of encryption. That means anyone with the public key can recover the original message by reversing the operation.
The result was a textbook PKCS#1 v1.5 signature block:
0x00 0x01 0xFF FF FF ... FF 0x00 <message>
And the message?
Contact John Doe over Slack and ask him what his favourite chocolate is for your chance to claim a prize!
Step 5.5: The clue I missed
Turns out there was a second clue that dropped while I was still stuck on finding the image:
Good work so far, if you are an early bird
A picture is worth a thousand words; one is the passwordPeel the logo as if it were an onion, one layer at a time
There are many tools to do that, but only one works fineOnce you find the secret message within,
Call upon Rivest, Shamir and Adleman to help get in!
I didn't see it until after I'd already solved the whole thing. Would've saved me some head-scratching — "peel the logo as if it were an onion" is basically spelling out steganography, and "Rivest, Shamir and Adleman" is literally RSA. Oh well.
To sum up
- The first clue leads to the logo PNG on Keycloak
- Steganography hides the data in LSBs of pixels of an ordinary PNG
- Compression keeps the payload small enough to fit in just 3 columns of pixels
- The "Open(my)ID" hint brings you back to Keycloak — full circle — for the public key
- RSA signing encrypts the message
- The message ties it back to the real world
Pretty cool.
Names and addresses are redacted.