运营 stealmybitcoin.com 的观察

他把 500 美元的比特币放在一台 PHP 服务器上,把链接发到 Hacker News。数以万计的人试图拿走它,只有一个人拿到了 flag,而他找到的密钥打不开钱包。

中文
复制
挑战页面的截图:左侧写着 Steal my Bitcoin,以及「22.68 hours without a successful hack」,说明服务器上藏着一把价值约 2,023 美元的比特币密钥;右侧是挑战钱包卡片,显示 0.02510313 BTC 和实时美元估值

上周,我在《纽约时报》网站首页看到一篇文章,报道称 PHP 中发现了一个严重漏洞。这篇文章以及其他类似报道声称,攻击者只需发送几个未经身份验证的 Web 请求,利用 PHP2Shell 漏洞,就能在全球数以百万计正在运行的 PHP 服务上实现任意远程代码执行。

我办公室的 Slack 炸开了锅,我和安全圈同事、朋友们的几个 Signal 聊天群也一样。

传言四起:有说某科技公司因为在野利用而打破禁令,抢在社区协调披露前数小时泄露了漏洞消息;有说黑客早已在野利用这个漏洞攻击网站,每一小时都至关重要,因为补丁要覆盖互联网 70% 的范围。

问题

PHP2Shell 是不是标志着一个新时代,被 AI 加持的随便什么人,只要给模型写几句精心措辞的提示词,就能黑进任何网站?

我望向窗外,本以为会看到地平线上火光映出的末日余晖,社会正在实时崩塌。什么也没有。

网上文章和推文铺天盖地,渲染着十级警报式的网络末日,我却找不到任何一处提到某个具体网站被黑。

接下来几天,少数几个不随大流的人指出,这个漏洞在实际操作中相当棘手(Elias Taylor;Lucas Luitjes),黑客组织起初对它的潜力颇为兴奋,后来也开始觉得它不实用、难利用(KELA)。

但与此同时,国内外媒体仍在连篇累牍地报道一场艰巨的应急行动:在全网范围内排查并修补基于 PHP 的网站。

与其干等着瞎猜,我决定自己去弄清楚。我有两个根本性的问题:

  1. PHP2Shell 在实验室环境之外真的可利用吗?
  2. 相对缺乏技能、但能用上 AI 和 PHP 源码的攻击者,能利用这一类漏洞吗?

结果

直接回答这两个问题:

1) PHP2Shell 在实验室环境之外真的可利用吗?

是。 毫无疑问。挑战的获胜者发表了一篇复盘,他通过公网连接直接攻击了我的服务器,用的正是 PHP2Shell 漏洞(也就是说,他没有利用我配置里的其他漏洞)。他构造了一个专门的 exploit,针对的是我这套 PHP 安装的具体配置以及它在服务器上的部署方式。

2) 手上有人工智能和 PHP 源码、技术不算高明的攻击者,能利用这一类漏洞吗?

目前还不能。 尝试利用的人数以万计。第一次成功攻破之后,挑战又开放了 9 个小时(下面会细说),期间只有两人成功触发了 print-flag 二进制,而只有一人,也就是挑战的获胜者,成功从中提取出了应用里的有效内容。

实验

核心思路很简单。租一台便宜的 VPS 用几周,在上面放一个 PHP 网站,再放一把比特币钱包的私钥,然后等着看有没有人能把钱包掏空。奇迹般地,“stealmybitcoin.com”这个域名还没被注册,我便把这当作上天的旨意,认定这项实验奉天承运。

我往一个干净的钱包里放了 500 美元的比特币,从一家主机商租了一台每月 2.50 美元的 Debian VPS,让 Codex 给我写一个基础网页,用 PHP 挂到网上。我把服务器配置成只有一个可执行二进制 print-flag 有权限读取包含 WIF 的 flag 文件,关掉了防火墙,然后把链接发到 Hacker News,看看有没有人愿意来试。

挑战落地页截图

之后事情就有点失控了。发帖后不到一小时,链接就冲上了 Hacker News 首页(基本挂了一整天),我那台每月 2.50 美元的 VPS 负载高到我连 SSH 会话都维持不住。

我最终把 VPS 从 1 vCPU、500 MB 内存、10GB 磁盘一路升到了 2 vCPU、4096 MB 内存、80 GB SSD。挑战期间,VPS 服务商曾两次临时关停我的站点:一次是怀疑它遭到分布式拒绝服务攻击,另一次则是因为“我们认定你的 CPU 资源占用情况过高”。

另外,由于这个漏洞利用本身的性质,不断有人因为请求量过高、部分成功的内存利用尝试以及各种乱七八糟的操作,把单个 PHP worker 打挂。

最后我总算把局面弄到了相对稳定的状态(比如大多数人大部分时间都能连上网站),用定期重置和健康检查顶替了真正的高可用托管。链接从 Hacker News 首页掉下去之后,流量也安静了不少。

