I recently got a talk accepted by a security conference, with an HTML <marquee> in my title. That led me to discover that a crafted talk title could take over the session of any logged-in organiser on the event platform, letting an attacker act in their admin portal and silently add themselves as an owner of the organisation. All it takes is a logged-in organiser opening a crafted, but not obviously suspicious, link.

The conference agenda was managed through aanmelder.nl, a Dutch event registration platform. They rendered my HTML not only in the acceptance email, but also on www.aanmelder.nl itself, through their view online link. That’s the same origin as aanmelder.nl’s admin dashboard for every tenant and user, which is exactly what the attack needed.

Extra fun: the conference was the ONE Conference, the Dutch government’s annual cybersecurity event, or, as they brand it: Europe’s prime cybersecurity event. My talk? Placing HTML in unusual places to get a foothold for exploits.

After reporting to aanmelder.nl, they confirmed and fixed it within one day.

My entry point: a talk title

A ONE Conference speaker badge in a lanyard holder, with the name line reading `Sasha <marquee><img src='//s42.re/oc.png'>` and a Speaker ribbon along the bottom.

My proposed title was “Strange <marquee> vectors, serious infrastructure impact”, so it contained a real <marquee> tag. I use <marquee> as a canary: it’s rarely used but still renders in modern browsers, and it’s funny. If I see parts of the page moving around, my inputs were not properly escaped.

I don’t usually risk this in talk titles, in case I break the Call for Papers form and miss my chance. But I made an exception for ONE Conference, which paid off.

First, the speaker upload portal, where I had to upload my slides. This was a separate vendor, SlideCrew, that the conference uses for slide collection:

I could not get enough access to this portal to confirm anything beyond a cosmetic joke.

Second, the acceptance email, which had a bigger impact. Mail clients are used to hostile HTML, so the scrolling title in an email is funny but not a security risk - on its own:

The interesting part is the link at the top: “Is this mail not displayed properly? Click here to view it online.” That link renders the mailing as a web page on www.aanmelder.nl, with my HTML.

So through a legitimate path, submitting a talk and being accepted, I could plant HTML on www.aanmelder.nl. I expect this would work for talk rejection notifications as well.

From marquee to javascript

A marquee proves my HTML is not being escaped, but that does not always mean javascript will run. To check, I made a trial account of my own and sent a test mailing to myself, with an alert(1) payload in my attendee’s first and last name fields.

Here is the view online link of that test mailing, on my own tenant subdomain:

That confirms javascript execution, not just HTML. But it ran on my own tenant subdomain, 179534.aanmelder.nl, not on www. A subdomain is a separate origin, so a payload there is limited by the same-origin policy. Could I get not only a marquee, but my own javascript, running on www itself?

Admin access from www.aanmelder.nl

The platform’s admin portal, where organisers manage their events, users and registrations, runs on www.aanmelder.nl, so to be most effective, I’d need to get my payload on that domain. Otherwise, CORS protections will interfere. But this seemed possible, as the view online link from my ONE Conference mail did render on www.

Aanmelder.nl later confirmed that new customers are on a different subdomain by default, but are moved to www once vetted and approved. I don’t know what the vetting process looks like. But the handful of previous emails I had from aanmelder.nl events all rendered on www.

The platform confirmed that the injection was not specific to my trial tenant: it would have executed on www as well. The escaping bug lived in email content rendering, where user content (from the organiser or an attendee/speaker) was escaped incorrectly or not at all.

This is a pattern I’ve been exploring before: low-stakes attack surface (here, the web view of a mail) sharing an origin with the admin portal. That’s how “haha funny scrolling text” becomes “account takeover”.

HttpOnly and other mitigations didn’t help

The session cookies on aanmelder.nl, user_key and session, are marked HttpOnly. I can’t read them from my javascript payload. However, for same-origin requests (and sometimes same-site), I can trigger HTTP requests, where the browser will include those cookies to authenticate the request. I can’t steal the secrets, but I can use them to perform actions.

