
1. 개요
| 항목 | 내용 |
| CVE ID | CVE-2026-38526 |
| CVSS 3.1 | 9.9 (Critical) |
| CWE | CWE-434 (Unrestricted Upload of File with Dangerous Type) |
| 영향 제품 | Webkul Krayin CRM v2.2.x (Laravel 기반 오픈소스 CRM) |
| 취약 엔드포인트 | POST /admin/tinymce/upload |
| 권한 요구 | 낮음 (로그인만 되어 있으면 됨, 관리자 권한 불필요) |
| 사용자 상호작용 | 불필요 |
CVSS 벡터는 AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H로, 네트워크를 통해 낮은 권한의 인증된 공격자가 사용자 상호작용 없이 기밀성·무결성·가용성 모두에 심각한 영향을 줄 수 있음을 의미합니다. 간단히 말해 "로그인만 할 수 있으면 서버 전체를 장악할 수 있는" 등급의 취약점입니다.
2. 왜 위험한가 — 한 줄 요약
Krayin CRM은 게시글이나 이메일 템플릿 등을 꾸밀 때 TinyMCE라는 리치 텍스트 에디터를 씁니다. 사용자가 에디터 안에 이미지를 붙여넣으면, 그 이미지 파일을 서버로 올려주는 업로드 API가 호출됩니다. 문제는 이 API가 "올라온 파일이 정말 이미지인지"를 전혀 확인하지 않는다는 점입니다. 그 결과 공격자는 .jpg가 아니라 .php 확장자를 가진 악성 스크립트를 올릴 수 있고, 그 파일은 웹에서 바로 접근 가능한 폴더에 저장됩니다.
3. 소스코드 레벨 분석
Laravel 기반 업로드 컨트롤러는 보통 아래와 같은 형태로 동작합니다. 이것이 실제로 취약했던 로직의 핵심 패턴입니다.

이 코드를 한 줄씩 뜯어보면 문제가 뭔지 명확해집니다.
- ① $request->file('file')은 HTTP 요청에 실려 온 파일을 그대로 가져옵니다. 이 시점에는 아직 아무 검증도 없습니다.
- ② getClientOriginalName()은 "클라이언트(브라우저)가 보내준" 파일명을 그대로 신뢰합니다. 즉 공격자가 shell.php라고 이름을 붙이면 서버는 그 이름을 의심 없이 받아들입니다. 서버 쪽에서 신뢰해서는 안 되는 값을 그대로 쓰는 전형적인 입력 검증 부재 패턴입니다.
- ③ 더 치명적인 부분입니다. Laravel의 storage/app/public 디렉터리는 php artisan storage:link로 웹 루트(public/storage)와 심볼릭 링크가 걸려 있습니다. 즉 여기 저장된 파일은 전부 URL로 직접 접근 가능합니다. 원래 이미지 파일이야 그래도 상관없지만, .php 파일이 여기 저장되면 "URL로 접근 가능한 PHP 스크립트"가 되어버립니다.
- ④ 저장된 파일의 정확한 URL을 공격자에게 친절하게 응답으로 돌려줍니다. 공격자는 파일 경로를 추측할 필요조차 없습니다.
정리하면 이 코드에는 세 가지 방어선이 모두 빠져 있습니다.
- 확장자 화이트리스트 검증 없음 — .jpg, .png, .gif 등으로 제한하지 않음
- MIME 타입(실제 파일 내용) 검증 없음 — Content-Type 헤더만 믿거나 아예 확인하지 않음
- 실행 가능한 디렉터리에 저장 — PHP 인터프리터가 돌아가는 웹 루트 하위에 업로드 파일을 둠
이 세 가지 중 하나만 제대로 지켜졌어도 공격이 성립하지 않았을 겁니다.
4. 공격 흐름
위 다이어그램처럼 공격은 4단계로 진행됩니다.

- 로그인 — 관리자 권한이 없는 일반 인증 사용자 계정으로도 /admin/tinymce/upload 엔드포인트에 접근할 수 있습니다.
- 파일 업로드 — 공격자는 아래와 같은 형태의 멀티파트 요청을 보냅니다.

