Creando un honeypot con Kamailio

Kamailio es un proxy SIP derivado de un proyecto anterior llamado OpenSER. A parte de ser Open Source, es una magnífica herramienta, indispensable si deseas montar un sistema de VoIP medianamente grande, es decir, llegando más allá de las típicas centralitas Asterisk, FreeSwitch o similares que nos permiten gestionar una oficina con X extensiones.

Aprovecho para mandar un saludo a mi compañero Victor Seva (@linuxmaniac) que desde hace poco pertenece al equipo de desarrolladores de Kamailio. 😉

Como comentaba, un proxy SIP nos permite realizar muchísimas cosas, como validar usuarios, reenviar el tráfico a servidores SIP, gestionar llamadas, etc. En este caso lo vamos a utilizar con las mínimas opciones. Simplemente le permitiremos recibir y emitir llamadas, pero con alguna que otra peculiaridad. Nuestra finalidad será la de controlar los escanéos que nos hagan e interceptar todas las llamadas que intenten realizarse a través del sistema, es decir, colocar un honeypot para cazar listillos.

Evidentemente para entender todo esto hay que tener unos mínimos conocimientos de SIP. Si quieres instalar un Kamailio, puedes consultar la documentación aquí: http://www.kamailio.org/wiki/

En primer lugar vamos a editar el fichero kamailio.cfg y vamos a añadir a la ruta REQINIT lo siguiente:


if($ua == "friendly-scanner" || $ua == "sundayddr" || $ua == "sip-scan" || $ua == "iWar" || $ua == "sipsak") {
 xlog("L_ALERT", "Attack attempt! IP:$si:$sp - R:$ruri - F:$fu - T:$tu - UA:$ua - $rm\n");
}

Lo que conseguimos con esto es que nos alerte ante un escaneo usando los programas típicos. Por ejemplo, con SIPVicious:


$ python svmap.py 192.168.2.18

| SIP Device | User Agent | Fingerprint |
---------------------------------------------------------------------
| 192.168.2.18:5060 | kamailio (4.0.1 (x86_64/linux)) | disabled |

En el log del sistema (/var/log/kamailio) vemos esto:


ALERT: <script>: Attack attempt! IP:192.168.2.10:5060 - R:sip:100@192.168.2.18 -
 F:sip:100@1.1.1.1 - T:sip:100@1.1.1.1 - UA:friendly-scanner - OPTIONS

De esta forma nos enteraremos enseguida si alguien nos realiza un escaneo con svmap. De todas formas, siempre viene bien ocultar nuestra versión de kamailio. así que en el fichero de configuración añadiremos:


####### Global Parameters #########
user_agent_header="User-Agent: kamailio 1.0"
server_header="User-Agent: kamailio 1.0"

Y si repetimos la búsqueda:


$ python svmap.py 192.168.2.18

| SIP Device        | User Agent   | Fingerprint |
--------------------------------------------------
| 192.168.2.18:5060 | kamailio 1.0 | disabled    |

Una vez que somos capaces de detectar los escaneos, vamos a jugar un poco con el atacante. Lo primero de todo, le vamos a permitir registrarse en nuestro sistema con cualquier usuario y con cualquier contraseña. Para ello, en la ruta REQINIT añadiremos:


if (is_method("REGISTER")) {
	xlog("L_INFO", "Get Register for user $fU (IP: $si)\n");

	# aceptamos todas las peticiones de registro sin verificar la contraseña
	sl_send_reply("200","OK");
}

Lo que hacemos con esto es permitir el registro de cualquier usuario con cualquier contraseña. Para ello responderemos con un ‘200 OK’, permitiéndoles siempre registrarse.

Si pasamos svcrack podemos ver que siempre ‘descubre’ la contraseña de cualquier usuario:


$ python svcrack.py -u100 -r1-100 -z4 192.168.2.18

| Extension | Password      |
-----------------------------
| 100       | [no password] |

$ python svcrack.py -u324324243 -r1-100 -z4 192.168.2.18

| Extension | Password      |
-----------------------------
| 324324243 | [no password] |

El motivo por el que dice que no tiene contraseña es porque SIPVicious lo primero que prueba ante un ataque por fuerza bruta, es la contraseña vacía.

