https://ufo.stealien.com/2026-01-26/Mongobleed-ko
STEALIEN Technical Blog
CVE-2025-14847(Mongobleed) 심층 분석
ufo.stealien.com
Mongobleed
MongoDB 서버가 네트워크 압축 알고리즘으로 zlib을 사용할 때 발생하는 허점을 악용해 초기화 되지 않은 힙 메모리 데이터가 누출될 수 있는 취약점
Mongobleed의 공격 흐름
- 인증되지 않은 사용자는 압축 해제 후 크기값을 실제 데이터의 크기보다 크게 조작해 서버에 전달함
- 서버는 압축 해제 과정에서, 사용자가 조작한 크기를 그대로 신뢰해 길이 검증 로직이 무력화됨
- 서버는 사용자가 조작한 압축 해제 크기 + 메시지 헤더 크기(16byte) 만큼의 힙 메모리를 할당하기 떄문에 잘못된 크기를 가진 Message 객체를 생성
- 이후 정상적인 BSON 파싱을 거친 후 해당 Message 객체가 응답을 전송하는 과정에서 다시 압축될 때 Message의 잘못된 크기를 기준으로 초기화되지 않은 힙 메모리 데이터가 함께 포함되어 외부로 누출됨
왜 Mongobleed?
bleed가 붙은 이유가 무엇일까?
2014년에 발생한 CVE-2014-0160(Heartbleed)와 공격 방식이 구조적으로 유사하기 때문
---> 공통점: 두 취약점 모두 사용자가 데이터의 길이 값을 실제 크기보다 크게 조작해 서버를 속이고, 그 결과 의도하지 않은 메모리 영역에 있는 값들이 응답에 포함되어 누출된다는 공통점
---> 차이점:

