fix(telefonia): pickup de grupo funcionando de verdade + RCE no dialplan + domínio real no REGISTER
Pedido explícito do usuário: "testa criando um ramal com callgroup e captura
chamada de outro ramal". O teste com softphones reais (não só leitura de
código) achou 3 problemas que a fase anterior tinha dado como resolvidos:
1. A variable de call group estava com o nome errado (`call-group`, convenção
do Asterisk) e a suposição de que o FreeSWITCH fazia pickup automático só
com ela era falsa. Corrigido pro nome certo (`callgroup`) e pro mecanismo
real (fork de leg `pickup/<grupo>` no bridge + extension `*8` chamando a
application `pickup`, lendo o grupo via `${user_data(...)}`).
2. Implementando o mecanismo acima, achada uma vulnerabilidade real de RCE: o
allowlist de `application` no dialplan nunca bloqueava `${system(...)}`
embutido dentro do `data` de qualquer application já permitida —
`mod_commands` está carregado, então isso era execução de comando
arbitrário no host do FreeSWITCH pra qualquer Tenant Admin. Corrigido com
um segundo allowlist (`ALLOWED_INLINE_API_FUNCTIONS` +
`IsSafeDialplanData`) que só libera funções de leitura seguras
(`user_data`, `escape`, `url_encode`, `url_decode`, `regex`, `strftime`).
3. Registrar um softphone de verdade contra o domínio do tenant (não só curl
no mod_xml_curl) revelava 403 Forbidden: o sofia profile `internal` tinha
`force-register-domain`/`force-subscription-domain`/
`force-register-db-domain` fixados no domínio antigo, ignorando o domínio
de cada tenant. Corrigido no Dockerfile do FreeSWITCH (imagem
reconstruída, não só patch ao vivo).
Testado ponta a ponta com 3 softphones reais (linphone-cli) em containers na
mesma rede Docker: ramal do mesmo grupo captura de verdade uma ligação
tocando em outro ramal via *8 (canais confirmados bridged via `show
channels`); ramal de grupo diferente tenta e falha. RCE confirmado bloqueado
via curl (`${system(id)}` → 400) sem quebrar `${user_data(...)}` legítimo.
TODO.md (PHASE 53) e docs/EXTENSIONS.md atualizados corrigindo as afirmações
incompletas da fase anterior ("nenhuma mudança de infra necessária").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
@@ -57,6 +57,27 @@ RUN sed -i "s/data=\"domain=\$\${local_ip_v4}\"/data=\"domain=${DEFAULT_SIP_DOMA
|
||||
/etc/freeswitch/vars.xml \
|
||||
&& grep -q "domain=${DEFAULT_SIP_DOMAIN}" /etc/freeswitch/vars.xml
|
||||
|
||||
# Multi-domínio real por tenant (PHASE 52, docs/EXTENSIONS.md) — achado
|
||||
# real, confirmado só depois de registrar um SIP de verdade (o teste por
|
||||
# curl direto no mod_xml_curl não pegava isto, porque roda ANTES do
|
||||
# xml_curl ser consultado): o profile "internal" vanilla vem com
|
||||
# `force-register-domain`/`force-subscription-domain`/
|
||||
# `force-register-db-domain` fixados em `$${domain}` — REGISTER de
|
||||
# QUALQUER identidade era resolvido contra o domínio fixo do profile
|
||||
# ("b2bcall.local"), nunca o domínio que o tenant realmente tem
|
||||
# (`acme.b2bcall.net`), dando 403 Forbidden sempre. `<domain name="all"
|
||||
# alias="true".../>` (também vanilla) só afeta contexto de dialplan, não
|
||||
# esta checagem de REGISTER. Removendo os 3 params (procedimento padrão
|
||||
# documentado do próprio FreeSWITCH pra multi-domínio), o REGISTER passa
|
||||
# a resolver o domínio do próprio pedido — confirmado registrando um
|
||||
# softphone de teste de verdade contra `acme.b2bcall.net`.
|
||||
RUN sed -i \
|
||||
-e '/<param name="force-register-domain"/d' \
|
||||
-e '/<param name="force-subscription-domain"/d' \
|
||||
-e '/<param name="force-register-db-domain"/d' \
|
||||
/etc/freeswitch/sip_profiles/internal.xml \
|
||||
&& ! grep -q "force-register-domain\|force-subscription-domain\|force-register-db-domain" /etc/freeswitch/sip_profiles/internal.xml
|
||||
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
|
||||
Reference in New Issue
Block a user