여기서 주목할 점은 Content-Type: image/jpeg를 공격자가 임의로 적어 넣었다는 겁니다. 서버가 이 헤더 값만 보고 "이미지구나" 하고 믿어버리면 손쉽게 우회당합니다. 파일의 실제 내용(매직 바이트)을 서버 쪽에서 검사하지 않으면 헤더는 아무 의미가 없습니다.
- 검증 없이 저장 — 서버는 파일을 storage/tinymce/ 같은 공개 디렉터리에 원본 파일명 그대로 저장하고, 저장된 URL을 응답으로 돌려줍니다.
- 코드 실행 — 공격자는 그 URL에 평범한 GET 요청을 보내기만 하면 됩니다. 웹 서버는 .php 확장자를 보고 해당 파일을 "실행"하므로, 그 안에 들어 있던 코드가 서버 권한(보통 www-data)으로 돌아갑니다.
업로드 자체는 "파일을 저장"하는 행위일 뿐인데, 저장 위치가 실행 가능한 영역이기 때문에 업로드가 곧 코드 실행으로 이어지는 구조입니다. 이게 CWE-434의 핵심 메커니즘입니다.
5. 영향 범위
- 웹 서버 프로세스 권한으로 임의 명령 실행
- 애플리케이션 설정 파일(.env) 탈취 → DB 자격 증명 등 추가 정보 노출로 이어짐
- 전체 시스템 침해로 발전할 수 있는 수준의 심각한 위험
- CRM 특성상 고객 개인정보·영업 데이터가 저장되어 있어 실제 피해 규모가 클 수 있음
전 세계적으로 약 2,700개 서비스가 영향을 받을 것으로 추정됩니다.
6. 대응 방안
| 구분 | 조치 |
| 즉시 조치 | /admin/tinymce/upload 엔드포인트를 WAF/리버스 프록시에서 차단하거나 접근 제한 |
| 단기 조치 | 업로드 디렉터리(storage/, public/storage)에서 PHP 실행을 막는 웹서버 설정 추가 (.htaccess에 php_flag engine off, 혹은 Nginx location 블록으로 .php 실행 차단) |
| 코드 수정 | 확장자 화이트리스트 검증 + 실제 파일 내용(MIME) 검증을 모두 적용 |
| 구조적 개선 | 업로드 파일명을 서버에서 무작위 UUID로 재생성하여 저장 (원본 파일명 신뢰 금지) |
| 구조적 개선 | 업로드 파일 저장 경로를 웹 루트 바깥, 혹은 별도 오브젝트 스토리지(S3 등)로 분리 |
| 권한 통제 | 업로드 기능은 꼭 필요한 역할(Role)에만 부여 — 모든 로그인 사용자가 아니라 |
| 모니터링 | 업로드 디렉터리에 대한 이상 접근/실행 로그 모니터링 |
수정된 코드는 대략 이런 형태가 되어야 합니다.

mimes: 룰은 확장자뿐 아니라 파일의 실제 시그니처(매직 바이트)까지 검사하므로, Content-Type 헤더 스푸핑만으로는 우회할 수 없습니다.
'CVE 취약점' 카테고리의 다른 글
| CVE-2026-93485 분석: WordPress 댓글 하나로 관리자 서버를 장악하는 Comment2XSS (0) | 2026.10.10 |
|---|---|
| CVE-2026-89274 분석: WP Recipe Maker, 댓글이 do_shortcode()를 만났을 때 (0) | 2026.10.09 |
| CVE-2026-6951: simple-git --config 플래그 우회를 통한 원격 코드 실행(RCE) 분석 (0) | 2026.10.07 |
| CVE-2026-94504 분석: Ninja Forms 저장형 XSS, </textarea> 한 줄로 관리자 화면을 뚫다 (0) | 2026.10.06 |
| CVE-2026-29057: Next.js Rewrites HTTP Request Smuggling 분석 (0) | 2026.10.05 |