pfSense Plus MIM API 툴킷 b4acd7ca - 높음 - 원격 - 파일 읽기 - Snort 규칙 {file} 디코드 후 결합
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-snort-file-join
분류: 파일 읽기 도달 범위: 원격
pfSense Plus MIM API (Netgate/pfsense-api b4acd7ca) - Netgate
GET /services/snort/rules/{name}/{file}는 두 개의 경로 매개변수를 받으며, 둘 다 type: string이고 pattern이 없습니다. 설명: 규칙 집합 내의 파일을 검사합니다. SnortRuleFileContents.filename은 "파일의 경로"입니다. 생성된 get_snort_ruleset_file._get_kwargs는 quote(str(...), safe="")를 수행하므로, ../는 전송 시 ..%2F가 됩니다. URL 디코딩 후 규칙 집합 디렉터리로 조인하는 서버는 여전히 빠져나갈 수 있습니다.
해당 경로에 GET을 수행할 수 있는 호출자와 플릿 JWT가 있으면, 등록된 모든 박스에서 MIM 에이전트 권한으로 지정된 규칙 집합 외부의 파일을 읽을 수 있습니다. 또한 조인은 실행되지 않았습니다. 이 랩은 공개 경로의 디코드 후 조인 가젯입니다.
이것은 9개 중 남은 8번째입니다. 지도: pfsense-api-nexus-mim. 동일한 핀 잔여물: fleet JWT. 이것은 jaredhendrickson/pfsense-api가 아닙니다.
| ID | 아직 CVE 없음 |
| 분류 | 파일 읽기 (Snort {file} 디코드 후 조인) |
| 도달 범위 | 원격 (인증된 GET; 플릿 JWT는 모든 device_id에서 동일한 경로를 재사용) |
| CWE | CWE-22 |
| CVSS | 높음: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N — Snort 규칙 집합 파일에 GET할 수 있는 JWT의 경우 |
| 제품 | Netgate/pfsense-api (pfSense Plus Nexus MIM 컨트롤러 OpenAPI + 생성된 클라이언트) |
| 영향받음 | main b4acd7ca21dbca739a530403a1f62a7440f7726f (2026-05-25, Add Snort-3 API definitions) |
| 인증 | 예제는 POST /login 후 컨트롤러 JWT를 전송합니다. OpenAPI에는 securitySchemes가 없습니다. 미인증은 입증되지 않았습니다. |
| 라이선스 | GNU Affero GPL v3.0 |
| 랩 | 127.0.0.1:19181 전용. 디코드 후 조인 가젯. 조인은 실행되지 않았습니다. |
GET /services/snort/rules/{name}/{file}을 수행할 수 있는 호출자가 필요합니다. 공식 예제는 POST /login 후 Authorization: Bearer를 사용한 다음, GET /mim/devices의 모든 id에 대해 해당 토큰을 base_url=.../api/device/pfsense/{device_id}/api로 재사용합니다. 생성된 작업은 여전히 AuthenticatedClient | Client를 입력합니다. 이 GET에서는 CSRF가 문제되지 않습니다.
| 방식 | 싱크 | 권한 | 실험실 vs 추론 |
|---|---|---|---|
생성된 클라이언트 GET with {file}=../../oracle/WITNESS (wire ..%2F..%2Foracle%2FWITNESS) | decode unquote(file) then join onto the named ruleset directory and read | 정찰: HTTP 200, filename /oracle/WITNESS, body PFSENSE-API-SNORT-FILE-JOIN-WITNESS. 의도된 것은 해당 ruleset 내부의 .rules 파일이다. | 실험실 입증 (gadget). Plus join은 실행되지 않음. |
동일한 GET with {file}=../../../etc/passwd | 컨트롤러가 이를 디바이스 파일시스템에 매핑하는 경우 동일한 join | 정찰: MIM 에이전트로 호스트 파일 읽기 (Plus WebGUI/PHP는 일반적으로 root). 실험실에서는 /oracle/WITNESS를 사용했으며, 실제 /etc/passwd는 아님. | 추론됨 (동일한 join, 호스트 경로). |
fleet JWT 이후 동일한 GET (POST /login 한 번, /api/device/pfsense/{id}/api에서 재생) | 동일한 비제약 {file}의 디바이스 프록시 | 정찰: 하나의 헬프데스크 형태 컨트롤러 토큰이 로그인이 의도된 박스뿐만 아니라 모든 등록된 박스에서 파일을 읽음. | 추론됨 (G6 계약 + 이 경로). Plus RBAC는 실행되지 않음. |
{name}에 동일한 잔여물 | GET|DELETE /services/snort/rules/{name} 및 형제 address/suppress {name}도 비제약 문자열임 | 정찰 / 무결성: 이름이 경로인 ruleset의 이름 변경 또는 삭제. | 스펙에서 추론됨. 실험실에서는 {file}만 구동함. |
| 동일한 wire 경로의 제한된 제어 | decode 후 relative_to(ruleset_dir.resolve()) | 닫힘. HTTP 400, witness 없음. | 실험실 입증 |
정상 {file}=local.rules | 의도된 ruleset 읽기 | 닫힘. 200, sid:1, witness 없음, naive와 control 모두. | 실험실 입증 |
인접 닫힘: ../의 클라이언트 퍼센트 인코딩 (encoding=%2F). decode 후 제어 join (control-confined=yes). Plus 컨트롤러 join (plus-join-not-executed). ruleset foo 내부의 local.rules 의도된 읽기. 인증되지 않은 GET (입증되지 않음; 예제는 Bearer를 전송함). POST /services/snort/rules 아카이브 / 심어진 .so (의도된 IDS 관리자, 이 잔여물이 아님). Diagnostics PHP/shell. 파일 쓰기. 호스트 셸. 이 잔여물은 RCE가 아니다.
2026-10-14 기준 HEAD에 태그가 없고 최신 커밋이 "Add Snort-3 API definitions"이기 때문에 Netgate/pfsense-api b4acd7ca21dbca739a530403a1f62a7440f7726f를 고정했다. 이것이 이 경로가 존재하는 방식이다. YAML에는 어디에도 pattern: 키가 없다. {name}과 {file}은 비제약 문자열이다. quote(..., safe="")는 클라이언트 인코더이지 서버 기반 디렉터리 검사가 아니다.
/services/snort/rules/{name}/{file}:
parameters:
- in: path
name: name
required: true
schema:
type: string
- in: path
name: file
required: true
schema:
type: string
get:
description: Obtain the contents of the specified file in the ruleset. This is intended for inspecting scripts provided by it.
operationId: GetSnortRulesetFile
"url": "/services/snort/rules/{name}/{file}".format(
name=quote(str(name), safe=""),
file=quote(str(file), safe=""),
),
잘못 기록된 오답: 클라이언트 인코딩을 닫는 순회로 취급; pfSense Plus 구성; Snort 아카이브 .so를 이 잔여물로 순위 지정; unauth GET 주장. 극장: 리버스 셸. 오라클은 /oracle/WITNESS의 gadget 200과 control 400이다.
python3 pfsense-api-snort-file-join-Abraxas-Labs.py
또는
cd lab
./run.sh
오직 http://127.0.0.1:19181만 대상으로 한다. Compose 프로젝트 pfsense-api-snort-file-join. 공개 경로의 decode-then-join gadget. Plus join은 실행되지 않는다. 실행 후 스택은 해체된다.
SUCCESS PFSENSE-API-SNORT-FILE-JOIN encoding=%2F decode-then-join=yes control-confined=yes plus-join-not-executed PFSENSE-API-SNORT-FILE-JOIN-WITNESS
../../../etc/passwd의 클라이언트 인코딩은 ..%2F..%2F..%2Fetc%2Fpasswd입니다. /services/snort/rules/foo/..%2F..%2Foracle%2FWITNESS에 대한 단순 GET 요청은 200을 반환했고, 파일명은 /oracle/WITNESS, 본문은 PFSENSE-API-SNORT-FILE-JOIN-WITNESS였습니다. 규칙셋 루트 아래에서 해석되는 제한된 제어 환경에서 동일한 요청은 400을 반환했고 witness는 없었습니다. 정상적인 local.rules는 foo 안에 머물렀습니다.
디코드 후 규칙셋 루트 아래에서 해석하십시오. .. 및 절대 경로를 거부하십시오. 읽기 전에 Path.resolve() 후 relative_to(ruleset_dir.resolve()) 또는 이에 상응하는 처리를 수행하십시오. OpenAPI에서 {file}(및 {name})을 규칙셋에 안전한 패턴으로 제한하여 생성기가 경로 형태의 문자열을 보내지 않도록 하십시오.
b4acd7ca21dbca739a530403a1f62a7440f7726fpfapi_openapi.yml /services/snort/rules/{name}/{file}get_snort_ruleset_file.pyGNU Affero GPL v3.0. LICENSE를 참조하십시오. 루프백 랩 전용. 보증 없음.