Un baneador wtf

Temas sobre programación ( php, c, sql, html, perl, python, ruby, java, bash, etc ) y recursos ( herramientas, frameworks, hosting, cms, etc )

Moderadores: akodo, maiku

Responder
MetTxin
Forista Medio
Forista Medio
Mensajes: 332
Registrado: Mié Abr 27, 2011 11:32 am

Un baneador wtf

Mensaje por MetTxin » Sab Mar 30, 2013 5:03 pm

No sabía dónde dejarlo, si en proyectos o aquí, pero como en realidad tampoco es un proyecto en sí sino un script único pues me he decidido por la sección «Programación»

Dejo un script en perl que sirve para banear mediante iptables a quienes incordien. Su funcionamiento es sencillo, revisa los logs del sistema que le digas y banea ips en función de coincidencias de cadenas en las líneas de dichos logs, destinado a servidores caseros.

En principio está destinado a quien tenga un poco experiencia, hay que saber un poco de perl y expresiones regulares, más que nada para saber qué se quiere machear y banear. Cada sistema es un mundo y es difícil adaptar algo para cualquiera. Tampoco está destinado para servidores de gran tráfico sino para los caseros, el típico que tiene el ssh y un par de servicios para uso propio o de poco tráfico. Para servidores serios hay otras herramientas tipo fail2ban (creo que se llama así) mucho más apropiadas.

El script (uno parecido) lo tengo corriendo desde hace más de 1 año en mi servidor (lo lanzo con cron cada minuto) y me ha quitado de encima a docenas de bots, principalmente para ssh y postfix, nada simpáticos.

Si alguien quiere más aclaraciones estoy dispuesto a darlas,

Un saludo!

Nota. Se requiere la utilidad «geoiplookup» si se quiere usar para saber de dónde viene la ip, si no se instala (es un pequeño paquete) hay que modificar el script

Código: Seleccionar todo