Si intentamos registrar un teléfono, veremos que se conecta sin problemas. Y en el log aparecerá algo así:


INFO: <script>: Get Register for user pepelux (IP: 192.168.2.16)
INFO: <script>: Get Register for user 200 (IP: 192.168.2.17)

Tras esto, vamos a registrarnos con el usuario 100 (por ejemplo) y lo que haremos será desviar todas las llamadas hacia nosotros. En la ruta REQINIT añadimos:


if (is_method("INVITE")) {
	xlog("L_INFO", "$fU is trying to call to $rU\n");
	$rU = "100"; # siempre nos llamará a nosotros
	xlog("L_INFO", "Sending call to $rU\n");
}

Y cuando se intente realizar una llamada a cualquier número nos sonará a nosotros:


INFO: <script>: pepelux (with IP:192.168.2.16) is trying to call to 666666666
INFO: <script>: Sending call to 100

INFO: <script>: 200 (with IP:192.168.2.17) is trying to call to 123456
INFO: <script>: Sending call to 100

¿Qué tal si en lugar de desviar la llamada a la extensión 100 la desviamos al número 091? 🙂

Y el fichero completo (kamailio.cfg) que he usado para las pruebas:

 


#!KAMAILIO
####### Include Local Config If Exists #########
import_file "kamailio-local.cfg"

####### Defined Values #########
#!define FLT_ACC 1
#!define FLT_ACCMISSED 2
#!define FLT_ACCFAILED 3
#!define FLT_NATS 5
#!define FLB_NATB 6
#!define FLB_NATSIPPING 7

####### Global Parameters #########
user_agent_header="User-Agent: kamailio 1.0"
server_header="User-Agent: kamailio 1.0"

### LOG Levels: 3=DBG, 2=INFO, 1=NOTICE, 0=WARN, -1=ERR
#!ifdef WITH_DEBUG
debug=4
log_stderror=yes
#!else
debug=2
log_stderror=no
#!endif

memdbg=5
memlog=5
log_facility=LOG_LOCAL0
fork=yes
children=4
port=5060
tcp_connection_lifetime=3605

####### Modules Section ########
# set paths to location of modules (to sources or installation folders)
#!ifdef WITH_SRCPATH
mpath="modules_k:modules"
#!else
mpath="/usr/local/lib/kamailio/modules_k/:/usr/local/lib64/kamailio/modules/"
#!endif

#!ifdef WITH_MYSQL
loadmodule "db_mysql.so"
#!endif

loadmodule "mi_fifo.so"
loadmodule "kex.so"
loadmodule "corex.so"
loadmodule "tm.so"
loadmodule "tmx.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "cfg_rpc.so"
loadmodule "mi_rpc.so"
loadmodule "acc.so"

# ----------------- setting module-specific parameters ---------------
# ----- mi_fifo params -----
modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo")

# ----- tm params -----
# auto-discard branches from previous serial forking leg
modparam("tm", "failure_reply_mode", 3)
# default retransmission timeout: 30sec
modparam("tm", "fr_timer", 30000)
# default invite retransmission timeout after 1xx: 120sec
modparam("tm", "fr_inv_timer", 120000)

# ----- rr params -----
# add value to ;lr param to cope with most of the UAs
modparam("rr", "enable_full_lr", 1)
# do not append from tag to the RR (no need for this script)
modparam("rr", "append_fromtag", 0)

# ----- registrar params -----
modparam("registrar", "method_filtering", 1)
/* uncomment the next line to disable parallel forking via location */
# modparam("registrar", "append_branches", 0)
/* uncomment the next line not to allow more than 10 contacts per AOR */
#modparam("registrar", "max_contacts", 10)
# max value for expires of registrations
modparam("registrar", "max_expires", 3600)
# set it to 1 to enable GRUU
modparam("registrar", "gruu_enabled", 0)

# ----- acc params -----
/* what special events should be accounted ? */
modparam("acc", "early_media", 0)
modparam("acc", "report_ack", 0)
modparam("acc", "report_cancels", 0)
/* by default ww do not adjust the direct of the sequential requests.
   if you enable this parameter, be sure the enable "append_fromtag"
   in "rr" module */
