Hola Visitante

Mostrar Mensajes

Esta sección te permite ver todos los posts escritos por este usuario. Ten en cuenta que sólo puedes ver los posts escritos en zonas a las que tienes acceso en este momento.


Temas - Berni69

Páginas: [1] 2
1
Noticias del Blog / LibBeat en PHP - Creando nuestro "log-shipper".
« en: Febrero 02, 2019, 07:25:00 pm »

En mi último proyecto me he visto en la necesidad de enviar datos desde una apliación PHP contra un elasticsearch que tenía montado. Los requisitos  del proyecto eran:





  • Insertar trazas operacionales en ElasticSearch
  • Usar el protocolo Beats en un puerto específico
  • Hacerlo en PHP




Qué es ElasticSearch ?





ElasticSearch es un motor de búsqueda basado en Lucene, provee capacidad de búsqueda de texto avanzada, distribuida, y multi tenant. Proporciona fiabilidad, escalabilidad y proporciona múltiples formas de acceso a sus datos.  Lucene es una base de datos no relacional (NoSql) de alto rendimineto hecha en Java y orientada a documentos, almacena los documentos en forma de JSON.  Habitualmente, cuando se hace referencia a ElasticSearch se habla del stack de elasticsearch (ELK/BELK):









  • ElasticSearch: Base de datos no relacional dónde se almacena información.
  • Logstash: Procesa y prepara los documentos para insertarlos dentro de elasticsearch.
  • Kibana: Herramienta de visualización de datos.
  • Beats: Envía logs a la plataforma ELK para que sean indexados en elasticsearch.




ELK Stack
Flujo de información ELK




¿Qué es Beats?





Como ya se ha comentado en el punto anterior, Beats es el encargado de enviar información a elasticsearch. Más que una pieza de software es un concepto, todos los shippers de la familia beats tienen en común que están escritos en Go y utilizan la librería libbeat para implementar el protocolo lumberjack v2 utilizado entre logstash y los shippers beats.  Existen múltiples shippers dentro de la família de beats. Los más comunes son:





  • Filebeat: Lee información de archivos de texto planos y los envía a logstash.
  • WinLogBeat: Envía logs de windows al Stack ELK.
  • PacketBeat: Captura paquetes en la red y los envía a ELK.
  • HeartBeat: Envía paquetes para saber si una máquina esta activa o no.
  • MetricBeat: Colecciona métricas de salud de las máquinas donde esta instalado y lo envía a ELK.
  • Y muchos más implementados por la comunidad




¿Cómo funciona Beats?





Tras mucho buscar no encontré nada en la red que me pudiera servir, todo el mundo terminaba usando el protocolo HTTP contra la API de Elasticsearch o contra una instancia de Logstash. Por esta razón, decidí investigar como funcionaba el protocolo de Beats. Este software implementa un protocolo conocido como Lumberjack en su segunda versión. Es un protocolo desarrollado inicialmente para logstash pero extendido y mejorado para ser más eficiente en su segunda versión.





La única información que encontré es el siguiente archivo en Github https://github.com/logstash-plugins/logstash-input-beats/blame/master/PROTOCOL.md#L12, dónde se explica vagamente el tipo de tramas que componen el protocolo pero no su uso.





Un paquete de Beats puede contener más de una trama la descripción básica de la trama es la siguiente:





### Framing

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +---------------+---------------+-------------------------------+
     |   version(1)  |   frame type  |     payload ...               |
     +---------------------------------------------------------------+
     |   payload continued...                                        |
     +---------------------------------------------------------------+



Cada trama es autocontenida y no está relacionada con la anterior. Para poder enviar una trama es necesario enviar una trama del tipo “Window” especificando el número de tramas de que se enviarán en el mismo paquete, si no se envía en el mismo paquete logstash no puede recontruir la información.  Despues de la trama window se añadirán tramas de datos (Json, Gzip, Data,..), por lo que un paquete de 3 mensajes tendría la siguiente forma:





|W(3)J(Payload1)J(Payload2)J(Payload3)|



Tras este mensaje el servidor debe responder con un ack y con el último mensaje procesado:





|A(3)|



Cómo no encontré nada hecho en PHP en GitHub decidí implementar mi propia implementación de lumberjack v2. La podéis encontrar en





https://github.com/berni69/libbeat-php





O si usáis composer, podéis instalarla con:





composer require berni69/libbeat



Una vez integrada en el proyecto, es tan sencilla de usar como:





use libbeat\BeatSender;
$beat = new  BeatSender('192.168.26.12', 5044);
$beat->send("test_log");
$beat->set_compression_rate(0);
$beat->send(["test_log2", "test_log3"]);



Este sencillo código permite enviar a logstash los mensajes “test_log”, test_log2 y test_log3 mediante el protocolo beats.





El caso típico de uso es enviar las excepciones de PHP al stack de elastic para poder depurar en caso de problemas. Esto es muy útil si se crea una cola dónde se van añadiendo todas las excepciones y un proceso asíncrono va procesando la cola y la va volcando a elastic usando el código anterior para no penalizar la ejecución de PHP.








2
Noticias del Blog / Security Headers en Wordpress
« en: Febrero 02, 2019, 02:28:15 pm »

Esta breve entrada tratará de enseñar como se debe configurar de manera segura una instalación de wordpress. El requisito para que esto funcione debe ser tener wordpress con un certificado seguro (SSL/TLS) configurado.





¿Qué son los security headers o encabezados/cabeceras de seguridad?





Son extensiones que proporciona el consorcio de W3C para proteger al usuario contra ataques malintencionados al navegador de un cliente. Este tipo de cabeceras son eficaces para bloqeuar recursos de terceros que no se cargan debidamente, o de dominios no confiables. Otras impiden que la web se cargue como un iframe o incrustada dentro de algún dipo de contenedor para evitar clickjacking (robo de clicks).





Una vez sabemos para que son las security headers, deberemos editar el archivo .htaccess  de nuestro wordpress:





Y añadir el siguiente bloque.





Options +FollowSymLinks -Indexes
php_flag display_errors Off
php_flag session.cookie_httponly on
php_flag session.cookie_secure on

RewriteCond %{REQUEST_METHOD} ^(TRACE|DELETE|TRACK) [NC]
RewriteRule ^(.*)$ - [F,L]

SetEnvIf Host ^www.bitsdelocos.es$ is_main_domain
Header set X-Content-Type-Options nosniff env=is_main_domain
Header set X-XSS-Protection "1; mode=block" env=is_main_domain
Header unset X-Powered-By env=is_main_domain
Header unset X-Pingback env=is_main_domain
Header unset SERVER env=is_main_domain
Header set Content-Security-Policy "default-src 'self' https: data: wss: 'unsafe-inline' 'unsafe-eval';" env=is_main_domain
Header set strict-transport-security "max-age=300; preload" env=is_main_domain





Las 4 primeras líneas impiden que se listen archivos en las carpetas que no poseen ningun index.php que cargue por defecto, se muestren errores que pueden exponer información sensible del servidor al haber un error o que se obtenga información de debug con los métodos trace y track del servidor apache.





La línea de SetEnvIf es útil si tenemos subdominios sin SSL y no queremos que esta configuración se herede en ellos, en caso de que todo esté securizado se puede eliminar y quitar “env=is_main_domain” de las demás.  





Con estas modificaciones se consigue añadir los headers necesarios para poder tener un wordpress seguro en la capa de “navegador”, y ocultar información sensible del servidor web que pudiera dar pistas a un atacante de nuestra infraestructura.








3
Noticias del Blog / Automatizando ACLs en SQUID
« en: Enero 03, 2019, 09:47:03 pm »

Buenas noches en esta entrada os traigo una forma muy sencilla de automatizar el uso de SQUID como proxy de nagevación temporal.





¿Qué es un proxy de navegación?I





Un proxy, o servidor proxy, en una red informática, es un servidor  —programa o dispositivo—, que hace de intermediario en las peticiones  de recursos que realiza un cliente (A) a otro servidor (C) (Wikipedia).





Proxy inverso/ Reverse Proxy
Arquitectura de un proxy inverso








Los proxys web son dispositivos que se utilizan para permitir, acelerar, inspeccionar  o  cachear peticiones a recursos web (HTTP/HTTPS). Esto permite reducir el ancho de banda usado, acelerar la navegación, o modificar contenido en función de reglas de capa 7. Este comportamiento es muy útil para poder controlar qué el tráfico que esta circulando por la red. Existen varios tipos de proxy según su arquitectura:





  • Proxys transparentes: Este tipo de proxys se añade de forma transparente en el flujo de la conexión de internet y el usuario sin que este tenga que configurar nada en su equipo (Las direcciones IP de origen y de destino no son alteradas)
  • Reverse Proxys: Este tipo de proxys instalan entre la entrada de internet y los servidores web, su funcionamiento se basa en cachean peticiones estáticas para liberar a los servidores web de tener que procesarlas cada vez que les llegan.
  • Forward Proxys: Este tipo d e proxys de navegación se encargan de cachear, aplicar ACLs, inspección de tráfico,.. pero es el usuario final que debe configurar el equipo para reenviar las peticiones al proxy.