#!/usr/bin/perl 
# -------------------------------------------------------
# Script para vigilar logs del sistema relacionados con
# entradas desde internet hacia ssh, postfix, webserver..
#
# Funciona de forma que sólo lee las líneas nuevas que haya
# desde la última lectura, las pasa por expresiones regulares
# y en caso de encontrar alguna cadena sospechosa predefinida 
# recupera si puede la ip de origen y la guarda en un hash
# junto con el número de intentos sospechosos ya encontrados
# para la misma ip.
# Si el número de intentos sobrepasa el límite de aguante
# banea con iptables al que incordia (si no lo está ya).
use strict;
use warnings;
# -------------------------------------------------------
# ---- CONFIGURACION ------------------------------------
# -------------------------------------------------------
# archivos para registrar los baneados y el log general
my $file_baneados = 'banwtf.log';
my $file_loggral  = 'vigwtf.log';
# array de logs a vigilar, añadir los que se deseen
my @logs = ();
$logs[0] = '/var/log/auth.log';
$logs[1] = '/var/log/maillog';
# número máximo de puntos de aguante
# 5 sería poco aguante, 20 aguante medio, 50 aguante top
# por defecto se irrita enseguida
my $aguante = 5;
# ips que no hay que banear, ejemplos
my @allows =  qw('192.168.1.10' '192.168.1.18');
# para modo test poner a 1, para activar wtf poner a 0
my $test = 1;
# -------------------------------------------------------
# ---- REGLAS -------------------------------------------
# -------------------------------------------------------
# hash de frases WTF y expresiones regulares.
# el principio es simple, busca en la línea de log una
# cadena sospechosa, wtf, le pasa el regexp para ver si
# puede sacar la ip de origen y si es así la penaliza con
# el valor que se le dé
my %hash_wtf = (
    '0001' => {  # ssh
        'wtf' => 'Invalid user',          # cadena WTF
        'reg' => '(\d+\.\d+\.\d+\.\d+)$', # regexp
        'pen' => 5,                       # penalización
        'act' => 1,                       # activo o no
    },
    '0002' => {  # ssh
        'wtf' => 'User root from \d+\.\d+\.\d+\.\d+ not',
        'reg' => 'User root from (\d+\.\d+\.\d+\.\d+) not',
        'pen' => 10,
        'act' => 1,
    },
    '0003' => {  # ssh
        'wtf' => 'Did not receive identification string from \d+\.\d+\.\d+\.\d+',
        'reg' => 'Did not receive identification string from (\d+\.\d+\.\d+\.\d+)',
        'pen' => 10,
        'act' => 1,
    },
    '0004' => {  # postfix
        'wtf' => 'tried to use disabled plaintext auth',
        'reg' => 'rip\=(\d+\.\d+\.\d+\.\d+),',
        'pen' => 10,
        'act' => 1,
    },
    '0005' => {  # postfix
        'wtf' => 'Relay access denied',
        'reg' => '\[(\d+\.\d+\.\d+\.\d+)\]:',
        'pen' => 2,
        'act' => 1,
    },
    '0006' => {  # postfix
        'wtf' => 'non-SMTP command from',
        'reg' => '\[(\d+\.\d+\.\d+\.\d+)\]:',
        'pen' => 10,
        'act' => 1,
    },
    '0007' => {  # postfix
        'wtf' => 'Connection rate limit exceeded',
        'reg' => '\[(\d+\.\d+\.\d+\.\d+)\]:',
        'pen' => 10,
        'act' => 1,
    },
    '0008' => {  # postfix
        'wtf' => 'Connection concurrency limit exceeded',
        'reg' => '\[(\d+\.\d+\.\d+\.\d+)\]:',
        'pen' => 10,
        'act' => 1,
    },
    '0009' => {  # postfix
        'wtf' => 'therichsheickc',
        'reg' => '\[(\d+\.\d+\.\d+\.\d+)\]:',
        'pen' => 10,
        'act' => 1,
    },
);
# -------------------------------------------------------
# ---- CUERPO DEL SCRIPT --------------------------------
# -------------------------------------------------------
# coge la opción de nivel de log --log
my $opt = $ARGV[0];
# si no hay level_log se establece a 2
$opt = "--log=2" unless $opt;
my $level_log = ($opt =~m/=(\d)$/,$1);
# por si acaso,
$level_log = 1 unless $level_log;
# nivel 2 para el test
$level_log = 2 if $test;

# hora de arranque del script, se loguea si level_log > 1
loguear ("#### Inicio script ####") if $level_log > 1;
# recupero el número de líneas a leer de cada log a vigilar
my @num_lin  = (); # líneas de cada log
my $cont     = 0;  # contador
my $lin_tot  = 0;  # líneas totales
foreach (@logs) {
    # comprueba que existe el archivo log antes de contar líneas
    print "No existe el archivo log $_\n" and next if (!-f $_);
    $num_lin[$cont] = devuelve_num_lineas_a_leer($logs[$cont]);
    $lin_tot += $num_lin[$cont];
    $cont++;
}
# sale si no hay nada nuevo que revisar
if ($lin_tot == 0) {
    loguear ("#### Fin script ####") if $level_log > 1;
    exit;
}
# sigo si hay algo, ....
loguear ("## leyendo $lin_tot líneas ##") if $level_log > 0;

# construyo la cadena de comando
my $cadena_cmd = "";
my @res = ();
$cont = 0;
my $and = "";
foreach (@logs) {
    $and = ' && ' if $cont > 0;
    $cadena_cmd .= $and .'tail -n ' . $num_lin[$cont] . ' ' . $logs[$cont];
    $cont++;
}
# lanza la cadena comando y recupero la salida en un array
@res = `$cadena_cmd`;
# recupera el hash de baneados guardado en la anterior lectura
# de esta forma no pierde los intentos anteriores
my %hash_cazados = devuelve_hash();
my $cont_lineas  = 0;
# revisa las líneas devueltas
foreach (@res) {
    my $cazado;
    my $linea = $_;
    foreach my $regla (sort keys %hash_wtf){
        if ($linea =~ m/$hash_wtf{$regla}{wtf}/ and $hash_wtf{$regla}{act}==1){
            $cazado = ($linea =~m/$hash_wtf{$regla}{reg}/,$1);
            $hash_cazados{$cazado}{intentos} += $hash_wtf{$regla}{pen} if $cazado;
            loguear ("wtf: $hash_wtf{$regla}{wtf}") if $level_log > 1;
            last;
        }
    }
    # si hay algún intruso...
    if ($cazado) {
        $hash_cazados{$cazado}{ip} = $cazado;
        loguear ("Cazado: $cazado Intentos: $hash_cazados{$cazado}{intentos} Linea: $_") if $level_log > 0;
    }
    # cuenta las líneas que va leyendo
    $cont_lineas++;
}