modparam("acc", "detect_direction", 0)
/* account triggers (flags) */
modparam("acc", "log_flag", FLT_ACC)
modparam("acc", "log_missed_flag", FLT_ACCMISSED)
modparam("acc", "log_extra", 
	"src_user=$fU;src_domain=$fd;src_ip=$si;"
	"dst_ouser=$tU;dst_user=$rU;dst_domain=$rd")
modparam("acc", "failed_transaction_flag", FLT_ACCFAILED)
/* enhanced DB accounting */

####### Routing Logic ########
# Main SIP request routing logic
# - processing of any incoming SIP request starts with this route
# - note: this is the same as route { ... }
request_route {
	# per request initial checks
	route(REQINIT);

	# NAT detection
	route(NATDETECT);

	# CANCEL processing
	if (is_method("CANCEL"))
	{
		if (t_check_trans()) {
			route(RELAY);
		}
		exit;
	}

	# handle requests within SIP dialogs
	route(WITHINDLG);

	### only initial requests (no To tag)
	t_check_trans();

	# record routing for dialog forming requests (in case they are routed)
	# - remove preloaded route headers
	remove_hf("Route");
	if (is_method("INVITE|SUBSCRIBE"))
		record_route();

	# dispatch requests to foreign domains
	route(SIPOUT);

	# handle registrations
	route(REGISTRAR);

	if ($rU==$null)
	{
		# request with no Username in RURI
		sl_send_reply("484","Address Incomplete");
		exit;
	}

	# user location service
	route(LOCATION);
}

route[RELAY] {
	# enable additional event routes for forwarded requests
	# - serial forking, RTP relaying handling, a.s.o.
	if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) {
		if(!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH");
	}
	if (is_method("INVITE|SUBSCRIBE|UPDATE")) {
		if(!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY");
	}
	if (is_method("INVITE")) {
		if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE");
	}

	if (!t_relay()) {
		sl_reply_error();
	}
	exit;
}

# Per SIP request initial checks
route[REQINIT] {
        # Si detectamos el User-Agent de un escáner conocido, mostramos una alerta
        if($ua == "friendly-scanner" || $ua == "sundayddr" || $ua == "sip-scan" || $ua == "iWar" || $ua == "sipsak") {
                xlog("L_ALERT", "Attack attempt! IP:$si:$sp - R:$ruri - F:$fu - T:$tu - UA:$ua - $rm\n");
        }

        # Al recibir un intento de registro, devolvemos siempre un OK
	if (is_method("REGISTER")) {
		xlog("L_INFO", "Get Register for user $fU (IP: $si)\n");

		if ($fU != "100")
			sl_send_reply("200","OK");
	}

        # Al recibir un intento de llamada, modificamos el destino para que siempre llame a la extensión 100
	if (is_method("INVITE")) {
		xlog("L_INFO", "$fU (with IP:$si) is trying to call to $rU\n");
		$rU = "100";
		xlog("L_INFO", "Sending call to $rU\n");
	}

	if (!mf_process_maxfwd_header("10")) {
		sl_send_reply("483","Too Many Hops");
		exit;
	}

	if(!sanity_check("1511", "7"))
	{
		xlog("Malformed SIP message from $si:$sp\n");
		exit;
	}
}

# Handle requests within SIP dialogs
route[WITHINDLG] {
	if (has_totag()) {
		# sequential request withing a dialog should
		# take the path determined by record-routing
		if (loose_route()) {
			route(DLGURI);
			if (is_method("BYE")) {
				setflag(FLT_ACC); # do accounting ...
				setflag(FLT_ACCFAILED); # ... even if the transaction fails
			}
			else if ( is_method("ACK") ) {
				# ACK is forwarded statelessy
				route(NATMANAGE);
			}
			else if ( is_method("NOTIFY") ) {
				# Add Record-Route for in-dialog NOTIFY as per RFC 6665.
				record_route();
			}
			route(RELAY);
		} else {
			if (is_method("SUBSCRIBE") && uri == myself) {
				# in-dialog subscribe requests
				exit;
			}
			if ( is_method("ACK") ) {
				if ( t_check_trans() ) {
					# no loose-route, but stateful ACK;
					# must be an ACK after a 487
					# or e.g. 404 from upstream server
					route(RELAY);
					exit;
				} else {
					# ACK without matching transaction ... ignore and discard
					exit;
				}
			}
			sl_send_reply("404","Not here");
		}
		exit;
	}
}

# Handle SIP registrations
route[REGISTRAR] {
	if (is_method("REGISTER"))
	{
		if(isflagset(FLT_NATS))
		{
			setbflag(FLB_NATB);
			# uncomment next line to do SIP NAT pinging 
			## setbflag(FLB_NATSIPPING);
		}
		if (!save("location"))
			sl_reply_error();

		exit;
	}
}

# USER location service
route[LOCATION] {
	$avp(oexten) = $rU;
	if (!lookup("location")) {
		$var(rc) = $rc;
		t_newtran();
		switch ($var(rc)) {
			case -1:
			case -3:
				send_reply("404", "Not Found");
				exit;
			case -2:
				send_reply("405", "Method Not Allowed");
				exit;
		}
	}

	# when routing via usrloc, log the missed calls also
	if (is_method("INVITE"))
	{
		setflag(FLT_ACCMISSED);
	}

	route(RELAY);
	exit;
}

# Caller NAT detection route
route[NATDETECT] {
#!ifdef WITH_NAT
	force_rport();
	if (nat_uac_test("19")) {
		if (is_method("REGISTER")) {
			fix_nated_register();
		} else {
			add_contact_alias();
		}
		setflag(FLT_NATS);
	}
#!endif
	return;
}

# RTPProxy control
route[NATMANAGE] {
#!ifdef WITH_NAT
	if (is_request()) {
		if(has_totag()) {
			if(check_route_param("nat=yes")) {
				setbflag(FLB_NATB);
			}
		}
	}
	if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB)))
		return;

	rtpproxy_manage();

	if (is_request()) {
		if (!has_totag()) {
			add_rr_param(";nat=yes");
		}
	}
	if (is_reply()) {
		if(isbflagset(FLB_NATB)) {
			add_contact_alias();
		}
	}