一些数据

由于监控不稳定,很难拿到有代表性的访客数和流量模式统计。我搭了一套相当可靠的日志,用来知道 print-flag 二进制什么时候被调用,另外尽力记录了 Web 请求头和请求体,希望能借此搞清那段时间前后发生了什么;在走红的最初几个小时里,由于站点受到的关注,我大约每 4 分钟就会写满一次 logrotate 配置(约 80MB 存储空间)。

因此,某些 PHP 崩溃的日志以及 print-flag 文件相关的活动记录,在挑战全程基本是完整的。更宽泛的 Web 请求日志只覆盖了挑战约 1,600 分钟时长中的 600 分钟左右。

在有可靠 Web 请求日志的那 10 个小时里:

  • 约 20,000 个独立 IP 以某种实质方式参与了挑战(而不只是访问首页)。
  • 约 120,000 个不同来源 IP 访问了站点。
  • 服务器收到了超过 1300 万 次 HTTP 请求。
  • 峰值负载时,服务器每分钟收到超过 18,000 次请求。

缺失的 netflow 数据无法支撑统计上可信的外推,但我可以肯定地说:有数万名玩家试图偷走我的比特币。

PHP 服务器错误日志和 print-flag 的执行日志要可靠得多,因为它们在负载很高时也没有被轮转,而且体量相对不大。在这些日志里,我观察了大约 26 个小时,发现:

  • 约 2,075 次不同的 PHP SIGSEGV 事件。这些事件也可能由 PHP 的其他 bug 引起,但通常你不会预期看到它们,它们大致可能与更接近触发漏洞的尝试相关。
  • 只有 两个 IP 成功触发了 print-flag 可执行文件,并且只有 一个人 以成功从服务器窃取数据的方式做到了这一点。

时间线