# loguea el número de líneas leídas totales si level_log > 1
loguear ("Líneas leídas: $cont_lineas") if $level_log > 1;

# ahora pasamos a banearlos si es necesario ...
foreach my $ip (sort keys %hash_cazados){
    # nos saltamos algunas ips que no queramos banear nunca...
    next if ("@allows" =~m /$ip/);
    # repasa el hash, si alguna ip tiene más de $aguante intentos sigue
    if ($hash_cazados{$ip}{intentos} > $aguante ) {
        # si ya está anotada como baneada salta al siguiente
        next if $hash_cazados{$ip}{baneado};
        # se asegura así y todo que no esté en las reglas de iptables
        my $esta_o_no = "-";
		$esta_o_no = `iptables -nL | grep $ip` if (!$test);
        # si no está ...
		if ($esta_o_no !~m/$ip/) {
			# no está en las reglas, se mira origen y 
            # se escribe en el log de baneados
            my $origen = `geoiplookup $ip | head -n 1 | sed -e 's/^.*://'`;
            chomp ($origen);
            open "FILE",">>","$file_baneados";
			my $hora = `date +"%d-%m-%Y %H:%M:%S"`;chomp($hora);
			print FILE "#$hora Baneado: $origen\n$ip\n";
            close (FILE);
            if (!$test) {
                my $comando_baneo_input = "iptables -I INPUT -p all -s $ip -j DROP";
                my $salida_baneo_input = `$comando_baneo_input`;
                my $comando_baneo_output = "iptables -I OUPUT -p all -d $ip -j DROP";
                my $salida_baneo_output = `$comando_baneo_output`;
            }

            # se escribe en el log de baneador
            loguear ("Baneado: $ip , intento: $hash_cazados{$ip}{intentos}") if $level_log > 0;
            # coloco el flag baneado a 1 para que no intente banearlo de nuevo
            $hash_cazados{$ip}{baneado} = 1;
		}
    }
    else {
        # si ya está baneada ni caso
        next if $hash_cazados{$ip}{baneado};
        # avisando
        if ($hash_cazados{$ip}{intentos} > 1){
            loguear ("Hacia el baneo: $ip , intentos: $hash_cazados{$ip}{intentos}") if $level_log > 1;
        };
    }
}
# guarda el hash de cazados para leerlo la próxima vez
guarda_hash(%hash_cazados);