The admin portal does its real work over a websocket. It’s a javascript app talking to the server over a Socket.IO connection, with a framework its own code calls jelements. It’s a rather convoluted interface, with blobs of HTML sent back and forth, tagged with random IDs. Actions aren’t regular HTTP POSTs, they’re “signals” sent down the socket. Setting the role to Owner while adding a user, for example, means sending a message like:

42["signal",{"signal":"change","id":"x2..9a",
  "page":"x5..15",
  "cookie_token":"QetxAjd...Gc.","rpc_count":4,
  "value":"organisation_owner"}]

Nonetheless, the XSS payload was able to add a new owner to the organisation. There was no re-authentication required, and the current owners would not be notified.

A full attack

End to end, it looks like this:

  1. Get content rendered on www. Either be an approved customer, or, more sneaky, get a legitimate approved event to send a mailing containing your payload. My own acceptance email was an example of exactly that: a genuine www.aanmelder.nl link, carrying my title’s HTML.
  2. An organiser opens the link while logged in. The payload runs on www, the admin origin.
  3. The payload finds the organiser’s organisation from the /en/login redirect, then fetch()es that admin page. The browser attaches the authentication cookies to requests, and the response body is readable. From it, the payload can read all further required tokens, IDs, and the websocket path.
  4. It opens the app’s websocket to that endpoint. Again, the browser attaches the user’s session cookies.
  5. It drives the Add user dialog over the socket, creating a new user with the Owner role.
  6. The attacker now holds their own owner account on the victim’s organisation.

Almost certainly, the attacker can take other actions, but adding an owner is the most interesting, because that access outlives the session and can be used at any later time. It’s too messy to share, but I have built and verified the full payload.

Aanmelder.nl is a significant platform: they say they’ve handled over 100.000 events. The same malicious link would have worked for each of these events.

Fixes

aanmelder.nl deployed two of my recommended fixes the same day:

  • Escaping the user content in the mail rendering.
  • Restricting the Content-Security-Policy, to limit both script execution and the connections a script can make. They had a CSP set already, but it was very open, allowing inline javascript and connections to external servers. CSP is an important layer here: it usually cannot be circumvented and will cause the browser to refuse payload execution. It can even be set per response, so the CSP on the email rendering page now allows almost nothing.

I also recommended, for defence in depth, to separate tenants from each other and especially from the admin interface. Sharing an origin between most tenants and the admin portal is what allowed this to escalate. A safer approach is a different domain for user content like email renderings, ideally with separate hostnames per tenant. This on its own would have prevented the escalation to the admin portal: default browser behaviour would not have allowed this cross-origin.

Disclosure

I reported this by email to security@aanmelder.nl on September 24, 2026. Their security policy was struck through, but they did respond quickly. As I felt I had to include this in my ONE Conference presentation, I gave them a deadline of 10 days.

As their bug bounty program is paused, there was no bounty. I thought that cross-tenant account takeover might be worth making an exception for, but given they top out at just € 100 for a “high” severity finding, it wasn’t really worth it anyways.

Prior work

In April 2026, Elad Meged discovered CVE-2026-41241, a high-severity stored XSS in pretalx, an open source CFP tool. That one was in the organiser’s talk search. I had explored pretalx before for this kind of vulnerability, but didn’t spot this one. He found it, like I found mine, while preparing a talk submission.

I delivered a talk about putting HTML where it isn’t expected and the consequences it can have, and the submission itself ended up as one of my demos :)

Other similar cases I published


I'm Sasha Romijn, an independent developer and internet infrastructure specialist in Amsterdam. I find security issues in internet infrastructure, usually in configurations, trust relationships, and cross-system behaviour that got overlooked. Some of my finds include rooting OpenWrt via SSID, taking down a European network with a TLS certificate, rooting Home Assistant through MeshCore, and, some years back, compromising Apple keychain access groups.

Find me on Mastodon, Bluesky, or work with me.