Forward Proxy
Arquitectura del ForwardProxy




¿Qué es squid?





SQUID es un proxy open source (http://www.squid-cache.org/) pensado para optimizar el uso de la web implementando cachés de contenido. Puede funcionar tanto como reverse proxy como forward proxy, es decir, sirve para optimizar la navegación de internet como para mejorar el delivery de una página web.





Para instalar SQUID en Centos 7 basta con ejecutar :





    yum -y install epel-release
    yum -y update
    yum clean all
    yum -y install squid
    systemctl start squid
    systemctl enable squid



De este modo tendremos SQUID funcionando accesible desde todas las redes privadas, para una una instalación doméstica es suficiente pero si se desea hilar más fino, squid necesita algunos tuneos. Para que no se pueda conectar todo el mundo ni acceder a todo el contenido SQUID incorpora varias ACLs modificables a través de su configuración. (/etc/squid/squid.conf)






# Example rule allowing access from your local networks.
# Adapt to list your (internal) IP networks from where browsing
# should be allowed
acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
acl localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
acl localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
acl localnet src fc00::/7       # RFC 4193 local private network range
acl localnet src fe80::/10       # RFC 4291 link-local (directly plugged) machines




¿Porqué no deberíamos dejar todos los rangos privado abiertos?





En una empresa, no todos los segmentos de red deben tener acceso libre a internet puesto que durante un ataque, simplifica mucho la instalación de software malicioso, el control del malware y la posible exfiltración de información.





¿Cómo securizarlo?





En una época donde la agilidad no es un mérito sino un requisito, es necesario implementar una solución que pueda ser ágil sin comprometer la integridad de los sistemas de la información. Por esta razón, tener que editar el archivo de configuración cada vez que un servidor debe conectarse es no es funcional. Por suerte, SQUID implementa un tipo de ACLs para llamar a scripts/binarios externos:





external_acl_type acl_ext ttl=10 %SRC /etc/squid/acl_ext.py
acl ExtAcl external acl_ext
http_access allow ExtAcl



Con estas tres líneas, SQUID será capaz de comunicarse con un componente externo enviando la dirección IP de origen, en este caso un script de Python que se conectará a una DB MySql dónde estarán las reglas de acceso:





#!/usr/bin/python

import mysql.connector
import sys
import logging

logger = logging.getLogger('squid_auth')
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('/var/log/squid_ext.log')
fh.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)


def match_acl(src):
    """USAGE:The function returns True if the user and passwd match False otherwise"""
    # Write your own function definition.
    # Use mysql, files, /etc/passwd or some service or whatever you want
    mydb = mysql.connector.connect(
        host="mydb.example.com",
        user="db_user",
        passwd="db_pwd",
        database="mydb"
    )
    mycursor = mydb.cursor()
    mycursor.execute("SELECT count(*) as c FROM squid_acls where valid_to >= now() and `start_ip` <= INET_ATON(%s) and `end_ip` >= INET_ATON(%s)",(src,src))
    myresult = mycursor.fetchall()
    logger.debug(myresult)
    return myresult[0][0] > 0


try:
    while True:
        # read a line from stdin
        src = sys.stdin.readline().strip()
        if src:
            if match_acl(src):
                logger.error("{}: OK".format(src))
                sys.stdout.write('OK\n')
            else:
                logger.error("{}: OK".format(src))
                sys.stdout.write('ERR\n')
            # Flush the output to stdout.
            sys.stdout.flush()
except Exception as ex:
    logger.error(ex)



Como backend tengo un una aplicación de Laravel que gestiona la tabla de mysql, el archivo de migración de esta tabla es:





<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddSquidAclTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('squid_acls', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('start_ip');
            $table->integer('end_ip');
            $table->timestamp('valid_to');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('squid_acls');
    }
}




El controlador de la API sería para esta tabla sería:





<?php

namespace App\Http\Controllers;

use App\Library\Networking\IpHelpers;
use App\Models\SquidAcl;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class SquidAclController extends Controller
{
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        request()->validate([
            'ip' => 'required',
            'time' => 'required|numeric'
        ]);

        $range = IpHelpers::getIpRange($request->input('ip'));
        if ($range != null) {

            $squid = new SquidAcl();
            $squid->start_ip = $range['start_ip'];
            $squid->end_ip = $range['end_ip'];
            $squid->valid_to = DB::raw('NOW() + INTERVAL ' . $request->input('time') . ' MINUTE');
            $squid->save();
            return response()->json(['result' => 1]);
        }
        return response()->json(['result' => 0]);
    }
}




La clase IpHelper que se encarga de obtener los rangos de IPs contenidos en un CIDR, en una IP, o en un dominio:





<?php
namespace App\Library\Networking;

class IpHelpers
{

    public static function getIpRange($cidr)
    {
        if (self::isIpv4($cidr)) {
            $start = $end = ip2long($cidr);
            return array('start_ip' => $start, 'end_ip' => $end);
        } elseif (self::isCidr($cidr)) {

            list($ip, $mask) = explode('/', $cidr);

            $maskBinStr = str_repeat("1", $mask) . str_repeat("0", 32 - $mask);      //net mask binary string
            $inverseMaskBinStr = str_repeat("0", $mask) . str_repeat("1", 32 - $mask); //inverse mask

            $ipLong = ip2long($ip);
            $ipMaskLong = bindec($maskBinStr);
            $inverseIpMaskLong = bindec($inverseMaskBinStr);
            $netWork = $ipLong &amp; $ipMaskLong;

            $start = $netWork + 1;//ignore network ID(eg: 192.168.1.0)

            $end = ($netWork | $inverseIpMaskLong) - 1; //ignore brocast IP(eg: 192.168.1.255)
            return array('start_ip' => $start, 'end_ip' => $end);
        } else {
            putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
            $cidr = gethostbyname($cidr);
            if (isset($cidr) &amp;&amp; self::isIpv4($cidr)) {
                return self::getIpRange($cidr);
            }
            return null;
        }

    }

    public static function isIpv4($ip)
    {
        return filter_var($ip, FILTER_VALIDATE_IP) !== false;
    }

    public static function isCidr($cidr)
    {
        $parts = explode('/', $cidr);
        if (count($parts) != 2) {
            return false;
        }

        $ip = $parts[0];
        $netmask = intval($parts[1]);

        if ($netmask < 0) {
            return false;
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            return $netmask <= 32;
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            return $netmask <= 128;
        }

        return false;
    }

}



Y para finalizar la ruta a la API:





Route::middleware('auth:api')->post('admin/squid/acl', 'SquidAclController@store');




Por ejemplo, si deseamos dar permisos temorales de conexión durante 60 minutos, bastaría con lanzar:





curl https://localhost/api/admin/squid/acl --data 'ip=192.168.33.33&amp;time=60' -H "Authorization: Bearer XXXXX" --http1.1



Donde XXXX es el token del usuario que tiene permisos para ejecutar esta acción.



4
Noticias del Blog / Automatizando ACLs en SQUID
« en: Enero 03, 2019, 08:09:45 pm »

5

Buenas tardes,


