본문 바로가기
CVE 취약점

CVE-2026-38526 취약점 분석: Krayin CRM TinyMCE 파일 업로드 RCE

by WhiteGuidance 2026. 10. 8.
반응형

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을 공격자에게 친절하게 응답으로 돌려줍니다. 공격자는 파일 경로를 추측할 필요조차 없습니다.

정리하면 이 코드에는 세 가지 방어선이 모두 빠져 있습니다.

  1. 확장자 화이트리스트 검증 없음 — .jpg, .png, .gif 등으로 제한하지 않음
  2. MIME 타입(실제 파일 내용) 검증 없음 — Content-Type 헤더만 믿거나 아예 확인하지 않음
  3. 실행 가능한 디렉터리에 저장 — PHP 인터프리터가 돌아가는 웹 루트 하위에 업로드 파일을 둠

이 세 가지 중 하나만 제대로 지켜졌어도 공격이 성립하지 않았을 겁니다.


4. 공격 흐름

위 다이어그램처럼 공격은 4단계로 진행됩니다.

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

 

여기서 주목할 점은 Content-Type: image/jpeg를 공격자가 임의로 적어 넣었다는 겁니다. 서버가 이 헤더 값만 보고 "이미지구나" 하고 믿어버리면 손쉽게 우회당합니다. 파일의 실제 내용(매직 바이트)을 서버 쪽에서 검사하지 않으면 헤더는 아무 의미가 없습니다.

  1. 검증 없이 저장 — 서버는 파일을 storage/tinymce/ 같은 공개 디렉터리에 원본 파일명 그대로 저장하고, 저장된 URL을 응답으로 돌려줍니다.
  2. 코드 실행 — 공격자는 그 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 헤더 스푸핑만으로는 우회할 수 없습니다.

반응형