sub devuelve_num_lineas_a_leer {
    my $archivo_log = shift;
    # recupera el número de líneas actual
    my $lineas_actuales = `wc -l $archivo_log | sed -e 's/ .*\$//'`;
    chomp($lineas_actuales);
    # coge el nombre del archivo para apuntar después última línea
    my $nom_file = ($archivo_log =~ m/.*\/(.*)$/,$1);
    # coge la última línea leída
    my $ultima_linea_leida = 1;
    $ultima_linea_leida = `cat .ult_$nom_file` if (-f ".ult_$nom_file");
    chomp ($ultima_linea_leida) if $ultima_linea_leida;
    # hace el cálculo de líneas a leer, primero mira si no se ha
    # rotado y es más corto que en la anterior lectura
    $ultima_linea_leida = 1 if ($lineas_actuales < $ultima_linea_leida);
    my $num_lin = $lineas_actuales - $ultima_linea_leida;

    # escribe el número total de líneas actuales para la próxima lectura
    open "FILE",">",".ult_$nom_file";print FILE $lineas_actuales;close (FILE);

    loguear ("Lineas en $nom_file $lineas_actuales, última leída anterior: $ultima_linea_leida, líneas a leer: $num_lin") if $level_log > 1;
    return $num_lin;
}
sub devuelve_hash {
    my %hash_guardado ;
    # si no hay file no hay hash todavía
    return %hash_guardado if !(-f (".hash.dat"));
    my @contenido = `cat .hash.dat`;
    foreach (@contenido) {
        next if ($_ =~ m/^#/);
        next if ($_ eq "");
        my ($ip,$intentos) = split(" ",$_);
        $hash_guardado{$ip}{ip} = $ip;
        $hash_guardado{$ip}{intentos} = $intentos;
        $hash_guardado{$ip}{intentos} = 0 unless $intentos;
        $hash_guardado{$ip}{baneado} = 0;
    }
    return %hash_guardado;
}
sub guarda_hash {
    my %hash_ips = @_;
    open "FILE",">",".hash.dat";
    foreach my $ip (sort keys %hash_ips){
        # no guarda los ya baneados en el hash
        next if ($hash_ips{$ip}{baneado});
        print FILE "$hash_ips{$ip}{ip} $hash_ips{$ip}{intentos}\n";
    }
    close (FILE);
}
sub loguear {
    my $rec_log = shift;
    chomp ($rec_log) if $rec_log =~/\n/;
    my $hora = `date +"%d-%m-%Y %H:%M:%S:%N"`;chomp($hora);
    open "FILE",">>","$file_loggral";
    print FILE "## $hora # $rec_log\n";
    close (FILE);
}
loguear ("## saliendo ##") if $level_log > 1;
loguear ("#### Fin script ####") if $level_log > 1;

editado: corregido algún fallo.
Última edición por MetTxin el Dom Abr 14, 2013 8:50 pm, editado 1 vez en total.
Avatar de Usuario
Ayax
Administrador
Administrador
Mensajes: 3390
Registrado: Jue Ene 01, 1970 2:00 am
Ubicación: León, Guanajuato; México.
Contactar:

Re: Un baneador wtf

Mensaje por Ayax » Sab Mar 30, 2013 7:18 pm

Hola, MetTxin:

Muy bueno para hacer pruebas y quizá para adaptarlo en ambientes más productivos.

Te lo enlazo a portada para que no se pierda ;-)

un saludo.
No hay nada que agradecer. Hago, lo tengo que hacer.
Reglamento del foro | Temas más preguntados | Twitter: @pacorevilla
MetTxin
Forista Medio
Forista Medio
Mensajes: 332
Registrado: Mié Abr 27, 2011 11:32 am

Re: Un baneador wtf

Mensaje por MetTxin » Sab Mar 30, 2013 8:28 pm

Gracias Ayax,

Me han quedado cosas por decir y explicar pero bueno, como va dirigido a usuarios de nivel medio pues ¡que pregunten! :)

Un saludo!
Avatar de Usuario
Fanton
Forista Distinguido
Forista Distinguido
Mensajes: 1339
Registrado: Jue Ene 08, 2009 8:00 am
Ubicación: Magdalena [Argentina]
Contactar:

Re: Un baneador wtf

Mensaje por Fanton » Dom Mar 31, 2013 12:25 am

Hola MetTxin, la verdad que es un excelente aporte, felicitaciones
Diplomacia, es el arte de saber lo que no se debe decir...
_________________
Hardware: Intel i5-3570k | ASUS P8H77-M |HD [500GB] [1 TB] | Ram 8 GB | GPU: Nvidia GeForce 210/1 GB
S.O. Debian@testing x86_64 Openbox
Linux user #506272
ercros
Forista Nuevo
Forista Nuevo
Mensajes: 16
Registrado: Dom Abr 14, 2013 2:47 am

Re: Un baneador wtf

Mensaje por ercros » Dom Abr 14, 2013 6:29 pm

lo que tengo yo es este pequeño script que su cometido es matar cualquier proceso de ssh en terminal como en nautilus (supuestamente,mas adelante lo explico)

Código: Seleccionar todo