MongoDB 네트워크 압축?
MongoDB 네트워크 압축 기능을 활용하면 전송되는 크기를 줄이고 대역폭의 소모를 최소화시켜 전반적인 통신 효율을 높일 수 있음. 그치만 높은 압축률을 보장하기 위해선 높은 CPU연산 비용이 필요함(지연시간 증가 우려)
압축 알고리즘
(1) snappy: 빠른 압축, 해제를 제공하며 CPU 부화가 낮음
(2) zlib: 높은 압축률 제공, 속도 느림
(3) zstandard: 높은 압축률 제공, 빠른 처리 속도 제공
Mongobleed가 레인보우 식스 해킹 사건에 악용되나?
사건 발생: 2025년 12월 27일, 유비소프트사에서 서비스 중인 레인보우 식스 게임이 해킹을 당하는 사건이 발생함. 보안 전문 그룹 vx-underground는 이 해킹 사건에 Mongobleed를 악용했다고 주장하는 익명의 해킹그룹들이 있다고 X에 게시함
이번 레인보우 식스 게임을 해킹했다고 주장하는 4개의 그룹이 존재합니다. 각 그룹은 다음과 같은 일을 했습니다.
첫 번째 그룹은 게임 서비스를 악용해 플레이어 차단 / 인벤토리 수정 / 340조 달러 상당의 게임화폐를 지급했습니다.
두 번째 그룹은 Mongobleed를 악용하여 내부 Git 저장소에 접근했고, 1990년대부터 관리된 자료를 탈취했다고 주장했습니다. 탈취된 자료에는 내부 소스코드와 소프트웨어 개발 키트 (SDK: Software Development Kit) 등이 포함되어 있다고 했습니다.
세 번째 그룹은 두 번째 그룹과 동일하게 Mongobleed를 악용했는데, 이 그룹은 유비소프트사의 시스템을 장악해서 사용자 데이터를 탈취했는데 성공했고 유비소프트사를 향해 사용자 데이터를 유출하겠다고 협박하면서 금전을 요구하고 있습니다.
네 번째 그룹은 두 번째 그룹이 거짓말을 하고 있다고 주장하며 이미 오래전부터 내부 소스코드에 대한 접근권한을 가지고 있었고, 또한 두 번째 그룹은 첫 번째 그룹을 사칭해 내부 소스코드 전체를 유출할 구실을 만들고 있다고 주장하고 있습니다.
각 그룹의 정체는 무엇인지, 서로 어떤 관계인지는 아직까지 정확히 밝혀진 바는 없습니다.
출처: https://x.com/vxunderground/status/2005008887234048091
특정 해커 그룹이 Mongobleed를 활용해 내부 자료를 탈취하거나 시스템 권한을 획득하고 사용자 데이터를 탈취했다고 하지만 현재까지 이 주장을 뒷받침할 공식적인 기술 증거는 공개되지 않은 상태임.
시나리오 추측(블로그 제작자)
(1) 누출된 메모리에서 민감한 자산에 대한 접근 정보를 확인하고 이를 활용한 경우
(2) 세간에 공개되지 않은 취약점(0-day)과 결합하여 시스템 탈취까지의 공격체인을 구성했을 경우
취약점 분석(왜 발생? 원인 분석?)
MongoDB의 통신 프로토콜: 일반적인 HTTP 프로토콜이 아닌, TCP/IP 소켓 상에서 동작하는 자체적으로 만든 무선 프로토콜을 통해 클라이언트와 통신함
- 메세지 헤더의 구조
struct MsgHeader {
int32 messageLength;
int32 requestID;
int32 responseTo;
int32 opCode;
}
필드명설명
| messageLength | 메시지의 총 크기 |
| requestID | 메시지를 고유하게 식별하는 식별자 |
| responseTo | 클라이언트가 보낸 메시지의 requestID |
| opCode | 메시지의 작업 유형 |
명령 코드 목록
| OP_COMPRESSED | 2012 | 압축을 사용해 다른 명령 코드를 래핑 |
| OP_MSG | 2013 | 표준 형식을 사용해 메시지를 전송하며, 클라이언트 요청과 DB 응답에 사용 |
| *OP_REPLY | 1 | 클라이언트 요청에 대한 응답 |
| *OP_UPDATE | 2001 | 컬렉션의 문서를 업데이트하는 데 사용 |
| *OP_INSERT | 2002 | 하나 이상의 문서를 컬렉션에 삽입하는 데 사용 |
| RESERVE | 2003 | 이전에는 OP_GET_BY_OID에 사용됨 |
| *OP_QUERY | 2004 | 컬렉션의 문서를 데이터베이스에 쿼리하는 데 사용 |
| *OP_GET_MORE | 2005 | 이전 쿼리의 결과를 이어서 가져오는 데 사용 |
| *OP_DELETE | 2006 | 컬렉션에서 하나 이상의 문서를 제거하는 데 사용 |
| *OP_KILL_CURSORS | 2007 | 클라이언트가 커서 사용을 종료했음을 DB에 알릴 때 사용 |
명령어들이 제거된 이후 모든 요청에 기본적으로 OP_MSG, 압축 시 OP_COMPRESSED를 사용함
MessageCompressorManager::decompressMessage() 함수 코드
- 압축된 메시지를 전달받아 압축 해제한 뒤 Message 객체 생성
StatusWith<Message> MessageCompressorManager::decompressMessage(const Message& msg,
MessageCompressorId* compressorId) {
auto inputHeader = msg.header();
ConstDataRangeCursor input(inputHeader.data(), inputHeader.data() + inputHeader.dataLen());
if (input.length() < CompressionHeader::size()) {
return {ErrorCodes::BadValue, "Invalid compressed message header"};
}
CompressionHeader compressionHeader(&input);
auto compressor = _registry->getCompressor(compressionHeader.compressorId);
if (!compressor) {
return {ErrorCodes::InternalError,
"Compression algorithm specified in message is not available"};
}
if (compressorId) {
*compressorId = compressor->getId();
}
LOGV2_DEBUG(22927, 3, "Decompressing message", "compressor"_attr = compressor->getName());
if (compressionHeader.uncompressedSize < 0) {
return {ErrorCodes::BadValue, "Decompressed message would be negative in size"};
}
// Explicitly promote `uncompressedSize` to a 64-bit integer before addition in order to
// avoid potential overflow.
size_t bufferSize =
static_cast<size_t>(compressionHeader.uncompressedSize) + MsgData::MsgDataHeaderSize;
if (bufferSize > MaxMessageSizeBytes) {
return {ErrorCodes::BadValue,
"Decompressed message would be larger than maximum message size"};
}
auto outputMessageBuffer = SharedBuffer::allocate(bufferSize);
MsgData::View outMessage(outputMessageBuffer.get());
outMessage.setId(inputHeader.getId());
outMessage.setResponseToMsgId(inputHeader.getResponseToMsgId());
outMessage.setOperation(compressionHeader.originalOpCode);
outMessage.setLen(bufferSize);
DataRangeCursor output(outMessage.data(), outMessage.data() + outMessage.dataLen());
auto sws = compressor->decompressData(input, output);
if (!sws.isOK())
return sws.getStatus();
if (sws.getValue() != static_cast<std::size_t>(compressionHeader.uncompressedSize)) {
return {ErrorCodes::BadValue, "Decompressing message returned less data than expected"};
}
outMessage.setLen(sws.getValue() + MsgData::MsgDataHeaderSize);
return {Message(outputMessageBuffer)};
}
POC 코드
Mongobleed의 핵심은 uncompressedSize를 원본 데이터의 크기보다 크게 만들어야 하는 것
POC코드 흐름:
- uncompressedSize가 조작된 메시지를 서버에 전송
- 서버가 응답한 compressedMessage를 읽어 zlib 알고리즘으로 압축을 해제해서 원본 데이터로 복구
- 누출된 정보가 포함되어 있음을 확인
저는 한번 두 가지 활용 시나리오를 생각해봤습니다.
- 얻은 정보를 기반으로 특정 주소 베이스를 구해 ASLR(Address Space Layout Randomization) 무력화하는 등 다른 취약점과 체이닝 시켜 최종적으로 원격 코드 실행(RCE)로 가기 위한 필수적인 Read Primitive 역할로 활용
- 메모리 내에 존재하는 민감한 정보(인증 정보, API 토큰 등)를 획득하여 후속 공격에 활용
이 취약점이 인증 없이도 원격에서 악용 가능하다는 점과 후속 공격에 미치는 파급력을 고려했는지 취약점의 위험도를 높은 점수로 산정
--> 원격 코드 실행이 가능하다는 것은 위험도가 매우 높다는 것!!
'SWUFORCE > 기술 보고서' 카테고리의 다른 글
| Kubernetes의 네트워크 이슈를 해결할 수 있는 network-node-manager (0) | 2026.05.10 |
|---|---|
| AI 열풍 뒤의 그림자: Claude 모듈로 위장한 악성코드 (0) | 2026.05.04 |
| 저작권 경고 이메일로 시작한 글로벌 피싱의 기술적 진화 (0) | 2026.03.23 |
| 통신사와 카드사 보안사고를 통해 본 정보보안 현황과 대응 전략 (0) | 2026.02.23 |
| 피지컬 AI(Physical AI)란 무엇인가요? (0) | 2026.02.05 |