
pfSense Plus MIM API toolkit b4acd7ca - Médio - Remoto - Privilégio remanescente - amostras oficiais verify_ssl=False
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-example-ssl-verify
Classe: Privilégio remanescente Alcance: Remoto
pfSense Plus MIM API (Netgate/pfsense-api b4acd7ca) - Netgate
O Client / AuthenticatedClient gerado define verify_ssl=True por padrão e repassa isso ao httpx. A documentação já chama verify_ssl=False de risco de segurança e mostra primeiro um caminho de bundle PEM. Todo exemplo oficial em Python e o helper_funcs.py ainda os constroem com verify_ssl=False. O example.js define NODE_TLS_REJECT_UNAUTHORIZED=0, então todo o processo Node ignora a verificação. O JSON de login coloca usuário e senha em base64. Isso é o contrato OpenAPI, não criptografia. O TLS é a camada de confidencialidade.
Um par on-path de :8443 apresenta qualquer certificado. O exemplo ainda faz POST em /login. O substituto captura o login admin em base64 e emite um JWT. Esse token é a corrente: G4 export-key, G5 ACME dump, G6 fleet JWT, G7 ACB userkey, o php/shell pretendido, e o JSON que o G1 interpola no SSH.
Esta é a Parte 9 do pfSense Plus MIM API toolkit. Leia este remanescente primeiro, mesmo sendo Medium. O JWT roubado é como o resto se torna alcançável a partir de uma estação de trabalho que copiou o example.py.
| ID | sem CVE ainda |
| Classe | Privilégio remanescente (verificação TLS do exemplo desativada) |
| Alcance | Remoto (on-path para :8443) |
| CWE | CWE-295 |
| CVSS | Médio: 6.8 CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N |
| Produto | Netgate/pfsense-api (cliente Plus Nexus MIM; não o pacote REST do CE) |
| Afetado | main público b4acd7ca21dbca739a530403a1f62a7440f7726f (2026-05-25) exemplos oficiais e helper_funcs.py |
| Autenticação | operador copia e executa um exemplo contra o controlador; nenhum papel extra no controlador |
| Licença | GNU Affero GPL v3.0 |
| Lab | apenas 127.0.0.1:19191. Não compõe o pfSense Plus. |
AC:H é on-path entre a estação de trabalho do operador e o controlador MIM. UI:R é copiar/executar o exemplo.
Vítima: estação de trabalho do operador executando um exemplo oficial ou helper_funcs.py contra https://controller:8443. Atacante: on-path para esse HTTPS (LAN, DHCP rogue, salto de VPN comprometido, ARP). Não é Internet não autenticada até o firewall.
| Método | Sink | Privilégio |
|---|---|---|
Apresentar qualquer certificado enquanto a amostra faz POST para /login ou /api/login com verify_ssl=False / NODE_TLS_REJECT_UNAUTHORIZED=0 | httpx verify=False (amostras Python) ou Node TLS reject-unauthorized 0 | Confidencialidade (comprovada em laboratório): ler o JSON de login. Lab: stand-in capturou base64 admin / lab-controller-secret (captured_b64_admin=yes). A decodificação é um único base64 -d. |
Mesmo MITM, 200 LoginResponse | amostra aceita qualquer certificado | Confidencialidade (comprovada em laboratório): roubar o JWT de acesso. Lab: INJECT obteve um token que carregava PFSENSE-API-EXAMPLE-SSL-VERIFY-WITNESS. |
Mesmo MITM, Set-Cookie no login | amostra copia client.get_httpx_client().cookies para AuthenticatedClient | Confidencialidade (cookie comprovado em laboratório, replay inferido): JWT de refresh de 24 horas. Replay POST /login/refresh com esse cookie mais o nome de usuário em base64 até o TTL do refresh. Lab capturou Set-Cookie: refresh=.... Refresh contra um controlador ativo não foi executado. |
| Replay do Bearer roubado para o controlador real (se o atacante também puder rotear até ele) | Plus MIM /api e /api/device/pfsense/{device_id}/api | Integridade (inferida): equivalente a admin como o usuário logado nesta API. O schema inclui POST /diag/cmd/php, POST /diag/cmd/shell, POST /diag/editfile, POST /diag/backup/restore, POST /diag/control halt/reboot. Não exercitado aqui. Este lab é um stand-in de controlador, não o Plus. |
| Replay do Bearer roubado como leftover 6 (token de frota) | um POST /login JWT reutilizado para cada device_id | Integridade (inferida): falar com o firewall B após autenticar como um usuário destinado ao firewall A. LoginResponse não tem allow-list por dispositivo. Pack: pfsense-api-fleet-jwt. RBAC do Plus não executado. |
| Replay do Bearer roubado como leftover 4 | GET /system/certauth/{refid}/export-key, GET /system/certificates/{refid}/export-key, GET /system/certificates/{refid}/export-p12 | Confidencialidade (inferida): extrair chaves privadas de CA e leaf, PKCS12 documentado sem proteção por senha, depois personificar VPN / WebGUI TLS / IPsec. Pack: pfsense-api-export-key-get. |
| Replay do Bearer roubado como leftover 5 | GET /services/acme | Confidencialidade (inferida): roubar a chave de conta do Let's Encrypt e as chaves dos certificados emitidos para cada hostname que o firewall registra. Pack: pfsense-api-acme-key-dump. |
Replay do Bearer roubado, ou vazamento de userkey de uma amostra que faz GET em ACB, como leftover 7 | GET /services/acb?userkey= e GET /services/acb/bkp?userkey= | Confidencialidade (inferida): listar e buscar blobs do Auto Config Backup (configuração completa). Pack: pfsense-api-acb-userkey. |
Moldar o JSON de ControllerSummary em direção ao leftover 1 | MITM de HTTPS com verify_ssl=False fornece o JSON que example-add-devices.py escapa com quotes e depois aplica .format no SSH | Integridade (inferida): $() / backticks / newlines em um campo do controlador executam como SSHUSER em cada endereço em pfsense_addresses. Pack: pfsense-api-enroll-json-ssh. |
| Injetar respostas em direção ao cliente | skip-verify aceita qualquer certificado | Integridade (token comprovado em laboratório, frota inferida): cunhar um JWT que a amostra manterá. Lab: token INJECT com o witness. Listas de dispositivos falsas / progresso de upgrade / sucesso de login contra uma amostra ativa são o mesmo gate. |
POST /diag/control, POST de firmware, restauração de um backup quebrado (após Bearer roubado) | superfície de admin pretendida uma vez que o TLS se foi | Disponibilidade (inferida): halt / reboot / imagem quebrada. Mesmo Bearer. Não testado em lab. |
Comprovado em laboratório vs inferido: falha no handshake do CONTROL, token INJECT e login admin em base64 capturado são comprovados em laboratório em 127.0.0.1:19191. Replay desse Bearer em um controlador Plus ativo, diag php/shell, G4-G7, moldagem de JSON do G1 e cunhagem de refresh-cookie de novos JWTs de acesso são inferidos a partir do OpenAPI e das amostras.