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:
@@ -18,11 +18,17 @@ export interface DirectoryUserParams {
|
||||
callerIdNumber?: string;
|
||||
tenantId: string;
|
||||
extensionId: string;
|
||||
/** Grupo de captura (secao 178) — vira a variable `call-group` no
|
||||
* directory; o *8 de group pickup no dialplan usa essa mesma variable
|
||||
* pra achar um canal tocando no mesmo grupo (`pickup` da FreeSWITCH
|
||||
* exige `call-group` batendo, senão qualquer ramal capturaria a
|
||||
* chamada de qualquer outro — achado real reportado pelo usuário). */
|
||||
/** Grupo de captura (secao 178) — vira a variable `callgroup` (sem
|
||||
* hífen: é o nome que o `mod_dptools`/convenção FreeSWITCH usa,
|
||||
* confirmado contra a documentação oficial — `${user_data(<ext>@
|
||||
* <domain> var callgroup)}` no dialplan lê exatamente esse nome). O
|
||||
* pickup em si não é automático por variável: o dialplan que atende a
|
||||
* ligação pro ramal precisa forkar um leg `pickup/${...essa var...}`
|
||||
* no `bridge`, e uma extension separada de feature code (`*8`) chama a
|
||||
* application `pickup` com o grupo de quem discou. Ver
|
||||
* docs/EXTENSIONS.md — achado real reportado pelo usuário, verificado
|
||||
* contra a documentação do FreeSWITCH antes de close (a primeira versão
|
||||
* usava `call-group`, que não é lido por nada). */
|
||||
callGroup?: string | null;
|
||||
}
|
||||
|
||||
@@ -60,7 +66,7 @@ export function buildDirectoryUserXml(params: DirectoryUserParams): string {
|
||||
<variable name="effective_caller_id_number" value="${callerIdNumber}"/>
|
||||
<variable name="b2bcall_tenant_id" value="${xmlEscape(params.tenantId)}"/>
|
||||
<variable name="b2bcall_extension_id" value="${xmlEscape(params.extensionId)}"/>
|
||||
${params.callGroup ? `<variable name="call-group" value="${xmlEscape(params.callGroup)}"/>` : ""}
|
||||
${params.callGroup ? `<variable name="callgroup" value="${xmlEscape(params.callGroup)}"/>` : ""}
|
||||
</variables>
|
||||
</user>
|
||||
</users>
|
||||
|
||||
Reference in New Issue
Block a user