live-signal
umdctf
Task: a Next.js app exposed server actions that accepted Prisma-style filters, letting attacker-controlled nested relation queries inspect hidden analyst signals. Solution: turn the injected filter into a boolean oracle, recover the unpublished signal fields and HMAC prefix-by-prefix, then submit them to the verification action for the flag.
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
live-signal — UMDCTF
Description
Original organizer description was not available in the saved notes.
English summary: the application exposed two Next.js server actions. One leaked public signals and their HMACs, and the other checked whether we knew a hidden insider signal. The bug was that the analyst handle filter was Prisma-style injectable, so nested relation predicates could be turned into a blind boolean oracle over unpublished data.
Analysis
Two action identifiers were reachable directly:
signalsByAnalyst:60a1ddfeb092ed787fb44362a27b61cff6e945d3d2verifyInsider:783c22fa5f432da054e7f973cb1a827a956078d769
Calling signalsByAnalyst returned public signals and their HMACs, which already showed how the application modeled analyst signal data. The more important issue was the handle filter: instead of being treated as a plain string, it behaved like a Prisma where object.
That meant queries such as nested signals.some.{ ... } were accepted. If a predicate matched at least one analyst row, the action response changed, giving a clean boolean oracle.
Using this oracle against analyst quant_sable, it was possible to confirm that a hidden future signal existed with:
publishedAt > 2026-04-25T00:00:00.000Z
Then the unpublished fields were extracted with repeated true/false probes:
ticker = KXELECTION-28NOVside = NOcontractPrice = 41
Finally, the same nested filter technique was used on the hidden signalHmac field with a prefix search:
signalHmac: { startsWith: prefix }
Recovering it nibble-by-nibble gave:
fc434bd5283de7356831a82e8838632c
At that point, verifyInsider could be called with the reconstructed tuple and returned the flag.
Solution
...
$ grep --similar
Similar writeups
- [web][free]open-insight— umdctf
- [web][free]umdmarket— umdctf
- [web][free]funikuler-vragam-kubani— alfactf
- [forensics][Pro]awk...wardddd ✂️— bluehensctf
- [network][free]security-breach-ruin— umdctf