$ cat writeup.md…
$ cat writeup.md…
asisctf2026
Task: a custom QuickJS-NG service executes attacker-supplied scripts with modules disabled, but adds a buggy Array.prototype.customFilter implementation. Solution: exploit its refcount underflow to forge a fake Float64Array, gain arbitrary read/write, leak libc, and redirect js_free to system('/readflag').
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Need a custom Filter? Please be my guest.
https://asisctf.com/tasks/QFilter_c9b8d86ebc59fcd1d7b9bf68ce3d9cb7b0f0f476.txznc 65.109.208.46 1337English summary: the remote wrapper accepts JavaScript until -- EOF --, then runs timeout 3 ./qjs on a custom QuickJS-NG 0.16.2 build. The goal is to escape the restricted runtime and make the SUID /readflag helper print the flag.
The service is a socat/Python wrapper around a custom qjs binary. Important environment facts:
-- EOF -- and executed with timeout 3 ./qjsstd and os were compiled in, but the custom module loader always rejects importsgc() and a few harmless globals are exposed, so import-based escapes do not workThe real bug is the custom Array.prototype.customFilter added in quickjs.c. Its reconstructed logic was:
this is an arraylen = p->u.array.count and elements = p->u.array.u.valuesis_object only from elements[0].tag == JS_TAG_OBJECTval = elements[i], call JS_DupValue only if is_object, invoke the callback, then always JS_FreeValue(ctx, val)That creates a refcount bug: if the first element is not an object, later refcounted elements are freed without first taking an owning reference, even though the array still points to them. With an array like [1, victim], victim can be released while arr[1] remains as a dangling JSValue.
There is also a stale-pointer property because both len and elements are cached before the callback runs, but the final exploit only needed the refcount underflow path.
One subtle but critical nuance was that reading the dangling slot too early breaks reclaim reliability. Accessing an array element duplicates the stale JSValue, which increases the reused block's refcount and sabotages the intended free/reclaim sequence.
...
$ grep --similar