LuaRocks Security Incident September 2026
LuaRocks Security Incident September 2026
LuaRocks.org 2026年9月安全事件
LuaRocks.org Security Incident, September 2026. On September 25th, 2026 we received a report of a remote code execution vulnerability in LuaRocks.org, coordinated through CISA. The vulnerability was fixed on September 26th. While investigating, we found that it had been exploited on the LuaRocks.org server several times between July 9th and August 20th, 2026. Because an attacker was able to run code on the server, we are treating everything that server had access to as exposed. The site has been moved to a newly built server, and every credential the old server held has been revoked and replaced. We have not found any evidence that existing packages were modified. Details on what we checked are below.
2026年9月,LuaRocks.org 发生安全事件。2026年9月25日,我们通过 CISA 协调收到了一份关于 LuaRocks.org 远程代码执行漏洞的报告。该漏洞已于9月26日修复。在调查过程中,我们发现该漏洞在2026年7月9日至8月20日期间曾多次被利用。由于攻击者能够在服务器上运行代码,我们将该服务器有权访问的所有内容视为已泄露。目前网站已迁移至新构建的服务器,旧服务器持有的所有凭据均已被撤销并更换。我们尚未发现任何现有软件包被篡改的证据。以下是我们检查的详细信息。
What you should do
您应该采取的行动
Create a new API key. All API keys have been revoked. If you use luarocks upload, create a new key from your API keys page.
创建新的 API 密钥。所有 API 密钥均已被撤销。如果您使用 luarocks upload,请从您的 API 密钥页面创建一个新密钥。
Log in again. All sessions have been ended. Change your password, and change it anywhere else you’ve used the same password. Passwords are stored as bcrypt hashes, which are slow to crack, but the hashes should be considered exposed. 重新登录。所有会话均已结束。请更改您的密码,并在其他使用相同密码的地方也进行更改。密码以 bcrypt 哈希形式存储,虽然破解难度较大,但这些哈希应被视为已泄露。
If you had two-factor authentication enabled, set it up again. The stored 2FA secrets were exposed, so they have been removed. 如果您启用了双重身份验证 (2FA),请重新设置。存储的 2FA 密钥已泄露,因此已被删除。
Upgrade LuaRocks to 3.12 or newer, especially if you use LuaJIT or Lua 5.1. LuaRocks 3.11.1 and older load rockspecs and manifests with loadstring in the same way (see below), so on LuaJIT or Lua 5.1 they will run precompiled bytecode if a server sends it in place of a rockspec or manifest.
将 LuaRocks 升级到 3.12 或更高版本,特别是如果您使用 LuaJIT 或 Lua 5.1。LuaRocks 3.11.1 及更早版本使用 loadstring 加载 rockspec 和清单的方式相同(见下文),因此在 LuaJIT 或 Lua 5.1 上,如果服务器发送预编译的字节码来代替 rockspec 或清单,它们将会运行该字节码。
If you installed any of the packages bcrcewon, 7e0b94029db0 or 7e0b9402f9c8, treat that machine as compromised. These were uploaded by the attacker on August 7th and have been removed.
如果您安装了 bcrcewon、7e0b94029db0 或 7e0b9402f9c8 中的任何软件包,请将该机器视为已受感染。这些包由攻击者于8月7日上传,现已被删除。
Package maintainers: we recommend reviewing the recent versions of your packages from the Security Audit page. 软件包维护者:我们建议您通过“安全审计”页面查看您软件包的近期版本。
Description of the issue
问题描述
A rockspec is a Lua file. When one is uploaded, LuaRocks.org runs it to read fields like the package’s name and version. To do this safely, the site loads the file with loadstring, runs the resulting function with an empty environment (setfenv) so it can’t reach any globals, and limits how many instructions it can execute. LuaRocks.org runs on OpenResty, so this happens in LuaJIT.
Rockspec 是一个 Lua 文件。当文件上传时,LuaRocks.org 会运行它以读取软件包名称和版本等字段。为了安全起见,网站使用 loadstring 加载文件,并在空环境 (setfenv) 中运行生成的函数,使其无法访问任何全局变量,并限制其可执行的指令数量。LuaRocks.org 运行在 OpenResty 上,因此这是在 LuaJIT 中执行的。
The mistake was in how the file was loaded. In Lua 5.1 and LuaJIT, loadstring accepts two kinds of input by default: Lua source code, and precompiled bytecode (the output of luac or luajit -b, which starts with the byte \27). The rockspec parser only ever expected source code, but it never told loadstring to reject bytecode, so an uploaded “rockspec” could be bytecode instead.
错误在于文件的加载方式。在 Lua 5.1 和 LuaJIT 中,loadstring 默认接受两种输入:Lua 源代码和预编译的字节码(luac 或 luajit -b 的输出,以字节 \27 开头)。Rockspec 解析器只期望源代码,但从未指示 loadstring 拒绝字节码,因此上传的“rockspec”实际上可能是字节码。
Bytecode is not safe to load from an untrusted source. LuaJIT does not verify it at all, so a hand-crafted file can contain instructions that read and write outside the function’s own data, giving it access to arbitrary memory in the server process. The empty environment only controls which globals the code can look up, and bytecode like this doesn’t need any: it can find the real Lua state in memory and call the functions the sandbox was meant to hide, running arbitrary code inside the web server.
从不受信任的来源加载字节码是不安全的。LuaJIT 完全不进行验证,因此精心制作的文件可以包含在函数自身数据之外进行读写的指令,从而使其能够访问服务器进程中的任意内存。空环境仅控制代码可以查找哪些全局变量,而此类字节码根本不需要这些:它可以找到内存中的真实 Lua 状态并调用沙箱本应隐藏的函数,从而在 Web 服务器内运行任意代码。
Any registered user could trigger this by uploading a rockspec, either through the website or the API. The code responsible had been part of LuaRocks.org for a long time. The fix passes the “t” (text only) mode to loadstring, which LuaJIT supports, and also rejects any file starting with \27 outright, since PUC Lua 5.1 ignores the mode argument. The code that reads manifests from other servers had the same mistake and now also loads them as text only.
任何注册用户都可以通过网站或 API 上传 rockspec 来触发此漏洞。负责此功能的代码在 LuaRocks.org 中存在已久。修复方案是向 loadstring 传递 “t”(仅文本)模式(LuaJIT 支持该模式),并直接拒绝任何以 \27 开头的文件,因为 PUC Lua 5.1 会忽略模式参数。从其他服务器读取清单的代码也存在同样的错误,现在也已改为仅以文本方式加载。
What happened
事件经过
We found three accounts, created for this purpose, that exploited the issue: 我们发现了三个为此目的而创建的账户,它们利用了该漏洞:
| Date | Activity |
|---|---|
| July 9 | Shell commands were run on the server through malicious uploads |
| 7月9日 | 通过恶意上传在服务器上运行了 Shell 命令 |
| August 7 | Shell commands were run again, including an apparent attempt to open a remote shell. Three malicious packages were published |
| 8月7日 | 再次运行了 Shell 命令,包括明显的远程 Shell 开启尝试。发布了三个恶意软件包 |
| August 16, August 20 | Several hundred automated exploit attempts through the upload API, reusing the published payloads |
| 8月16日, 8月20日 | 通过上传 API 进行了数百次自动化漏洞利用尝试,重复使用了已发布的载荷 |
| September 25 | Vulnerability report received |
| 9月25日 | 收到漏洞报告 |
| September 26 | Issue fixed, site placed in read-only mode, attacker accounts suspended, malicious packages removed, all credentials revoked |
| 9月26日 | 问题修复,网站进入只读模式,攻击者账户被封禁,恶意软件包被删除,所有凭据被撤销 |
The account the web server ran as could gain full administrative access to the server, so we assume the attacker could read anything on it, including the entire database. Web 服务器运行所使用的账户可以获得服务器的完全管理权限,因此我们推测攻击者可以读取服务器上的任何内容,包括整个数据库。
What was exposed
泄露内容
Because a remote code execution took place, we assume anything on the machine was read by the attacker: 由于发生了远程代码执行,我们推测机器上的所有内容都被攻击者读取了:
- Account information: usernames, email addresses and bcrypt password hashes
- 账户信息:用户名、电子邮件地址和 bcrypt 密码哈希
- API keys (all revoked)
- API 密钥(均已撤销)
- Two-factor authentication secrets (all removed)
- 双重身份验证密钥(均已删除)
- Tokens from linking GitHub accounts. These only granted access to your GitHub profile and email address, and have all been revoked through GitHub
- 关联 GitHub 账户的令牌。这些令牌仅授予对您的 GitHub 个人资料和电子邮件地址的访问权限,并且已通过 GitHub 全部撤销
- Session records, account activity logs, and the IP addresses and browser details stored in them
- 会话记录、账户活动日志以及其中存储的 IP 地址和浏览器详细信息
- Server credentials for third-party services, which have all been revoked
- 第三方服务的服务器凭据,均已撤销
What we checked
我们的检查工作
Every day, LuaRocks.org copies the public manifest and every published rockspec and rock into a public git repository, rocks-moonscript-org/moonrocks-mirror (development versions go to moonrocks-dev-mirror). Each daily commit records exactly which published files were added, changed or removed, so the repository is a history of every change to the files LuaRocks.org serves, kept outside the server. That history gave us a copy from before the first attack to compare against.
LuaRocks.org 每天都会将公共清单以及每个已发布的 rockspec 和 rock 复制到一个公共 git 仓库 rocks-moonscript-org/moonrocks-mirror 中(开发版本存入 moonrocks-dev-mirror)。每天的提交记录了哪些已发布文件被添加、更改或删除,因此该仓库是 LuaRocks.org 所提供文件变更的历史记录,且存储在服务器之外。该历史记录为我们提供了第一次攻击前的数据副本以供比对。
After this analysis, we have found no evidence that any existing module was tampered with or replaced. 经过分析,我们没有发现任何现有模块被篡改或替换的证据。
- Every package file in storage was compared against the mirror from July 8th and against our database.
- 存储中的每个软件包文件都与7月8日的镜像及我们的数据库进行了比对。
- Every difference was explained by a normal upload, copy or deletion made through the site.
- 每一个差异都可以通过网站进行的正常上传、复制或删除操作来解释。
- Every upload between July 9th and September 26th was compared against the uploading account’s usual activity, and where possible against the package’s published source. We found no uploads made by anyone other than the account owner, apart from the three attacker accounts.
- 7月9日至9月26日期间的每一次上传都与上传账户的日常活动进行了比对,并在可能的情况下与软件包的发布源码进行了比对。除了那三个攻击者账户外,我们没有发现任何非账户所有者进行的上传操作。