UTC 日期和时间事件
10 月 24 日 20:30挑战计时开始。
10 月 24 日 20:30我发布“Show HN: Steal My Bitcoin”(https://news.ycombinator.com/item?id=50224916)。
10 月 25 日 01:14挑战计时开始后首次记录到 PHP SIGSEGV。注意:这早于 php-version.php,在这个最初版本的挑战上随后还发生了七次 PHP SIGSEGV。
10 月 25 日 12:59我添加 php-version.php,它返回 PHP 版本和 UTC 时间。
10 月 25 日 15:44我添加 ping.php,一个小型 POST 端点,对 message=ping 返回 pong。
10 月 25 日 15:47一个 PHP 进程成功运行 print-flag。这次执行与最终的获胜者相关。ping.php 页面并未在漏洞利用链中使用。
10 月 25 日 20:32我收到获胜者的邮件,问我服务器上密钥对应的比特币钱包地址是不是写错了。获胜者提供了与上述 15:47 事件对应的确切 flag、时间戳、IP 地址和 payload,以及一份描述获胜漏洞利用的 writeup。
10 月 25 日 21:06我对照服务器日志核查获胜者的说法,确认 flag 与服务器上的 flag 完全一致,并且与成功执行前后的 Web 流量日志相符。
10 月 25 日 22:38我在 Hacker News 上发布解题公告(https://news.ycombinator.com/item?id=50236736)。
10 月 25 日 ~22:59我审阅获胜者的最终 writeup,并直接转出钱包里的内容。

过程中的困难

我原本以为这会是个流量很低的蜜罐。几个人在 Hacker News 上戳一戳,最后被某个僵尸网络、扫描器或者自动化漏洞利用工具盯上,把我的比特币偷走。然后我写一篇小博客,贴一下漏洞被利用前后那一个小时左右的日志,就此收工。

我完全没料到它会以那种方式火起来。火起来是好事。很多人看到了这个挑战,我的研究问题很快有了答案,甚至还有人往里投钱,把比特币奖池的金额加大了。

火起来也有难受的地方:突然之间,一大堆真正的 PHP 安全专家(以及许多非专家)对我本人、我的动机和我的网站管理能力都有了看法!

后端应该做什么?

第一个批评是服务器根本无法被利用。有人说它实际上就是一个 nginx 页面(比如根本没有 PHP),也有人说服务器的具体配置让漏洞利用不可能成功。

这些说法到底有多准确,我到现在也不是 100% 确定,因为有些人在挑战的原始版本上就已经能触发 SIGSEGV 了。

不过我确实对网站做了几处修改,我认为这些调整是合理的,能提高挑战的可行性,同时又不是刻意把服务器弄成有漏洞的样子。

首先,我加了一个 php-version.php 页面,和原本由 PHP 托管的 index.html 页面一起提供,以防漏洞利用需要 PHP 解释器真正参与。虽然早期一些关于 PHP2Shell 的报道暗示这可能并不是利用的硬性前提(例如 https://www.wiz.io/blog/php2shell-php-rce:“利用并不依赖应用代码中的缺陷:即使是一个跑在受影响 PHP 版本上的简单‘Hello World’应用也可能存在漏洞。”),但在我看来,PHP SAPI 服务器中负责提供 .html 文件的那部分有可能不受这些任务影响。

我还加了一个 ping.php 页面:只要有人向服务器 POST 值 ping,它就返回 pong 这个词。加这个极简应用,是考虑到漏洞利用可能需要 PHP 动态响应用户提供的输入。在我看来,这两项要求(响应用户输入、运行 .php 页面)在真实应用中相当普遍,因此为这些代码路径提供支持并没有违背这次挑战的本意。

还有一些其他反馈,我基本没理会,它们要求提供 PHP 应用的具体变体,或者修改我放在网站前面的(非常基础的)nginx 代理配置,那套配置用于辅助路由、隔离挑战和记录日志。如果我按这个方向再做些修改,或许会有更多人解出挑战。但没有具体证据能证明会这样。

比特币密钥到底是什么?

第二个主要的托管问题,用一段视频说明更清楚。下面是我导出挑战中所用比特币钱包 WIF 私钥的过程(注意:这里展示的是另一个空钱包。请不要使用这个密钥,因为我已经把它录成视频发到网上了!):

视频:导出钱包的 WIF 私钥

我以为自己是按这个流程把钱包密钥放进了挑战里。结果并不是。我导出的 WIF 其实是持有比特币的那个钱包地址派生路径上的一个父密钥。我至今仍不完全确定,我本应提供哪一组凭据才能让比特币可被窃取。如果以后再办这类挑战,我大概会把 flag 设为完整的钱包恢复密钥,而不是某个单独的私钥。

所以获胜者从服务器上拿到了真正的比特币密钥,却无法用它动那笔钱,钱在由该父密钥派生出的分支钱包里。

好在他们通过邮件联系了我,并提供了极其详细的证明,包括:服务器上钱包密钥的全文、它的哈希、他们利用服务器时的精确时间戳、利用时使用的确切 IP 地址、利用时使用的确切 payload、开发漏洞利用过程中的 AI 对话记录,以及一份完整的漏洞利用说明。

有了这些信息,我在日志里找到了完整的记录,把攻击时间与 print-flag 二进制文件上的日志精确到秒地对上,确认他们确实知道服务器上 flag 文件的完整值。

恭喜获胜者,不仅解出了挑战,还解出了那个隐藏的附加挑战:保留出色的漏洞利用日志!

结论

那么,我从这次经历中学到了什么?

我上线这个网站,多少带着怀疑:关于一个严重的 PHP CVE,报道铺天盖地,现实中却几乎找不到真正的受害者。

说实话,我认为那是我的一次误判。PHP2Shell 是真家伙。这是一个可利用的漏洞,而一个动机足够强的对手,在 AI 的帮助下,即使拿不到内存地址、也接触不到物理服务器来定制攻击路径,也完全可以用它黑掉一个网站。

如果你现在运营着一个基于 PHP 的网站,绝对应该给 PHP2Shell 打补丁。

我完全预计,未来几个月会出现越来越多可用的 PHP2Shell 通用漏洞利用链。

如果你运营的服务价值足够高、或者足够公开,值得别人专门花心思,那么我在 stealmybitcoin.com 上的经历让我相信:有人针对你的系统配置专门定制一条可用的 PHP2Shell 漏洞利用,只是时间问题,而不是会不会发生的问题。

反过来,我认为 stealmybitcoin.com 也很能说明 AI 辅助网络攻击的现状。数以万计的人试图攻击一个挂着公开已知(且可利用!)CVE 的网站,只有两个人接近触发这个漏洞。

我为方便利用而做的一些服务端改动可能起了作用(比如获胜者是在我把 php-version.php 加到服务器上大约 3 小时后解出挑战的),但在获胜者成功解出到我下线挑战之间的约 9 个小时里,数以万计的攻击者都没能触发 flag 二进制文件。

很清楚,现在还远没到那种地步,水平一般的攻击者拿上一个顶级 LLM,再配上一个难啃的 CVE(比如 PHP2Shell 这种),就能把它变成可靠的网络攻击工具。

不过,我们确实每天都在逼近那个节点。我不认为有什么办法能把这只猫重新塞回袋子里,但我希望未来几个月里,开源社区和系统安全生态的共同努力能让难啃的漏洞依然难啃。

我职业生涯中有相当一部分时间在做攻击性安全研究,在我看来,PHP2Shell 又一次说明了:让好人能快速、公开地找到漏洞,好让所有人抢先打上补丁,这件事的重要性从未像现在这样高。

来源: Pavursec← 返回首页