Sitemap
Dr.kasbr

Dr.kasbr

flagyard web-NOSJ

5 min readJan 20, 2026

--

Press enter or click to view image in full size
Welcome to our E-commerce website, I wish you enjoy the journey. 
- First Man Standing

Hi everyone this challenge stayed unsolved for a long time so in this writeup I’ll walk through the full idea i Hope you enjoy ; )

Press enter or click to view image in full size

The application exposes two roles:

Buyer == needs a valid invitation code to open the store and receive the flag
Seller == can generate buyer invitation codes

So first we must register a seller, then use seller to invite the buyer

When you register as Buyer

you gonna need Invitation

Press enter or click to view image in full size

So let’s register as seller to inv the buyer

Press enter or click to view image in full size

And here is the first trick which is we need active the seller

but When you check your req

Press enter or click to view image in full size
Press enter or click to view image in full size

The trick is the activated only accepts a boolean (true/false) and you can’t set it to true

we can bybass this by duplicate keys using Unicode truncation:

Press enter or click to view image in full size

Now the seller account becomes active and you can access the seller dashboard

Press enter or click to view image in full size

let’s inv our Buyer account

Press enter or click to view image in full size

Back to burp to see the req

Press enter or click to view image in full size

ok now we have the inv code

Press enter or click to view image in full size

Invitation submitted! The invitation is still new and upcoming, we’ll contact you once this store is open.

So until now the idea is clear we just need tp open the store by find inv code open it for us

moving into the next step

The endpoint /get_buyer_invitation accepts a list of names. When the list contains repeated values, the response returns a list of sequential invitation codes (one per element)

Press enter or click to view image in full size

so i used

{"name": [NaN, NaN, NaN,
NaN, NaN, NaN,]}

To dump all codes

Get Dr.kasbr’s stories in your inbox

Join Medium for free to get updates from this writer.

Soo the idea now is trying all these codes and see if anything gonna happen

but yah nothing worked i even checked the formatting/lengt for every code but it’s wan’t the same every code have random number

Press enter or click to view image in full size

However, after restarting the instance multiple times and re-dumping, a clear structure appeared:

- There are always 800 codes
- The codes are sequential and deterministic for the instance
- The code values are in the 0..2^32-1 range
- no values outside unsigned 32‑bit range

That lines up strongly with a PRNG-backed generation mechanism rather than “real randomness”.

If we go back to the description:

Welcome to our E-commerce website, I wish you enjoy the journey.

- First Man Standing

So if you think about it, it’s not really random https://en.wikipedia.org/wiki/Mersenne_Twister

Since we have 800 outputs, we can recover the state using MT19937

MT19937 facts:

  • Once the state is known, you can predict future outputs
  • You can also rewind to previous outputs

Confirming MT19937

Press enter or click to view image in full size
1) Submit the first 624 values to an MT19937 state recovery tool
2) Predict the next few outputs and compare to the dump.
3) If predictions match, the stream is confirmed as MT19937.

This check succeeds:
predicted outputs match the dumped list, so the stream is confirmed

So

We dumped 800 sequential outputs, but the valid invitation is earlier than this window. That means we must rewind the PRNG.

Rewind math

After submitting 624 outputs, the recovery tool’s internal cursor sits at output index 624 of the observed stream. To generate the previous 800 values, we offset by:

-(624 + 800)
sage: rc = RandCrack()
....: for v in outputs[:624]:
....: rc.submit(v)
....: rc.offset(-(624 + 800))
....: prev_outputs = [rc.predict_getrandbits(32) for _ in range(800)]
sage: print(prev_outputs)
[2367117681, 42282097, 3934616742, 821412120, 1012857204, 1418681078, etc...

This yields the 800 earlier outputs in the stream.

Soo was a dead end for me at first, because based on the hint

 First Man Standing

i thought the whole idea was submit the first code was generated

┌──(oxfun㉿0xfun)-[~/Desktop/new_ctf/flagyard/NOSJ]
└─$ python3 solve_nosj.py
[*] got 800 codes. first: 4214217566
[*] rewound 800 codes. first: 2426950288

So it wasn’t exactly what I expected. The real idea was: submit all the recovered codes, and one of them will be the right invitation

┌──(oxfun㉿0xfun)-[~/Desktop/new_ctf/flagyard/NOSJ]
└─$ python3 solve_nosj.py
[*] got 800 codes. first: 4214217566
[*] rewound 800 codes. first: 2426950288
[?] interesting response at 786: Your flag is: FlagY{x}
here is the right code 2908459947

And yeah that’s it ; )

--

--