#!/bin/bash
#script realizaco por Dani Bellver Martinez
#correo : dani_chella@hotmail.com
#script en bucle que mata cualquier proceso relacionado con ssh tanto en terminal como por nautilus



read -p "para activar el antissh introduzca su
contraseña: " pass



lugar="$HOME/logs/log_antissh.txt"

if [ ! -f $lugar ];then
	clear
	echo "se ha generado un log en la siguiente ubicacion :"
	echo "$HOME/logs/log_antissh.txt"
	mkdir $HOME/logs/
	touch $HOME/logs/log_antissh.txt 
	sleep 2

	
else
	clear
	echo "el log_antissh.txt se encuentra en:"
	echo "$lugar"
	sleep 2
fi

sleep 2
clear
while true;do
pid=`ps aux|grep "\[priv\]"|tr -s " "|cut -f2 -d" "`



	for iii in $pid;do
		
		
		
	        sudo kill $iii <<< `echo $pass` 2>/dev/null
		if [ $? -eq 0 ];then
			echo "Intento de ssh abortado."
			echo "Proceso eliminado : ssh"
			echo "Fecha             : `date +%d/%m/%y`"
			echo "Hora		  : `date +%H:%M`"
			echo "PID		  : $iii"
			echo "------------------------------------" 
			echo "Intento de ssh abortado." >> $lugar
			echo "Proceso eliminado : ssh" >> $lugar
			echo "Fecha             : `date +%d/%m/%y`" >> $lugar
			echo "Hora		  : `date +%H:%M`" >>  $lugar
			echo "PID		  : $iii" >> $lugar
			echo "------------------------------------" >>  $lugar
		else
			echo "se ha producido un error, posiblemente sea que"
			echo " la contraseña introducida sea erronea"
			echo "------------------------------------"
		fi
	
	done
pid=""
done
------------
digo supuestamente porque es la unica cadena de texto "\[priv\]" que he encontrado para hacerle un grep que me busque los pid de ssh nautilus, aunque tambien mata los de la terminal... aunque parece peligroso porque si no escapo los corchetes mata todos los procesos y la pantalla se queda vacia, y no puedes hacer nada.
-----------
tengo otro que solo es para ssh terminal que te dice la ip de quien te intento hacer ssh. lo tengo en la clase, el lunes me lo traigo a casa y el martes por la mañana lo publico
MetTxin
Forista Medio
Forista Medio
Mensajes: 332
Registrado: Mié Abr 27, 2011 11:32 am

Re: Un baneador wtf

Mensaje por MetTxin » Dom Abr 14, 2013 8:48 pm

Gracias por compartir el código ercros.

Me entra alguna duda, ¿cómo distingues un usuario autorizado de uno que no lo esté? No veo claro ese punto, podría matar un proceso abierto por ti mismo, y si no se distinguen ¿no bastaría con desactivar el demonio ssh y ya está?

Otra cosa, una idea, para evitar mezclar procesos (macheando aquellos que no tienen nada que ver) podrías usar «who -up», incluso ganarías algo de tiempo (aunque me queda la duda si serviría para los procesos abiertos por nautilus, no uso nautilus)

un saludo y gracias de nuevo por compartir!
ercros
Forista Nuevo
Forista Nuevo
Mensajes: 16
Registrado: Dom Abr 14, 2013 2:47 am

Re: Un baneador wtf

Mensaje por ercros » Dom Abr 14, 2013 9:22 pm

1ª -pregunta- realmente no distingue nada... no se porque con el \[priv\] mata los procesos ssh, no he hecho muchas pruebas al respecto.
para distinguir supongo que comparando los usuarios de /etc/passwd y de ps aux (tipo filtro) y lo que no coincida con /etc/passwd que mate su proceso bastaria.
2ª -pregunta- si jajajaja, pero estas tonterias vienen de perlas para practicar
con who tengo otro...(solo por terminal) que el martes lo publicare que te muestra la ip del intruso y mata su proceso..

soy aun un novato en este mundo, entonces estoy limitado jeje
gracias a ti por contestar...:)
Responder