LuaRocks Security Incident September 2026
LuaRocks 보안 사고: LuaJIT 바이트코드로 서버 원격 코드 실행
LuaRocks.org가 LuaJIT 바이트코드를 일반 Lua 소스처럼 불러오는 취약점으로 원격 코드 실행 공격을 받았습니다. 운영자는 서버를 교체하고 자격 증명을 폐기했으며, 기존 패키지 변조 흔적은 찾지 못했다고 밝혔습니다. 이용자는 API 키를 재발급하고 비밀번호와 2단계 인증을 다시 설정해야 합니다.
- 주제
AI 요약
LuaRocks.org는 2026년 9월 25일 CISA를 통해 원격 코드 실행 취약점 제보를 받았고, 다음 날 취약점을 수정했습니다. 조사 결과 공격자는 7월 9일부터 8월 20일 사이 취약점을 여러 차례 악용했습니다. 공격자가 서버에서 코드를 실행한 만큼 운영자는 서버가 접근할 수 있던 모든 정보가 노출됐다고 간주하고, 새 서버를 구축해 사이트를 이전했습니다.
취약점 원인
LuaRocks.org는 패키지의 이름과 버전을 확인하려고 업로드된 rockspec을 Lua 파일로 실행합니다. 제한된 환경을 설정하고 실행 명령 수를 제한했지만, 파일을 읽는 loadstring에 텍스트만 허용하는 옵션을 지정하지 않았습니다. Lua 5.1과 LuaJIT의 loadstring은 기본적으로 Lua 소스 코드와 미리 컴파일한 바이트코드를 모두 받아들입니다.
LuaJIT는 바이트코드의 안전성을 검증하지 않습니다. 공격자가 만든 바이트코드는 함수 내부 데이터 범위를 벗어나 서버 프로세스의 메모리를 읽거나 쓸 수 있습니다. 빈 환경으로 전역 함수 접근을 막아도 바이트코드는 메모리에서 실제 Lua 상태를 찾아 샌드박스를 우회할 수 있습니다. 따라서 등록된 계정이라면 웹사이트나 API에서 악성 rockspec을 올려 웹 서버 안에서 임의 코드를 실행할 수 있었습니다.
수정 코드는 loadstring에 텍스트만 받는 ‘t’ 모드를 전달하고, 바이트코드 시작 바이트인 \27로 시작하는 파일도 거부합니다. PUC Lua 5.1은 모드 인수를 무시하기 때문입니다. 다른 서버에서 manifest를 읽는 코드에도 같은 문제가 있어 텍스트만 불러오도록 바꿨습니다. LuaRocks 3.11.1 이하를 쓰는 이용자, 특히 LuaJIT나 Lua 5.1을 쓰는 경우 3.12 이상으로 업그레이드하라고 안내했습니다.
공격 경과와 노출 정보
운영자는 공격을 위해 만든 계정 세 개를 확인했습니다. 7월 9일 악성 업로드로 셸 명령이 실행됐고, 8월 7일에는 원격 셸을 열려 한 것으로 보이는 시도와 악성 패키지 세 건의 게시가 있었습니다. 8월 16일과 20일에는 업로드 API를 통해 공개된 페이로드를 재사용한 자동 공격 수백 건이 이어졌습니다. 웹 서버 계정은 서버의 전체 관리자 권한으로 올라갈 수 있었으므로, 운영자는 데이터베이스 전체를 포함해 서버의 모든 정보를 읽었을 가능성을 전제로 대응했습니다.
노출 가능성이 있는 정보는 사용자 이름과 이메일, bcrypt 비밀번호 해시, API 키, 2단계 인증 비밀값, GitHub 연동 토큰, 세션 기록과 계정 활동 로그, IP 주소와 브라우저 정보입니다. 제3자 서비스용 서버 자격 증명도 대상에 포함됩니다. 운영자는 API 키와 세션을 폐기했고, 2단계 인증 비밀값을 삭제했으며, GitHub 접근 토큰과 기존 서버 자격 증명도 모두 폐기하거나 교체했습니다.
이용자는 새 API 키를 발급하고 다시 로그인해야 합니다. 비밀번호를 바꾸고 같은 비밀번호를 다른 곳에서도 썼다면 그곳의 비밀번호도 바꿔야 합니다. 비밀번호는 bcrypt 해시로 저장했지만, 운영자는 해시가 노출됐다고 간주하라고 안내했습니다. 2단계 인증을 사용했다면 다시 설정해야 합니다. 공격자가 8월 7일 올린 bcrcewon, 7e0b94029db0, 7e0b9402f9c8 패키지 가운데 하나라도 설치했다면 해당 기기를 침해된 것으로 취급하라고 권고했습니다. 패키지 관리자는 Security Audit 페이지에서 최근 패키지 버전을 확인해야 합니다.
패키지 조사와 대응
LuaRocks.org는 매일 공개 manifest와 게시된 rockspec 및 rock 파일을 공개 Git 저장소인 rocks-moonscript-org/moonrocks-mirror에 복사합니다. 개발 버전은 moonrocks-dev-mirror에 저장합니다. 날짜별 커밋 기록을 공격 이전 시점과 대조해 패키지 파일과 업로드 기록을 조사했고, 기존 모듈이 변조되거나 교체된 흔적은 찾지 못했다고 밝혔습니다. 업로드 파일을 저장소의 7월 8일 미러 및 데이터베이스와 비교했고, 해당 기간의 업로드를 계정의 평소 활동과 가능한 경우 패키지 소스에 대조했습니다. 공격자 계정 외에는 계정 소유자가 아닌 사람이 올린 파일을 찾지 못했으며, 조사 기간에 올라온 rockspec 중 바이트코드를 포함한 파일도 공격자 패키지뿐이었습니다. 사이트 코드 변경, 데이터베이스 함수나 역할 추가, 서버에 남겨둔 지속성 장치도 발견하지 못했습니다.
다만 공격자가 삭제한 패키지는 소유자가 직접 삭제한 경우와 구분하기 어렵고, 침해된 서버가 이용자에게 실제로 보낸 응답 내용도 확인할 수 없다고 덧붙였습니다. 악성 rockspec은 mirror.luarocks.org와 GitHub 공개 미러에도 복사됐으며, 두 곳에서 모두 제거했습니다. 운영자는 취약점 수정과 테스트 추가, 공격자 계정 정지, 악성 패키지 삭제, 사이트의 일시적인 읽기 전용 전환을 진행했고, 기존 서버 대신 새 서버를 마련했습니다.
Lobsters 반응
- @technomancy — rockspec은 Lua 파일입니다. rockspec을 올리면 LuaRocks.org는 패키지 이름과 버전을 읽으려고 제한된 환경에서 실행합니다. 그런데 파일을 불러오는 함수가 Lua 소스뿐 아니라 미리 컴파일된 LuaJIT 바이트코드도 받아들였습니다. Lua 프로그램의 보안 문제를 읽을 때마다 60~70% 정도는 이 문제가 원인인 것 같습니다. 정말 답답합니다. load 함수는 99%의 경우 텍스트 코드를 불러오는 데 쓰이지만, 가끔 바이트코드에도 쓰입니다. 신뢰할 수 없는 텍스트를 불러오는 일은 안전하고 잘 작동하지만, 신뢰할 수 없는 바이트코드를 불러오는 일은 안전하지 않습니다. load의 기본 ‘mode’ 인수는 ‘bt’라서 바이트코드와 텍스트를 모두 받습니다. 기본값을 ‘t’로 바꾸고, 호출자가 바이트코드도 허용하겠다고 명시한 경우에만 받아들이면 이런 문제가 많이 사라질 겁니다. Lua 자체에서 원인을 고치기가 이렇게 간단하다는 점이 특히 답답합니다.
- @mdaniel — 신뢰할 수 없는 출처의 바이트코드를 불러오는 일은 안전하지 않습니다. 저는 Lua를 잘 모르지만, 글을 읽으니 샌드박스가 소스 코드만 검사하고 바이트코드가 나타나면 “어서 오세요”라고 맞아준 것처럼 보였습니다. 당신과 제가 비슷한 게으름을 지적하는 것 같습니다. 제가 잘못 이해한 게 아니라면 검사를 하긴 하지만 절반만 하는 셈입니다. 두 입력 경로를 똑같이 신뢰할 수 없는 것으로 취급했다면 t와 bt를 구분하는 일이 덜 중요해졌을 것 같습니다.
- @fanf — 두 입력 경로를 똑같이 신뢰할 수 없는 것으로 취급했다면 t와 bt를 구분하는 일이 덜 중요해졌을 것 같습니다. 문제는 더 낮은 수준에 있습니다. Lua 바이트코드는 C 관점에서 “안전하지 않습니다”. Lua 5.1에서는 Java나 Wasm처럼 바이트코드가 안전한지 검증하려는 시도가 있었지만, 검증 오류가 계속 발견돼 Lua 5.2에서 바이트코드 검증기를 없앴습니다. 글에서 설명하듯 LuaJIT에도 바이트코드 검증기가 없습니다. Lua 샌드박스는 기능 기반 보안으로 코드가 원치 않는 일을 못 하게 막습니다. Lua 컴파일러는 텍스트 소스 코드가 인터프리터의 메모리 안전성을 깨지 못하게 보장합니다. 샌드박스를 만들 때 일반적인 파싱과 해석 외에 별도 검사를 더할 필요는 없습니다. 표준 라이브러리에서 원치 않는 모듈을 빼면 됩니다. 하지만 원시 바이트코드는 메모리 안전성을 깨뜨릴 수 있으므로 불러오면 안 됩니다. 바이트코드도 샌드박스 안에서 실행되지만, 소스 코드와 달리 샌드박스 밖으로 나올 수 있습니다.
- @technomancy — “검사를 하긴 하지만 절반만 한다”라고 표현하지는 않겠습니다. 두 경우 모두 샌드박스는 표준 Lua 전역 변수 접근을 막습니다. 일반 텍스트 코드는 그 제한 때문에 악의적인 동작을 하기 어렵지만, 바이트코드는 전역 변수 테이블을 거치지 않고도 원하는 일을 할 수 있습니다. 두 입력 경로를 똑같이 신뢰할 수 없는 것으로 취급했다면 t와 bt를 구분하는 일이 덜 중요해졌을 것 같습니다. 기술적으로는 맞는 말입니다. 하지만 신뢰할 수 없는 텍스트 코드까지 불러오지 못하게 하면 언어의 가장 큰 장점 가운데 하나를 없애게 됩니다. 이렇게 큰 대가를 치르지 않고도 쉽게 고칠 수 있습니다. 기본값을 고치거나 위험한 작업을 별도 함수로 옮기면 됩니다.
- @goldstein — 이 버그를 제쳐두더라도 언어 수준의 격리에 의존하는 방식은 일반적으로 바람직하지 않다고 생각합니다. 인터프리터와 표준 라이브러리까지 공격 표면에 포함하고, 가상 메모리가 제공하는 보호도 잃습니다. 예를 들어 Spectre 같은 공격에 취약해집니다. 이 사례처럼 공격에 성공했을 때 영향 범위도 매우 큽니다. bwrap 안에서 신뢰할 수 없는 코드를 실행하는 일은 쉽고 성능도 크게 느려지지 않습니다. 이런 버그와 언어 환경을 겨냥한 공격 모두를 막아줍니다. 심층 방어 차원에서 컨테이너 안에서도 setfenv를 쓸 수 있습니다.
원문: LuaRocks.org / 번역·요약: Trawling