pfSense Plus MIM API 툴킷 b4acd7ca - 높음 - 원격 - 권한 잔여 - example-add-devices.py JSON을 SSH로
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-enroll-json-ssh
분류: 권한 잔여물 도달 범위: 원격
pfSense Plus MIM API 툴킷 (b4acd7ca) - Netgate
example-add-devices.py는 SSH를 통해 각 pfSense Plus 장치에 Nexus 컨트롤러를 등록하는 문서화된 방법입니다. 이 스크립트는 GET /mim/controller/identity를 json.dumps하고, "만 교체한 뒤, 그 블롭을 두 번 .format하여 큰따옴표로 묶인 curl -d POSIX 스크립트에 삽입합니다. Paramiko exec_command는 pfsense_addresses의 모든 주소에서 SSHUSER(샘플 기본값 admin)로 스크립트를 실행합니다.
json.dumps와 replace('"', '\\"')는 따옴표를 닫힌 상태로 유지합니다. $(), 백틱, ${...}는 JSON 메타문자가 아닙니다. 이들은 여전히 열려 있는 큰따옴표 -d 문자열 안에서 살아 있습니다. OpenSSH exec는 셸입니다. 그것이 바로 잔여물입니다.
이것은 서드파티 CE REST 패키지가 아닙니다. pfSense Plus Nexus Multi-instance Management 컨트롤러를 위한 공식 OpenAPI 클라이언트와 샘플입니다. 저는 Plus를 작성하지 않았습니다. 실습 환경은 고정된 보간과 모의 /var/run/pfnet-controller.sock입니다.
| ID | 아직 CVE 없음 |
| 분류 | 권한 잔여물 (컨트롤러 JSON이 등록 SSH로 유입) |
| 도달 범위 | 원격 (샘플이 verify_ssl=False를 설정한 상태에서 CONTROLLER_URL에 대한 경로상 공격자, 또는 ControllerSummary 문자열 필드의 작성자) |
| CWE | CWE-78 |
| CVSS | 높음: 8.1 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H |
| 제품 | Netgate/pfsense-api (Plus MIM 툴킷) |
| 영향받음 | 공개 main b4acd7ca21dbca739a530403a1f62a7440f7726f example-add-devices.py |
| 인증 | 운영자가 문서화된 샘플을 실행합니다. 공격자는 경로상에 있거나 요약 문자열을 작성할 수 있습니다. 컨트롤러 로그인은 여전히 먼저 발생합니다. |
| 라이선스 | GNU Affero GPL v3.0 |
| 실습 환경 | 127.0.0.1:19101만 해당. Plus 이미지 없음. |
Nexus MIM 잔여물 시리즈의 파트 1. 형제 프로젝트: enroll-autoadd, unix-sock-noauth, example-ssl-verify (G9는 경로상 피어가 이 JSON을 구성하는 방식입니다), fleet-jwt, export-key-get.
잔여물은 여전히 열려 있는 큰따옴표 -d 문자열 내부의 명령 치환입니다. json.dumps와 따옴표 처리는 문자열을 닫힌 상태로 유지합니다. $(), 백틱, ${...}는 살아남아 OpenSSH가 스크립트를 exec할 때 SSHUSER로 실행됩니다.
| Method | Sink | Privilege | Lab-proven or inferred |
|---|---|---|---|
ControllerSummary 문자열 필드에 $()(또는 백틱 / ${...})를 넣은 뒤, 운영자가 example-add-devices.py를 실행하도록 한다 | controller_info.replace('"', '\\"') → device_script.format(...) → sshclient.exec_command(device_script) (example-add-devices.py ~104-161) | pfsense_addresses의 각 주소에서 SSHUSER(샘플 기본값 admin; pfSense admin SSH는 일반적으로 root에 준함)로 셸 실행 | Lab-proven (보간에 대해): INJECT vpn_listenaddr=$(printf %s PFSENSE-API-ENROLL-JSON-SSH-WITNESS >/oracle/witness)가 따옴표 전용 이스케이프를 통과했고(dollar-paren-survived=yes), witness를 기록했다(inject-oracle=yes). Paramiko/SSH 자체는 이 compose에 포함되지 않는다. |
샘플이 verify_ssl=False를 사용하는 동안 GET /api/mim/controller/identity에 대한 TLS MITM | 동일한 ControllerSummary 파싱 후 동일한 스크립트 | 동일한 셸. 모든 ControllerSummary 필드는 str 또는 Unset이므로, 타입 파싱이 되는 MITM JSON 본문이면 충분하다. | Inferred (샘플에서 유추): example-add-devices.py의 로그인 Client와 AuthenticatedClient 모두 verify_ssl=False를 설정한다. Lab은 summary 객체를 직접 POST한다. 관련 항목: example-ssl-verify. |
컨트롤러의 name 또는 vpn_listenaddr에 치환 문자를 기록한 뒤, 다음 enroll을 기다린다 | 동일한 sink | 운영자가 다음에 샘플을 실행할 때 동일한 셸 | Inferred. 해당 필드에 대한 사전 쓰기가 필요하다(이미 컨트롤러 관리자이거나 unix-sock-noauth 관련 항목). |
동일한 셸, ControllerSummary.additional_properties를 통한 추가 키 | 동일한 dump에 추가 MITM 키가 포함된다(to_dict는 from_dict 이후 남은 항목을 복사) | 추가 필드가 -d와 셸로 전달된다 | Inferred (to_dict에서 유추). Lab은 vpn_listenaddr를 통해서만 주입한다. |
| Method | Sink | Privilege | Lab-proven or inferred |
|---|---|---|---|
| enrollment 워크스테이션 stdout 읽기 | print("CONTROLLER_SUMMARY:", controller_info) | 컨트롤러 identity key, Netgard vpn_pubkey / vpn_netkey / listen 주소 | Inferred (샘플 print에서 유추). Lab은 실제 컨트롤러 blob을 출력하지 않는다. |
| SSH exec 채널 스니핑 또는 MITM | exec_command(device_script) 페이로드는 curl -d JSON이다 | 모든 디바이스로 전송되는 동일한 컨트롤러 identity | Inferred. 해당 채널의 AutoAddPolicy는 관련 보고서다. |
| gadget에 대한 CONTROL 케이스 | POST /case name=control | 정상적인 vpn_listenaddr가 변경 없이 unix 소켓에 도달한다 | Lab-proven. 192.0.2.10:51820가 그대로 POST되었고, oracle 없음(control-clean=yes). |
| Method | Sink | Privilege | Lab-proven or inferred |
|---|---|---|---|
SSHUSER로 주입된 명령 | 동일한 exec_command | pfnet-controller 중지, enrollment 삭제, 재부팅 또는 기타 방법으로 디바이스를 MIM에서 이탈시킨다 | Inferred (셸의 부작용으로 유추). Lab은 witness 파일만 기록한다. |
Requires: 공식 샘플 경로(운영자 enroll). 해당 샘플이 실행 중일 때 on-path peer에 대해 UI:N. 컨트롤러 로그인은 여전히 먼저 발생한다.
pfSense 박스에서 admin으로의 셸은 "JWT가 php를 할 수 있다"는 것이 아니다. 그것은 enrollment 워크스테이션이 sh -c를 통해 방화벽에 컨트롤러 blob을 전달하는 것이다. 거기서부터 조작된 JSON은 디바이스가 JWT 없이 /var/run/pfnet-controller.sock에 POST하는 내용이다(unix-sock-noauth). AutoAddPolicy는 이 스크립트를 수신하는 SSH peer가 고정되지 않음을 의미한다(enroll-autoadd). G9 verify_ssl=False는 on-path peer가 애초에 JSON을 기록하는 방식이다.
| Surface | Why |
|---|---|
생성된 라이브러리 기본값 verify_ssl=True | 이 잔여물은 샘플이며, pfapi.Client 기본값이 아니다. |
curl -d "..."의 따옴표 탈출 | JSON과 " 치환으로 문자열이 닫힌 상태로 유지된다(quote-only-escape=yes). |
from_dict / json.dumps를 통과한 ControllerSummary에서 분리된 raw-newline 스크립트 | U+000A가 \n이 된다. |
| 등록 워크스테이션에서의 RCE | 형식은 로컬이다. 셸은 원격 exec이다. |
| Nexus 컨트롤러로의 인증되지 않은 HTTPS | login.sync가 여전히 먼저 실행된다. |
add_controlled_device.sync | 파싱된 JSON을 HTTPS로 전송한다. DeviceIdentity.controller_add를 exec하지 않는다. |
| CONTROL listenaddr | $() / 백틱 없음. 오라클을 기록하지 않는다. |
unix POST 본문 내 INJECT $() | 확장은 curl 이전에 sh에서 발생한다(inject-meta-reached-unix는 실패). |
Paramiko AutoAddPolicy() | 형제 팩. 이것과 혼용하지 말 것. |
| JWT 없는 디바이스 unix-socket MIM | 형제 팩. |
| pfSense Plus 컨트롤러/데몬 | 이 저장소에 없음. 구성되지 않음. |
의도된 POST /diag/cmd/php 및 /diag/cmd/shell | 이미 관리자인 Nexus 콘솔. 이 잔여물이 아님. |
| 서드파티 CE REST 패키지 | 다른 GitHub 트리. |
github.com/Netgate/pfsense-api b4acd7ca21dbca739a530403a1f62a7440f7726f(2026-05-25, Add Snort-3 API definitions)를 고정했다. 태그는 없다. 트리는 OpenAPI 3.0.3(pfapi_openapi.yml, 486개 경로, 755개 오퍼레이션)에 생성된 Python과 샘플이 더해진 형태다. HTTPS 컨트롤러와 pfnet-controller는 Plus 전용이며 저장소에 없다.
열두 번의 탐색. Login/JWT, diagnostics, MIM enrollment, client TLS, users/certs, packages, VPN/firewall, OpenAPI security, device IDOR, git/firmware/feeds, 그리고 모든 생성된 오퍼레이션에 대한 loopback mock. 755/755 _get_kwargs가 127.0.0.1:55980으로 전송되었다. AuthenticatedClient는 항상 Bearer를 보냈다. Client는 php/shell을 포함해 아무것도 보내지 않았다. 경로 파라미터는 ../를 퍼센트 인코딩한다. 라이브러리 기본값 verify_ssl=True. 모든 공식 예제는 False로 설정한다.