webfreehard

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/
direct_server_action_invocationprisma_filter_injectionboolean_oracle_extractionprefix_bruteforce

$ 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: 60a1ddfeb092ed787fb44362a27b61cff6e945d3d2
  • verifyInsider: 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-28NOV
  • side = NO
  • contractPrice = 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