#!endif
	return;
}

# URI update for dialog requests
route[DLGURI] {
#!ifdef WITH_NAT
	if(!isdsturiset()) {
		handle_ruri_alias();
	}
#!endif
	return;
}

# Routing to foreign domains
route[SIPOUT] {
	if (!uri==myself)
	{
		append_hf("P-hint: outbound\r\n");
		route(RELAY);
	}
}

# manage outgoing branches
branch_route[MANAGE_BRANCH] {
	xdbg("new branch [$T_branch_idx] to $ru\n");
	route(NATMANAGE);
}

# manage incoming replies
onreply_route[MANAGE_REPLY] {
	xdbg("incoming reply\n");
	if(status=~"[12][0-9][0-9]")
		route(NATMANAGE);
}

# manage failure routing cases
failure_route[MANAGE_FAILURE] {
	route(NATMANAGE);

	if (t_is_canceled()) {
		exit;
	}
}

 

Saludos

 

6 comentarios

  1. Que buena publicacion, lo instalare, lo probare, y posteriormente, lo comentare en el sitio web busy-tone.org

    Muchas Gracias, muy bueno.

  2. Hola, precioso tutorial. Solo tengo una duda que igual y espero puedas aclararme. Mi pregunta es: que beneficio economico tiene un atacante al robar minutos? Te lo menciono por que en mis tiempos de novato (que aun lo soy, solo que no tanto) me «vaciaron» varios servidores (lo bueno es que los tenia con prepago). Ya que realmente no entiendo que ocurre, de hecho en esos servidores que me comprometieron se guardaron las grabaciones, y solo se escuchaban ruidos «raros». Como dato de donde me metieron el «gol» fue de Sudafrica.

    Saludos!

  3. Hola dexter

    El beneficio es claro. Imagina que yo vendo minutos y la salida en lugar de usando un proveedor de pago la hago usando tus cuentas hackeadas .. a coste 0 para mi y todo beneficios

  4. Aunque el artículo tiene un tiempo, aprovecho para comentar que las cuentas voip hackeadas también se utilizan para llamar a números premium y obtener buenos beneficios en metálico. Un saludo!

Deja un comentario