Hace ya algún tiempo publiqué una entrada haciendo referencia a túneling mediante SSH (https://www.bitsdelocos.es/2015/08/tunnel-ssh-con-raspbian-y-bitvisessh/). En esta entrada, versará sobre túneling sobre DNS (Domain Name Server). El concepto es el mismo: sobre un flujo de peticiones DNS insertar consultas forjadas de un modo conveniente parainyectar información de otro protocolo sobre DNS y así saltarse las protección de un firewall. La definición de un túnel en informática es:


Se conoce como túnel o tunneling a la técnica que consiste en encapsular un protocolo de red sobre otro (protocolo de red encapsulador) creando un túnel de información dentro de una red de computadoras.


Para poder llevar a cabo este escenario, necesitaremos:



  • Servidor: VPS o un equipo linux con una IP de internet estática.

  • Cliente: Sistema operativo linux o una máquina virtual con linux en su defecto.

  • Tener control sobre un dominio y sus entradas de DNS.

  • Tener chrome con la extensión SwitchyOmega.


La idea principal se basa en crear un subdominio al que se le harán las peticiones de DNS y un NS (Name Server o Servidor de nombres) al que enviar las peticiones malformadas. Necesitaremos añadir la siguiente configuración en nuestro DNS público:



  • tunel.bitsdelocos.es (NS apuntando a ns.bitsdelocos.es)

  • ns.bitsdelocos.es (A apuntando a la IP del VPS)


De este modo se conseguirá que todas las peticiones DNS del tipo xxxx.tunel.bitsdelocos.es se envíen a ns.bitsdelocos.es. Lo siguiente que necesitamos es montar un servidor de nombres que haga la función de resolver de paquetes generados maliciosamente que le lleguen. En este caso usaremos iodine (https://github.com/yarrick/iodine) . He usado debian 9 base como servidor de DNS. Para hacer el build de iodine necesitaremos ejecutar como super usuario:



apt-get install gcc make autoconf libz-dev git
git clone https://github.com/yarrick/iodine.git
cd iodine
make && make install

Si todo ha ido bien iodine estará instalado en nuestro sistema:



root@Debi:~# iodine -v
iodine IP over DNS tunneling client
Git version: 27e5d6f

Para hacer funcionar este servidor de DNS bastará con ejecutar:



iodined -c -P <PASSWORD> -n <IP ESTATICA> 172.16.0.1 tunel.bitsdelocos.es -F /var/run/iodine.pid

En el caso de que estemos usando la red local 172.16.0.0/24 podremos cambiar este rango por uno que no estemos usando.


Para comprobar que todo salió bien, se puede ejecutar el comando ip addr show para ver si se asignó una ip a la interfaz virtual dnsX:



root@Debi:~# ip addr show dev dns0
11: dns0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1130 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none
    inet 172.16.0.1/27 scope global dns0
       valid_lft forever preferred_lft forever

En nuestro PC debemos ejecutar el cliente que nos levantará el túnel contra el NS ns.bitsdelocos.es haciendo uso de resoluciones DNS al subdominio tunel.bitsdelocos.es. Para ello lanzaremos iodine en el cliente del siguiente modo:



iodine  -P <PASSWORD> <IP DNS FORWARDER> tunel.bitsdelocos.es -F /var/run/iodine.pid

Donde   es la ip del forwarder que use la red a la que estamos conectados o bien un servidor externo como el NS de google (8.8.8.8). Si no ha habido errores se podrá levantar un proxy SOCKS5 con el comando SSH:



ssh -fN -D *:8080 root@172.16.0.1

La IP 172.168.0.1 es la ip que se le habrá asignado a la interfaz dnsX en el servidor.


Para comprobar que todo ha ido bien se debe instalar el plugin switchy omega en el navegador chrome y configurarlo:

 


La IP que aparece en la captura es la ip de la máquina virtual donde se está ejecutando el cliente de iodine.


Para comprobar si ha ido bien la configruación del proxy, debemos abrir una web y con F12 inspeccionar la ip remota de la página abierta, si corresponde con la ip de nuestro proxy significa que habrá funcionado.


Se ha configurado Switchy omega con Chrome.


Otro modo de comprobar si funciona es usando CURL en un terminal linux



[root@localhost ~]# curl --socks5-hostname  127.0.0.1:8080 www.bitsdelocos.es -v
* About to connect() to proxy 127.0.0.1 port 8080 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.bitsdelocos.es
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: openresty
< Date: Sun, 21 Oct 2018 15:52:40 GMT
< Content-Type: text/html; charset=iso-8859-1
< Content-Length: 211
< Connection: keep-alive
< Location: https://www.bitsdelocos.es/

Este método de túneling es mucho menos efectivo que el túnneling por ssh ya que el overhead introducido es mucho mayor.



6
Noticias del Blog / Shaping con nginx: Limitando CPS (II)
« en: Septiembre 10, 2018, 09:32:36 am »

Hace unos días, se me planteó el problema de limitar las CPS (conexiones por segundo a un servicio de APIs externo), por lo que mi primera idea fue limitarlo en el proxy de salida de los servidores (SQUID). Pero eso implicaba cambiar y tunear las IPtables de una forma poco dinámica ya que si el proveedor está en una CDN habría que montar un proceso de actualización de reglas,.. o bien limitarlo para todas las salidas externas del proxy con lo que no resultaba ser una buena opción así que seguí buscando,… en este caso encontré nginx como proxy inverso con el módulo limit_req_zone que permite hacer rate limiting de peticiones.


Siguiendo el esquema de la entrada anterior (Shaping con Iptables: Limitando CPS):



Y la misma arquitectura de pruebas:

























Nombre del ServidorIPDescripción
shaper0110.112.112.101Nginx Reverse Proxy
webserver0110.112.112.102Servidor web con nginx
server0110.112.112.106Envío de tráfico

Para simplificar configuraciones, y la lectura de los scripts se han hecho 2 suposiciones.



  1. Tenemos acceso privilegiado a la máquina (sudo -i) o similar.

  2. En el archivo de hosts (/etc/hosts) de cada máquina se ha añadido lo siguiente:
    10.112.112.101 shaper01
    10.112.112.102 webserver01
    10.112.112.106 server01



Para las pruebas se ha habilitado la siguiente zona en nuestro nginx (/etc/nginx/conf.d/rate-limit.conf):



limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
    listen       80;
    server_name  shaper01;
    location / {
       limit_req zone=mylimit;
       proxy_set_header Host "webserver01";
       proxy_pass http://webserver01;

    }
}

En esta zona se indica que todas las peticiones con el server_name “shaper01”, en la ruta / e hijas se aplicará un rate-limiting de 10r/s, y tras aplicar esta  limitación serán reenviadas las peticiones contra el servidor webserver01.


El único cambio que hay que hacer en el programa cliente es la url dónde apuntarán sus peticiones, en este caso, se deberán abrir contra el servidor shaper01 en lugar del endpoint de la API.


Las pruebas se han lanzado con curl igual que en la entrada anterior:



while [ 1 ]; do curl shaper01 -s > /dev/null; done

Tras analizar los resultados del nginx del server web, podemos afirmar que está funcionando el rate-limiting.


Conexiones Por segundo usando nginx



7
Noticias del Blog / Shaping con Iptables: Limitando CPS
« en: Septiembre 07, 2018, 10:33:16 pm »

Buenas noches, después de mucho tiempo sin pasarme por aquí, esta noche os traigo una pequeña entrada para ver como se puede configurar un rate limiting (shaping) en función de las conexiones por segundo en lugar del por ancho de banda (bandwidth).


Durante el transcurso de esta semana, me han consultado si se podría limitar el consumo de una API REST de un proveedor externo a priori no debería ser mucho problema, cuando te piden esto lo primero que piensas es en limitar el ancho de banda consumido en un packetshaper/firewall aplicando políticas de QoS. Pero esta vez, se solicitó que se limitara por transacciones por segundo permitidas. Tras revisarlo con los compañeros nunca habíamos montado nada así por lo que me he dispuesto a investigar un poco más y he llegado la solución que os contaré en este post.


En la siguiente imagen se describe la arquitectura inicial, en el recuadro rojo se muestra que existen N instancias de una aplicación que competirán por 10 conexiones por segundo contra la API externa (verde).





Por definición las aplicaciones intentan usar todos los recursos que pueden para llevar a cabo su cometido lo más rápido posible.


La arquitectura propuesta para poder limitar realizar el rate-limiting de salida contra la API externa, pasa por montar un proxy HTTP (en este caso squid pero valdría cualquier proxy http/https) que tenga visibilidad sobre todas las conexiones que se realizan a la API y en él crear una política de de connection_tracking con Iptables. De este modo la arquitectura quedaría de la siguiente forma:



Para hacer la prueba de concepto he montado 3 máquinas linux con Centos 7 (1GB de RAM cada una y dos núcleos deberían sobrar para la prueba). A partir de ahora las máquinas recibirán el nombre:

























Nombre del ServidorIPDescripción
shaper0110.112.112.101Squid + Iptables
webserver0110.112.112.102Servidor web con nginx
server0110.112.112.106Envío de tráfico

Para simplificar configuraciones, y la lectura de los scripts se han hecho 2 suposiciones.



  1. Tenemos acceso privilegiado a la máquina (sudo -i) o similar.

  2. En el archivo de hosts (/etc/hosts) de cada máquina se ha añadido lo siguiente:
    10.112.112.101 shaper01
    10.112.112.102 webserver01
    10.112.112.106 server01



En el servidor webserver01, he instalado un nginx de prueba para que nos sirva contenido estático y poder ver sus logs:


yum install epel-release
yum install nginx
systemctl enable nginx
systemctl start nginx

En el servidor shaper01 he instalado squid y iptables:



yum install epel-release -y
yum install iptables-services -y
yum -y install squid
echo "cache deny all" >> /etc/squid/squid.conf
systemctl restart iptables squid
systemctl enable iptables squid

En este punto Iptables estan bloqueando todo el tráfico por defecto por que en los siguientes comandos, voy a resetear estos parámetros (no lo hagáis así si ya está instalado y tiene reglas por configuradas):



iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -t nat -F
iptables -t mangle -F
iptables -F
iptables -X

El siguiente paso es configurar las reglas para limitar el número de conexiones por segundo:



iptables --new-chain RATE-LIMIT
iptables --append INPUT  --match conntrack --ctstate NEW --jump RATE-LIMIT
iptables --append RATE-LIMIT \
    --match hashlimit \
    --hashlimit-mode dstip \
    --hashlimit-upto 10/sec \
    --hashlimit-burst 9 \
    --hashlimit-name conn_rate_limit \
    --jump ACCEPT
iptables --append RATE-LIMIT --jump REJECT

Con este comando creamos una nueva “cadena” en iptables que se ejecutará cuando haya una nueva conexión y no haya hecho match con ninguna regla existente.

Explicación del comando:



  1. match hashlimit: Tipo de plugin de iptables que se aplicará en este caso hashlimit (variación avanzada del módulo de limits)

  2. hashlimit-mode dstip: Permite agrupar los (CPS) por ip de destino, origen,..  

  3. hashlimit-upto 10/sec: Número de conexiones máximas por unidad de tiempo

  4. hashlimit-burst 9: Ráfaga máxima soportada

  5. hashlimit-name conn_rate_limit: Nombre con el que guardara la información internamente

  6. jump REJECT: Acción por omisión permite hacer ACCEPT/DROP/REJECT. Se ha creado con reject porque avisa al origen de que no es posible establecer la conexión. Si se hiciera con DROP la aplicación quedaría esperando hasta obtener un TimeOut ralentizando mucho el proceso de captura de errores y reintentos.


En el servidor server01, se ha configurado el un curl para que use nginx y vaya haciendo peticiones:



export http_proxy="http://shaper01:3128"
export https_proxy="http://shaper01:3128"
while [ 1 ]; do curl webserver01 -s > /dev/null; done

Si todo ha ido bien en el nginx deberíamos ver logs como el siguiente:



10.112.112.101 - - [07/Sep/2018:19:49:58 +0000] "GET / HTTP/1.1" 200 3700 "-" "curl/7.29.0" "10.112.112.106"

Para comprobar que está funcionando correctamente, he lanzado el while con el curl en el server01 sin haber activado las iptables y tras un tiempo he ejecutado los comandos para hacer el rate-limiting. Tras esto he analizado el fichero de log de nginx con algo de ayuda de bash y excel:



cat access.log  | cut -d " " -f 4 | uniq -c
      3 [07/Sep/2018:21:03:12
     74 [07/Sep/2018:21:03:13
     74 [07/Sep/2018:21:03:14
     74 [07/Sep/2018:21:03:15
     75 [07/Sep/2018:21:03:16
     75 [07/Sep/2018:21:03:17
     75 [07/Sep/2018:21:03:18
     74 [07/Sep/2018:21:03:19
     74 [07/Sep/2018:21:03:20
     74 [07/Sep/2018:21:03:21
     74 [07/Sep/2018:21:03:22
     75 [07/Sep/2018:21:03:23
     74 [07/Sep/2018:21:03:24
     75 [07/Sep/2018:21:03:25
     74 [07/Sep/2018:21:03:26
     75 [07/Sep/2018:21:03:27
     75 [07/Sep/2018:21:03:28
      9 [07/Sep/2018:21:03:29
     11 [07/Sep/2018:21:03:46
     10 [07/Sep/2018:21:03:47
     10 [07/Sep/2018:21:03:48
     10 [07/Sep/2018:21:03:49
     10 [07/Sep/2018:21:03:50
     10 [07/Sep/2018:21:03:51
      8 [07/Sep/2018:21:03:52
      8 [07/Sep/2018:21:03:53
     10 [07/Sep/2018:21:03:54
     10 [07/Sep/2018:21:03:55
     10 [07/Sep/2018:21:03:56
     10 [07/Sep/2018:21:03:57
     10 [07/Sep/2018:21:03:58

Si graficamos la información, se observa claramente el momento dónde he activado el rate-limiting:


Espero que os resulte de utilidad o almenos entretenida la lectura.



8
Noticias del Blog / Integración OSSIM - Elasticsearch
« en: Febrero 22, 2018, 10:35:29 am »

Qué es ossim?


El software de seguridad OSSIM, del inglés  Open Source Security Information Management, es un conjunto de herramientas de seguridad (recogida de logs, monitorización, escaneo de vulnerabilidades,…) orquestadas para conseguir una solución de seguridad capaz  de correlacionar y detectar eventos de seguridad y vulnerabilidades en una infraestructura  determinada.


Tras haber gestionado ossim, me he dado cuenta que la integración de OSSIM con el mundo exterior es algo complicada.. expresiones regulares, creación de nuevos plugins, tener que pasar siempre por syslog,… Para evitar estos problemas se ha desarrollado un nuevo datasource de ossim para permitir la integración de OSSIM con elasticsearch.


El código se encuentra en mi repositorio público:


https://github.com/berni69/ossim-agent-elasticsearch


El código que se debe añadir a ossim es:


ParserElastic.py

ElasticDetector.py


Además se debe modificar el componente Agent.py (línea 559 aprox) para añadir un nuevo tipo de datasource.



                elif plugin.get("config", "source") == "elasticsearch":
                    parser = ParserElastic(self.conf, plugin, None)
                    parser.start()
                    self.detector_objs.append(parser)

Para que esto funcione se debe añadir la librería de elasticsearch para python:



pip install elasticsearch

Ejemplo de configuración:


https://raw.githubusercontent.com/berni69/ossim-plugins-elasticsearch/master/elasticsearch-example.cfg



9
Noticias del Blog / Mantén tus dispositivos actualizados con Ansible
« en: Diciembre 07, 2017, 12:43:23 am »

Si te gusta cacharrear como a mi, al final terminas teniendo varios dispositivos conectados a la red de tu casa y otros tantos distribuidos en el cloud, una de las tareas más tediosas a las que te puedes enfrentar es irlos actualizado a medida que pasa el tiempo. No a todos estos dispositivos me conecto habitualmente por lo que no siempre recuerdo actualizarlos, para solucionar esto he decidido que voy a usar Ansible. Esta pieza de software se define como:



Ansible es categorizado como una herramienta de orquestación. Maneja nodos a través de SSH y no requiere ningún software remoto adicional (excepto Python 2.4 o posterior para instalarlo). Wikipedia


Para poder instalar Ansible es necesario disponer de Pip y Python en nuestro sistema. Yo lo decidí instalar en mi Raspberry Pi por lo que además tuve que instalar software adicional:



apt-get update
apt-get upgrade python
apt-get install libssl-dev libffi5-dev python-dev build-essential
cd /tmp/
wget wget https://bootstrap.pypa.io/get-pip.py
python get-pip.py
pip install ansible

Dependiendo de la velocidad de la tarjeta SD de tu Raspberry Pi y la versión de ésta, puede tardar casi una hora en instalar todo el software necesario.


Una vez instalado el software, debemos crear el inventario de máquinas en /etc/ansible/hosts



mkdir /etc/ansible/
touch /etc/ansible/hosts

En mi caso voy a crear un grupo de servidores llamado servers que contiene un servidor vps y 2 raspberrys:



[servers]
mivps.example.com   ansible_connection=ssh  ansible_user=root
rpi1                ansible_connection=local #Host donde ejecuto ansible
rpi2                ansible_connection=ssh  ansible_user=pi    ansible_host=192.168.1.13

Donde la primera columna es el nombre del dispositivo al que nos vamos a conectar, ansible_host es la ip en el caso de que el nombre no se pueda resolver por dns y ansible_user es el usuario que usará ansible para conectarse al dispositivo.


Habitualmente, si no se especifica lo contrario, ansible intentará usar la clave ssh del usuario local para conectarse a los servidores remotos, si el usuario no tiene ninguna clave ssh generada, la crearemos.



mkdir $HOME/.ssh
chmod 600 $HOME/.ssh
ssh-keygen -t ecdsa -C "miusuario@miequipo"

Este proceso debe generar 2 claves (pública y privada) llamadas ~/.ssh/id_ecdsa.pub y ~/.ssh/id_ecdsa. Debemos copiar la clave pública (.pub) en el archivo authorized_keys del servidor remoto, para ello ejecutaremos el comando:



ssh-copy-id -i ~/.ssh/id_ecdsa root@mivps.example.com

Si queremos probar que se han copiado las claves ssh correctamente y tenemos acceso podemos ejecutar el comando



root@rpi1:~# ansible servers -m ping
mivps.example.com | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
rpi1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
rpi2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}


Si todos los hosts aparecen como success, es que la instalación de claves ha ido correctamente.


Una vez copiadas todas las claves en los servidores, debemos crear el archivo /etc/ansible/update.yml:



---

- hosts: servers
  become: true
  become_user: root
  become_method: sudo
  tasks:
    - name: Update packages list [APT]
      apt: update_cache=yes
      when: ansible_os_family == 'Debian'
    - name: Upgrade packages [APT]
      apt: upgrade=safe
      when: ansible_os_family == 'Debian'
    - name: Upgrade packages [YUM]
      yum:
        name: '*'
        state: latest
        update_cache: yes
      when: ansible_os_family == 'RedHat'

Para finalizar, debemos lanzar el comando



root@rpi1:~# ansible-playbook /etc/ansible/update.yml

PLAY [servers] *********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************************************************************
ok: [mivps.example.com]
ok: [rpi2]
ok: [rpi1]

TASK [Update packages list] ********************************************************************************************************************************************************************************
changed: [mivps.example.com]
changed: [rpi2]
changed: [rpi1]

TASK [Upgrade packages] ************************************************************************************************************************************************************************************
changed: [mivps.example.com]
changed: [rpi1]
changed: [rpi2]

PLAY RECAP *************************************************************************************************************************************************************************************************
mivps.example.com          : ok=3    changed=2    unreachable=0    failed=0  
rpi1                       : ok=3    changed=2    unreachable=0    failed=0  
rpi2                       : ok=3    changed=2    unreachable=0    failed=0  


Si todos los hosts aparecen como 0 failed y 0 unreachable es que la ejecución del script ha ido correctamente.



10
Noticias del Blog / Reiniciando automáticamente el router CG6640E
« en: Octubre 29, 2017, 05:00:54 pm »

Desde hace un tiempo el router de ONO me falla cuando hace más de una semana que no se ha reiniciado, por lo que cada X tiempo me tenía que acordar de reiniciarlo para no quedarme sin servicio durante algún hito importante.


Trasteando un poco he visto que en su firmware existía una página que estaba oculta en los menús:


http://192.168.x.1/modem_configuration.html


Esta página, además de permitirte hacer un reset de fábrica del aparato, te permite cambiar las frecuencias favoritas del CM y hacer un reboot del dispositivo:


Configuración avanzada del modem de ONO


Con lo que con un poco de ayuda de las herramientas de desarrollador de google y unos cuantos reinicios del dispositivo he podido ver que estaba ejecutando por debajo:


http://192.168.x.1/setRestartCM.html


Eso es un avance, llamando a esa url se reinicia el módem siempre que haya un usuario logeado en el módem desde la IP que llama.


Por lo que he tenido que investigar como hacer login en el dispositivo, es un mecanismo muy sencillo que no usa ni cookies ni tokens ni sesiones, solo depende de la IP de origen de la petición:


http://192.168.x.1/login/Login.txt?password=xxxx&user=admin


Una vez hecho login la petición de login, el router devolverá el nombre del usuario si ha sido correcto o false si no ha funcionado el login.


Para finalizar, sólo es necesario hacer una petición http a cualquier html para que el reinicio se ejecute correctamente.


Si juntamos todos estos requisitos, queda el siguiente script en bash:




#!/bin/bash

USER="admin"
PASSWORD='password'
HOST="192.168.x.1"

#Login

echo "[+] Loging"

curl -v -G -XGET "http://${HOST}/login/Login.txt" --data-urlencode  "password=${PASSWORD}" --data-urlencode  "user=${USER}"


#Reboot
sleep 4
echo "[+] Rebooting"
curl -0 -v -XGET  "http://${HOST}/setRestartCM.html"

# Fake HTML

sleep 1
echo "[+] Sending Fake HTML"
curl -0 -v -XGET  "http://${HOST}/index.html"


Con un crontab para ahorrarnos el trabajo, quedaría un script capaz de reiniciar automáticamente el router con la frecuencia que le digamos.



11
Noticias del Blog / Laravel: Securizando APIs
« en: Octubre 28, 2017, 05:51:49 pm »

Desde hace un tiempo he estado trabajando en una apliación web para facilitar la gestión de servidores, para ello he desarrollado varias APIs para poder ejecutar comandos en servidores. Uno de los puntos más criticos de esto es permitir o denegar el acceso a los usuarios para llamar a estas APIs.


En mi caso, estoy usando el sitema de auth de Laravel con la librería Adldap2-Laravel para manejar accesos desde Active Directory. Por determinadas razones nececsito que mis usuarios puedan hacer llamadas via un token de autorización. Este paso es muy sencillo, Laravel con su módulo de Auth nos proporciona este mecanismo si utilizas el driver de base de datos y no el driver LDAP ya que no comprueba si el token que usas pertenece a un usuario dado de baja en ldap o le ha expirado la cuenta. Para corregir este comportamiento debemos hacer lo siguente.


Debemos añadir al archivo de configuración de autenticación (config/auth.php), los nuevos guards y providers “custom” que vamos a generar:



    'guards' => [
        ...
        'api' => [
            'driver' => 'token',
            'provider' => 'api-users',
        ],
    ],
    'providers' => [
        .....
         'api-users' => [
             'driver' => 'custom',
             'model'  => App\User::class,
         ],
    ],

El siguiente paso es indicar en el archivo de rutas de APIs que el guard de authenticación que queremos usar es el de ‘api’ (routes/api.php):



Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

Para que el provider de usuarios sepa que clase debe usar debemos crear nuestro proveedor e indicarselo a laravel:


app/Providers/CustomUserProvider.php



<?php
namespace App\Providers;

use Adldap\Laravel\Facades\Adldap;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class CustomUserProvider extends EloquentUserProvider

{
/**
* Retrieve a user by the given credentials. *
* @param array $credentials *
@return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public

function retrieveByCredentials(array $credentials)
{
if (empty($credentials)) {
return;
}
                // First we will add each credential element to the query as a where clause.
                // Then we can execute the query and, if we found a user, return it in a
                // Eloquent User "model" that will be utilized by the Guard instances.
                $query = $this->createModel()->newQuery();
foreach($credentials as $key => $value) {
if (!Str::contains($key, 'password')) {
$query->where($key, $value);
}
}

$user = $query->first();
if (!$user) {
return;
}

// Securing API CALLS with LDAP checks

$adldap_user = Adldap::search()->users()->find($user->id);
if ($adldap_user->isExpired() || $adldap_user->isDisabled()) {
return;
};
return $user;
}
}

Dentro de la clase anterior hemos sobreescrito la función retrieveByCredentials para que además de comprobar el token del usuario compruebe si éste ha expirado en LDAP o está deshabilitado.


El siguiente archivo que debemos modificar es el AuthServiceProvider para que nos cargue nuestro proveedor ‘custom’:



app/Providers/AuthUserProvider.php




<?php

namespace App\Providers;

use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider

{
/** * The policy mappings for the application. * * @var array */
protected $policies = ['App\Model' => 'App\Policies\ModelPolicy', ];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public

function boot()
{
$this->app['auth']->provider('custom',
function ($app, array $config)
{
$model = $app['config']['auth.providers.users.model'];
return new CustomUserProvider($app['hash'], $model);
});
$this->registerPolicies();

//

}
}


Adicionalmente, como la librería de LDAP no está preparada para el uso de tokens, es necesarío parchear para que la primera vez que hace login el usuario le genere un token:



app/Controllers/Auth/LoginController.php


A este controller le debemos sobreescribir la funcion authenticated del siguiente modo:



    protected function authenticated(Request $request, $user)
    {
        if (empty($user->api_token)) {
            $user->api_token = str_random(60);
            $user->save();
        }
    }

De esta forma, la primera vez que se genera un usuario al no tener un token asignado lo generaremos al vuelo.


Para comrpobar si ha ido se está generando el token podemos revisar la base de datos o imprimirlo en el blade del siguiente modo:



  @if(Auth::check())
        <a href="api/user?api_token={{ Auth::user()->api_token }}">Mi usuario JSON</a>
  @endif


12
Noticias del Blog / Toshiba Qosmio X305-Q708
« en: Abril 29, 2017, 11:18:42 am »


 


Visitando TechPowerUp encontramos un portátil impresionante elToshiba Qosmio X305-Q708.


Un portátil básicamente para jugones con sus tres gráficas y bastante potencia. Tenemos una pantalla de 17″ y una gráfica NVidia GeForce 9400M con memoria integrada y dos 9800M GTSdedicadas, cada una con 512 MB de memoria GDDR3, tambien cuanta con un procesador bastante potente  un Core 2 Extreme QX9300 a 2.53 GHz, cuenta con 4gb de RAM DDR3 de serie, en´cuestión de almacenamiento  tiene unos discos duros un SSDde 128 GB y uno tradicional de 320. Las conexiones son Chip WiFi Atheros (802.11 b/g/n), Bluetooth 2.1.


Opinión: yo tengo un toshiba qosmio f30 y la verdad esque no tengo casi ninguna pega la resolución de la pantalla es inigualable  la gráfica aguanta bastante bien los juegos el sonido se escucha bastante bien.


Algunos fallos que no me gustan es la batería que no me a durado mucho la verdad y el peso que la verdad los4kg que pesa se nota a la hora de transportarlo



13
Noticias del Blog / Actualizar DNS con DHCP
« en: Abril 29, 2017, 11:00:47 am »

En la entrada anterior vimos como montar un servidor bind y un server dhcp en linux para bloquear la resolución de algunas webs en nuestra red de área local. (Suplantando nuestros dns en la red local). Ya que tenemos el laboratorio montado en casa, podemos mejorar sus funcionalidades haciendo que todos los dispositivos que reciban una dirección DHCP de nuestro servidor queden registrados en el DNS con una entrada dinámica. Hacerlo a mano sería bastante laborioso y en breves tendríamos la zona dns desactualizada.


Para ello me he creado una zona a la que llamaré “canostra.local”, esta zona engloba mi red local. Siguiendo con el ejemplo anterior, tendremos 3 dispositivos en la red:



192.168.22.1  --> Gateway
192.168.22.2  --> Equipo Windows con reserva de DHCP
192.168.22.11 --> DNS + DHCP.

Para poder crear el dynamic dns, el primer paso es editar el archivo para crear las nuevas zonas:



sudo vi /etc/bind/named.conf.local


Se van a crear 2 zonas, la primera de resolución directa (cuando haces un nslookup de un dominio para que te devuelva la ip) y la segunda zona de resolución inversa (al hacer un nslookup de una ip el dns puede devolverte el dominio asociado). Además de estas zonas, debemos decir que solo se permita actualizar dinámicamente una zona desde el servidor dhcp, en este caso la misma raspberry pi (127.0.0.1), además de especificar la clave que se debe usar para realizar el update.



include "/etc/bind/rndc.key";
controls {
    inet 127.0.0.1 allow { localhost; } keys { "rndc-key"; };
};

### dynamic zones (updated by DDNS) #########

zone "canostra.local" {
    type master;
    file "/etc/bind/db.canostra.local";
    allow-update { key "rndc-key"; };
};

zone "22.168.192.in-addr.arpa" {
    type master;
    notify no;
    file "/etc/bind/db.192.168.22";
    allow-update { key "rndc-key"; };
};

Si os fijáis en la segunda zona tiene un nombre un poco extraño, esto es una zona “legacy” de los principios de Internet. El formato del nombre de la zona viene especificado por la red a la que va a resolver con sus octetos en orden inverso.


Los siguiente que neceistamos hacer es crear los archivos



/etc/bind/db.canostra.local
/etc/bind/db.192.168.22

En estos archivos especificaremos la zona y sus propiedades:



$ORIGIN .
$TTL 86400      ; 1 day
canostra.local            IN SOA  ns.canostra.local. root.localhost. (
                                3          ; serial
                                604800     ; refresh (1 week)
                                86400      ; retry (1 day)
                                2419200    ; expire (4 weeks)
                                604800     ; minimum (1 week)
                                )
                        NS      ns.canostra.local.
                        A       192.168.22.1
$ORIGIN canostra.local.
mi-pc                   A       192.168.22.2
ns                      NS      192.168.22.11

Fijaos bien en la notación de “canostra.local IN SOA ns.canostra.local. root.localhost. ” y los puntos al final del ns, si no los ponemos no funcionará y dará un error de carga. El otro punto importante es que el “email” debe existir, en nuestro caso root.localhost existe por defecto. Con esta configuración conseguiremos que al registrarse nuevas máquinas en la red, vayan apareciendo entradas del tipo A en esta zona.



$ORIGIN .
$TTL 907200     ; 1 week 3 days 12 hours
22.168.192.in-addr.arpa IN SOA  ns.canostra.local. root.localhost. (
                                2014071403 ; serial
                                28800      ; refresh (8 hours)
                                604800     ; retry (1 week)
                                604800     ; expire (1 week)
                                86400      ; minimum (1 day)
                                )
                        NS      ns.canostra.local.
$ORIGIN 22.168.192.in-addr.arpa.
$TTL 3600       ; 1 hour
1                        PTR     canostra.local.
6                        PTR     mi-pc.canostra.local

Igual que antes tened cuidado con los puntos al final de los dominios. Con esta configuración se ha conseguido crear la zona inversa de resolución para el dominio canostra.local.


Para comprobar que todo funciona se debe reiniciar el servicio de dns:



sudo service bind9 restart

Si todo va bien, podemos probar la resolución directa y la resolución inversa:



$nslookup mi-pc.canostra.local
Server:         127.0.0.1
Address:        127.0.0.1#53

Name:   mi-pc.canostra.local
Address: 192.168.22.6

Inversa:



$ nslookup 192.168.22.6
Server:         127.0.0.1
Address:        127.0.0.1#53

6.22.168.192.in-addr.arpa       name = mi-pc.canostra.local.22.168.192.in-addr.arpa.

$ nslookup 192.168.22.1
Server:         127.0.0.1
Address:        127.0.0.1#53

1.22.168.192.in-addr.arpa       name = canostra.local.

Si el DNS ha conseguido resolver estas direcciones es que lo hemos creado correctamente. Como siempre es recomendable ir revisando el syslog ya que da pistas sobre los errores que podemos tener en la configuración.


Si en el syslog vemos que aparecen errores del tipo



zone canostra.local/IN: MI_PC.canostra.local/A: bad owner name

Es debido a que la barra baja no está permitida por defecto, para habilitarla deberemos editar el archivo:



sudo vi /etc/bind/named.conf.options

Y añadir las entradas:



check-names master warn;
check-names slave warn;

Si vemos errores de que hay problemas de integridad con dnssec, será necesario añadir las siguientes opciones al mismo archivo:



dnssec-enable yes;
dnssec-validation no;
dnssec-lookaside auto;

Para que DHCP pueda actualizar las entradas DNS será necesario un último paso, dar permisos de escritura al usuario bind a la carpeta donde están las zonas:



chown -R bind:bind /etc/bind/

En este punto del manual, ya podemos pasar a editar la configuración de DHCP:



sudo cat /etc/dhcp/dhcpd.conf

En este archivo debemos comentar la línea:



#ddns-update-style none;

Y añadir las siguientes justo abajo:



#Configuracion para integrar con dns#

ddns-update-style interim;
include "/etc/bind/rndc.key";

zone canostra.local. {
        primary 127.0.0.1;
        key "rndc-key";
}

ddns-domainname "canostra.local";

Con estas líneas especificamos que zona se debe utilizar para actualizar el dns, y con que  clave. Además en la configuración del rango de DHCP debemos volverlo a especificar (además de especificar la zona inversa):



# Rangos
subnet 192.168.22.0 netmask 255.255.255.0 {
      range 192.168.22.15 192.168.22.250;
      option subnet-mask 255.255.255.0;
      option routers 192.168.22.1;

      #integracion ddns
      # DNS zones to update

      zone 22.168.192.in-addr.arpa. {
            primary 192.168.22.11;
            key "rndc-key";
      }

      zone canostra.local. {
            primary 192.168.22.11;
            key "rndc-key";
      }

}

Ya solo queda reiniciar el servidor dhcp y revisar que se añaden entradas en el dns, para ello basta con observar el archvio de syslog. En este punto he conectado mi teléfono a la red local:



tail -200f /var/log/syslog
Apr 29 09:35:03 raspberrypi named[3036]: client 192.168.22.11#42676/key rndc-key: updating zone 'canostra.local/IN': adding an RR at 'MI-TELEFONO.canostra.local' A
Apr 29 09:35:03 raspberrypi dhcpd: Added new forward map from MI-TELEFONO.canostra.local to 192.168.22.200
Apr 29 09:35:03 raspberrypi named[3036]: client 192.168.22.11#42676/key rndc-key: signer "rndc-key" approved
Apr 29 09:35:03 raspberrypi named[3036]: client 192.168.22.11#42676/key rndc-key: updating zone '22.168.192.in-addr.arpa/IN': deleting rrset at '200.22.168.192.in-addr.arpa' PTR
Apr 29 09:35:03 raspberrypi named[3036]: client 192.168.22.11#42676/key rndc-key: updating zone '22.168.192.in-addr.arpa/IN': adding an RR at '200.22.168.192.in-addr.arpa' PTR
Apr 29 09:35:03 raspberrypi dhcpd: Added reverse map from 200.22.168.192.in-addr.arpa. to MI-TELEFONO.canostra.local

Si hicieras un ping a MI-TELEFONO.canostra.local debería contestarte:



ping MI-TELEFONO.canostra.local
PING MI-TELEFONO.canostra.local (192.168.22.200) 56(84) bytes of data.
64 bytes from MI-TELEFONO.canostra.local (192.168.22.200): icmp_seq=1 ttl=64 time=279 ms
64 bytes from MI-TELEFONO.canostra.local (192.168.22.200): icmp_seq=2 ttl=64 time=220 ms
64 bytes from MI-TELEFONO.canostra.local (192.168.22.200): icmp_seq=3 ttl=64 time=156 ms

Si quisiéramos poder resolver los nombres de hosts sin tener que especificar el dominio, deberíamos añadir la siguiente linea en el  archivo de configuración de dhcp:



option domain-search "canostra.local";

Pues con esto la entrada de hoy, espero que os haya gustado.



14
Noticias del Blog / Suplantando nuestros DNS en la red local
« en: Abril 29, 2017, 12:06:09 am »

Desde hace un tiempo, es muy común que la gente utilice su teléfono indiscriminadamente a todas horas. Esta mañana cuando desperté me dirigí a la cocina a saludar a mis padres y como es costumbre tras varios minutos apareció mi hermana escuchando un servicio de streaming de audio a todo volumen, le pedí por favor que lo desconectara y como es pasó de mi, así que esto basto para encender mi creatividad. Como no tengo el hardware necesario para montar un Firewall + Squid para que me aplique control parental, he decidido montar un aproximación con mi Raspberry (Ya os aviso de que es muy rudimentario, pero de momento efectivo). La finalidad de este post es poder bloquear el acceso a una serie de dominios en determinadas horas del día.


Evidentemente, para alguien que sepa algo de redes este procedimiento no sirve de nada, bastaría con forzar los dns de la máquina en la que se está trabajando a los de nuestro ips/google/opendns para que nos sirviera las entradas reales, pero creo que para mi propósito me sirve sobrado.


Antes de empezar supondremos que tenemos una RED de área local que corresponde con el rango 192.168.22.0/24 dónde



192.168.22.1  --> Gateway
192.168.22.2  --> Equipo Windows con reserva de DHCP
192.168.22.11 --> DNS + DHCP.

En la siguiente imagen os presento una aproximación (en este caso no sería ROGUE DNS porque nosotros lo permitimos con nuestro servidor DHCP, ya que es nuestra red, pero si montáramos este escenario intentando suplantar el DHCP real se podría considerar un ataque) de lo que realizaremos:


Rogue DNS


En mi caso el Router del ISP no me permite configurar que servidores DNS serán servidos con este protocolo por lo que tendré que instalar un servidor de DHCP en la Raspberry y desactivar el propio del router.




El primer paso consiste en instalar Raspbian en nuestro dispositvo, acto seguido instalaremos el servidor DNS (bind9) y DHCP (isc-dhcp-server):



sudo apt-get install bind9 dnsutils
sudo apt-get install isc-dhcp-server

Una vez se han terminado de instalar estos paquetes, debemos configuramos servidor DNS para hacer de relay (El relay permite resolver Zonas DNS que no son nuestras. P.E google.es). Para ello debemos editar:


	
sudo vi /etc/bind/named.conf.options

Dentro de la sección options añadimos los forwarders esto permite utilizar DNS externos para que nos resuelvan consultas que no tenemos en cache ni somos maestros de la zona. En este ejemplo se añadirán los un servidor de nombres de Opendns (Cisco) y un servidor de nombres de Google (en la práctica son muy utilizados). Notad que utilizo vi para editar el archivo si no estáis acostumbrados a este editor quizá sea mejor que utilicéis nano.



forwarders {
       208.67.222.222;
       8.8.8.8;
};
dnssec-enable yes;
dnssec-validation no;
dnssec-lookaside auto;

Una vez hemos terminado de hacer cambios, reiniciamos el servicio:



sudo service bind9 restart

Para probar si los cambios se han aplicado bien, utilizaremos la utilidad nslookup para resolver direcciones en nuestro servidor de DNS, para ello es necesario especificar que el server que usaremos es el 127.0.0.1.



$ nslookup
> server 127.0.0.1
Default server: 127.0.0.1
Address: 127.0.0.1#53
> google.es
Server:         127.0.0.1
Address:        127.0.0.1#53
Non-authoritative answer:
Name:   google.es
Address: 216.58.201.131

Si resuelve correctamente significa, que los forwarders de dns se han configurado correctamente.


El siguiente paso es crear una zona maestra para hacer un “override” de un dominio determinado. Como el servidor de nombres DNS primero revisa si tiene una zona (un dominio y todas sus entradas asociadas) y si no la tiene la pregunta a los forwarders, podemos aprovechar este comportamiento para sobrescribir el dominio que no queremos que sea accesible desde la red.


Cuando estás realizando un ataque, este montaje es muy común en redes en la que los elementos de red no controlan por donde les debe venir la información del servidor DHCP, esto se conoce como suplantación de DHCP y rogue/suplantación de DNS.


Para hacer esta configuración, lo primero que hay que hacer es editar el archivo:



sudo vi /etc/bind/named.conf.local

Y crear la zona para la web que queremos servir. En mi caso me interesa bloquear/suplantar la web: “miwebbloqueada.com”



zone "miwebbloqueada.com" {
type master;
file "/etc/bind/db.miwebbloqueada.com";
};

El siguiente paso es crear la base de datos de información del dominio. Este fichero contendrá todas los “Resource Records” (entradas del DNS asociadas a un dominio). Estas entradas nos facilitan información sobre los servidores de nombre de un dominio, la dirección ip que debe resolver un subdominio de ese dominio, los servidores de correo de un dominio, y mucha más información que no viene al caso.


	
sudo cp /etc/bind/db.local /etc/bind/db.miwebbloqueada.com

Editamos el archivo para que quede con la información mínima para que resuelva una ip distinta a la original (como localhost), con esto podríamos hacer un ataque de “fishing” o un bloqueador de publicidad, o de servicios molestos por la mañana…


	
sudo vi /etc/bind/db.miwebbloqueada.com
;
; BIND data file for local loopback interface
;
$TTL    604800
@       IN      SOA     ns.miwebbloqueada.com root.localhost. (
 2         ; Serial
604800         ; Refresh
 86400         ; Retry
2419200         ; Expire
604800 )       ; Negative Cache TTL
;
@   IN  NS  ns.miwebbloqueada.com.
ns      IN      NS      192.168.22.11
@       IN      A       127.0.0.1

Una vez configurado debemos reiniciar el servicio de DNS.



sudo service bind9 restart

Si todo ha ido bien deberíamos ver en el syslog que la zona ha sido cargada correctamente



$ tail -f /var/log/syslog
Apr 28 20:39:11 raspberrypi named[15686]: managed-keys-zone: loaded serial 2
Apr 28 20:39:11 raspberrypi named[15686]: zone 0.in-addr.arpa/IN: loaded serial 1
Apr 28 20:39:11 raspberrypi named[15686]: zone 127.in-addr.arpa/IN: loaded serial 1
Apr 28 20:39:11 raspberrypi named[15686]: zone 255.in-addr.arpa/IN: loaded serial 1
Apr 28 20:39:11 raspberrypi named[15686]: zone miwebbloqueada.com/IN: ns.miwebbloqueada.com/NS '192.168.22.11.miwebbloqueada.com' has no address records (A or AAAA)
Apr 28 20:39:11 raspberrypi named[15686]: zone miwebbloqueada.com/IN: loaded serial 2
Apr 28 20:39:11 raspberrypi named[15686]: zone localhost/IN: loaded serial 2
Apr 28 20:39:11 raspberrypi named[15686]: all zones loaded

Sale una advertencia que no es del todo correcta, nos viene a decir que solo hay configurado el subdominio “.” y no hay ninguno más por lo que todos los subdominios se resolverían igual. La prueba de fuego igual que antes, es ejecutar nslookup. Con esta utilidad, deberíamos poder ver la web en cuestión apuntando a la IP que hayamos puesto



$ nslookup
> server 127.0.0.1
Default server: 127.0.0.1
Address: 127.0.0.1#53
> miwebbloqueada.com
Server:         127.0.0.1
Address:        127.0.0.1#53

Name:   miwebbloqueada.com
Address: 127.0.0.1

Si resuelve bien, aquí termina la configuración del DNS.


Como ya os comenté antes, mi router no me permite modificar las propiedades básicas del protocolo DHCP por lo que tendré que desactivarlo y montar mi propio server. Si vuestro router lo permitiese, bastaría con cambiar los DNS por defecto y poner que resuelva la IP de vuestra Raspberry. Dicho esto, pasamos a la configuración del DHCP.


Editamos configuración para poner la IP de la Raspberry manualmente para que sea estática ya que el servidor DHCP será la propia Raspberry (cuidado cuando se edite esta configuración, si no es correcta o acorde con vuestra red puede dejar inaccesible por red la raspberry en el próximo reinicio). Para ello debemos editar el archivo:



sudo vi /etc/network/interfaces

Una vez abierto debemos comentar la linea



#iface eth0 inet manual

Y a continuación escribir (adaptando las ips a vuestra red):



allow-hotplug eth0
iface eth0 inet static
address 192.168.22.11
netmask 255.255.255.0
gateway 192.168.22.1
nameserver 8.8.8.8

Creamos una copia de seguridad de la configuración inicial del DHCP:



cp /etc/default/isc-dhcp-server  /etc/default/isc-dhcp-serverold
cp /etc/dhcp/dhcpd.conf  /etc/dhcp/dhcpd.confold

Editamos el server DHCP para que escuche en eth0 que, es la única interfaz de la Raspberry:



sudo vi /etc/default/isc-dhcp-server

Y buscamos la línea:



INTERFACES=”″

Y la reemplazamos por esta otra:



INTERFACES=”eth0″

Una vez hemos hecho que el server escuche en eth0 hay que indicarle el comportamiento que debe tener el DHCP:



sudo vi /etc/dhcp/dhcpd.conf

A continuación os pego una copia de mi configuración del DHCP, lo importante aquí es configurar el campo “option domain-name-servers 192.168.22.11;” para que apunte a la raspberry y los rangos de IPs. Adicionalmente, he creado un elemento con IP reservada, en este caso mi equipo, os será útil cuando tengáis que abrir los puertos. Como punto extra, os diré que tener reservada la IP en este DHCP me permite llevar un control de mis dispositivos ya que en el router no podía ponerles nombres. Cuando vayáis a configurar vuestro DHCP aseguraos de que dentro de subnet esté la “option routers” apuntando a vuestro Gateway, sino perderéis conectividad con Internet.



#Configuracion del servifor DHCP
ddns-update-style none;
option domain-name "micasa";
option domain-name-servers 192.168.22.11;
default-lease-time 7200;
max-lease-time 21600;
authoritative;
log-facility local7;

# Rangos
subnet 192.168.22.0 netmask 255.255.255.0 {
      range 192.168.22.15 192.168.22.200;
      option routers 192.168.22.1;
      option subnet-mask 255.255.255.0;
}


# ASIGNACIONES ESTATICAS  #
host mipc {
   hardware ethernet 00:11:22:33:44:55;
   fixed-address 192.168.22.2;
}

Una vez hemos hecho estos cambios, para que surjan efecto, debemos reiniciar el server dhcp:



sudo service dhcpcd restart

Pasados unos segundos, podemos comprobar si el servicio haya arrancado sin errores en los eventos del sistema:



tail -20f /var/log/daemon.log  
Apr 28 21:22:33 raspberrypi dhcpcd[1021]: sending signal TERM to pid 991
Apr 28 21:22:33 raspberrypi dhcpcd[991]: received signal TERM from PID 1021, stopping
Apr 28 21:22:33 raspberrypi dhcpcd[991]: eth0: removing interface
Apr 28 21:22:33 raspberrypi dhcpcd[1021]: waiting for pid 991 to exit
Apr 28 21:22:33 raspberrypi dhcpcd[1021]: dhcpcd[1021]: waiting for pid 991 to exit
Apr 28 21:22:33 raspberrypi dhcpcd[991]: exited
Apr 28 21:22:33 raspberrypi systemd[1]: dhcpcd.service: main process exited, code=exited, status=1/FAILURE
Apr 28 21:22:33 raspberrypi systemd[1]: Unit dhcpcd.service entered failed state.
Apr 28 21:22:33 raspberrypi systemd[1]: Starting dhcpcd on all interfaces...
Apr 28 21:22:33 raspberrypi dhcpcd[1025]: version 6.7.1 starting
Apr 28 21:22:33 raspberrypi dhcpcd[1025]: dev: loaded udev
Apr 28 21:22:33 raspberrypi dhcpcd[1025]: DUID 00:01:00:01:20:7e:13:db:b8:27:eb:e4:b0:68
Apr 28 21:22:33 raspberrypi dhcpcd[1025]: eth0: IAID eb:e4:b0:68
Apr 28 21:22:34 raspberrypi dhcpcd[1025]: eth0: soliciting an IPv6 router
Apr 28 21:22:34 raspberrypi dhcpcd[1025]: eth0: rebinding lease of 192.168.22.11
Apr 28 21:22:34 raspberrypi dhcpcd[1025]: eth0: changing route to 192.168.22.0/24
Apr 28 21:22:34 raspberrypi dhcpcd[1025]: eth0: changing default route via 192.168.22.1
Apr 28 21:22:34 raspberrypi dhcpcd[1025]: forked to background, child pid 1040
Apr 28 21:22:34 raspberrypi systemd[1]: Started dhcpcd on all interfaces.
Apr 28 21:22:46 raspberrypi dhcpcd[1040]: eth0: no IPv6 Routers available

El siguiente paso es desactivar el dhcp del router principal y renovar las IPs en nuestros dispositivos para ver que nos nos sirve nuestro DHCP:



Adaptador de Ethernet Ethernet:

   Sufijo DNS específico para la conexión. . : micasa
   Descripción . . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
   Dirección física. . . . . . . . . . . . . : 00-11-22-33-44-55
   DHCP habilitado . . . . . . . . . . . . . : sí
   Configuración automática habilitada . . . : sí
   Vínculo: dirección IPv6 local. . . : fe80::bc04:e853:fee7:e419%26(Preferido)
   Dirección IPv4. . . . . . . . . . . . . . : 192.168.22.2(Preferido)
   Máscara de subred . . . . . . . . . . . . : 255.255.255.0
   Concesión obtenida. . . . . . . . . . . . : viernes, 28 de abril de 2017 19:54:24
   La concesión expira . . . . . . . . . . . : sábado, 29 de abril de 2017 1:38:34
   Puerta de enlace predeterminada . . . . . : 192.168.22.1
   Servidor DHCP . . . . . . . . . . . . . . : 192.168.22.11
   IAID DHCPv6 . . . . . . . . . . . . . . . : 253521765
   DUID de cliente DHCPv6. . . . . . . . . . : 00-01-00-01-1A-ED-3C-DE-1C-6F-65-90-30-42
   Servidores DNS. . . . . . . . . . . . . . : 192.168.22.11
   NetBIOS sobre TCP/IP. . . . . . . . . . . : habilitado

Como podéis observar, el servidor DHCP y el servidor DNS apuntan a nuestra Raspberry Pi por lo que entrada de DNS que no queramos entrada que podemos bloquear.


Ya solo quedaría crear un cron para que copie una config con las db que queremos y lo restablezca pasado X tiempo. Por ejemplo, a mi solo me interesa que esto este activo de 7:00-9:00.


Así que crearé 2 archivos:



/etc/bind/named.conf.local.block
/etc/bind/named.conf.local.noblock

El archivo block contendrá las entradas a las zonas bloqueadas mientras que el noblock no contendrá ninguna zona. Además crearé un pequeño script en bash para hacer el cambio de archivos (como root debemos ejecutar):



touch /root/change-dns.sh
chmod +x /root/change-dns.sh
vi /root/change-dns.sh

Y pegamos el siguiente código:



#!/bin/bash
if [ -f "/etc/bind/named.conf.local.$1" ]; then
        cp -f "/etc/bind/named.conf.local.$1" /etc/bind/named.conf.local
        /usr/sbin/service bind9 restart
else
        echo "File doesn't exist"
fi

Guardamos los cambios y ejecutamos



crontab -e

Dentro del crontab podemos añadir las siguentes lineas:



0 7 * * * bash /root/change-dns.sh "block" > /tmp/block 2>  /tmp/block.err
0 9 * * * bash /root/change-dns.sh "noblock" > /tmp/block 2>  /tmp/block.err


15
Noticias del Blog / Let’s Encrypt la CA Open Source
« en: Abril 24, 2017, 03:19:52 pm »

Para todos aquellos que no conozcáis Let’s Encrypt os invito a que visitéis su Web  .  Esta entidad certificadora es Open Source y expide certificados gratuitamente siempre que podamos verificar la propiedad del sitio web mediante el protocolo ACME (automatic certificate management environment).  Este protocolo nos permite administrar y expedir certificados de manera automática.


La única pega de esta entidad es que la validez de los certificados es de 90 días. Por lo que cada 3 meses hay que renovarlos en el servidor. Existen varios mecanismos de autenticación de un dominio, el más sencillo es la comprobación web donde la entidad certificadora nos solicita que creemos un .html con un contenido específico en nuestro dominio. Para automatizar el uso de ACME se creó un proyecto que se llama  dehydrated en el que se gestionan en bash las llamadas a letsencrypt. Este proyecto es modular y permite parametrizar fácilmente las opciones de despliege de los certificados:


https://github.com/lukas2511/dehydrated


Este tipo de autenticación vía http presenta un problema, no puedes autenticar subdominios no publicados en Internet ya que la CA no podría conectarse a comprobar el challenge. Para este tipo de escenarios podemos utilizar la autenticación DNS01, donde se publica la información en un registro TXT del DNS que nos gestiona el dominio. Para usar este tipo de challenge, hay que especificar un “hook” a dehydrated para indicarle como tiene que interactuar con nuestro DNS.


En mi caso dispongo de una solución DNS de EfficientIP solid server, he creado un hook para usar dehydrated con SolidServer. Se puede encontrar en github:


https://github.com/berni69/solidserver-challenge


Para usarlo bastaría con descargarlo junto con dehydrated:



$ cd ~
$ git clone https://github.com/lukas2511/dehydrated
$ cd dehydrated
$ mkdir hooks
$ git clone https://github.com/berni69/solidserver-challenge.git hooks
$ chmod +x dehydrated
$ ./dehydrated --challenge dns-01  --cron --domain "test.example.com" --hook "hooks/solid-hook.py"

Saludos!



Páginas: [1] 2