Proteção contra portscans com Ossec HIDS e Portsentry

O Ossec HIDS é sem sombra de dúvidas uma das maiores ferramentas de proteção de host, mas como nada é perfeito existe uma deficiência na detecção e bloqueio de portscans.

O próprio Daniel Cid, pai da criança, informou isso em resposta a um questionamento sobre o assunto na lista do Ossec. Vejo o trecho abaixo:

“Ossec by itself does not detect portscans. However, if you send your firewall
logs to ossec it can detect portscans by analyzing your fw logs…”

Para ajudar neste tarefa Daniel indica o uso do iplog em conjunto com o Ossec, mas a última versão é de 2001 e sem manutenção como informa seu desenvolvedor:

“I am through working on this project. I will not be making any updates, and I will ignore just about all email about it. If anybody wants to take it over (for whatever reason), let me know.”

Para atender está demanda podemos utilizar o Postsentry, IDS responsável por detectar e bloquear tentativas de varredura de portas TCP/UDP.

Neste artigo irei apresentar como integrar o Ossec ao Portsentry tornando mais robusta a segurança do seu host. Estou levando em conta que você já possui o Ossec instalado e funcionando.

Instale e inicie o Portsentry

aptitude install portsentry && portsentry -atcp && portsentry -audp

Prepare o Ossec para trabalhar juntamente com o Portsentry adicionando algumas regras locais e do decoder[1].

[1] O decoder é responsável por interpretar os logs de várias ferramentas e juntamente com as regras realizar alguma ação.

Primeiro remova a seguinte regra (linhas 2240-2257) do arquivo /var/ossec/etc/decoder.xml

<!– Portsentry –>

<decoder name="portsentry">

  <program_name>^portsentry</program_name>

</decoder>

<decoder name="portsentry-attackalert">

  <parent>portsentry</parent>

  <prematch>attackalert: Connect from host: </prematch>

  <regex offset="after_prematch">(S+)/S+ to (S+) port: (d+)$</regex>

  <order>srcip,protocol,dstport</order>

</decoder>

<decoder name="portsentry-blocked">

  <parent>portsentry</parent>

  <prematch>is already blocked. Ignoring$</prematch>

  <regex>Host: (S+) is</regex>

  <order>srcip</order>

</decoder>

Adicione a seguinte regra no final do arquivo /var/ossec/etc/decoder.xml, antes da tag EOF

<decoder name="portsentry">

 <program_name>^portsentry</program_name>

</decoder>

<decoder name="portsentry-attackalert">

   <parent>portsentry</parent>

   <prematch>attackalert: TCP SYN/Normal scan from host: </prematch>

   <regex offset="after_prematch">(S+)/S+ to (S+) port: (d+)$</regex>

   <order>srcip,protocol,dstport</order>

</decoder>

<decoder name="portsentry-blocked">

   <parent>portsentry</parent>

   <prematch>is already blocked Ignoring$</prematch>

   <regex>Host: (S+)/S+ is</regex>

   <order>srcip</order>

</decoder>

<decoder name="portsentry-scan">

  <parent>portsentry</parent>

  <prematch>^attackalert: </prematch>

  <regex offset="after_prematch">scan from host: (S+)/S+ to S+ port: (d+)$</regex>

  <order>srcip, dstport</order>

</decoder>

<decoder name="portsentry-host">

  <parent>portsentry</parent>

  <prematch offset="after_parent">^attackalert: Host: </prematch>

  <regex offset="after_prematch">^(S+)/S+ </regex>

 <order>srcip</order>

</decoder>

Apartir dai o OSSEC passará a interpretar os logs gerados pelo Postsentry. Agora adicione as seguintes linhas no final do arquivo /var/ossec/rules/local_rules.xml, antes da tag EOF

<group name="syslog,portsentry,">

  <rule id="160000" level="0" noalert="1">

    <decoded_as>portsentry</decoded_as>

    <description>Grouping for the PortSentry rules</description>

  </rule>

  <rule id="160002" level="3">

    <if_sid>160000</if_sid>

    <match>attackalert:</match>

    <description>Connection from a host.</description>

  </rule>

  <rule id="160003" level="8" frequency="4" timeframe="180" ignore="60">

    <if_matched_sid>160002</if_matched_sid>

    <description>Repeated connections from the same host.</description>

    <same_source_ip/>

    <group>recon,</group>

  </rule>

  <rule id="160004" level="10" frequency="8" timeframe="180" ignore="60">

   <if_matched_sid>160002</if_matched_sid>

    <description>Host is still scanning</description>

    <same_source_ip />

    <group>recon,</group>

  </rule>

</group>

Reinicie o OSSEC

invoke-rc.d ossec restart

Após reiniciar o OSSEC qualquer tentativa de varredura usando NMAP ou qualquer outro portscan será bloqueada. Recomendo a utilização de um firewall local, segue o exemplo de um script bastante efetivo.

#!/bin/sh

SYSCTL=”/sbin/sysctl -w”

IPT=”/sbin/iptables”
IPTS=”/sbin/iptables-save”
IPTR=”/sbin/iptables-restore”

INET_IFACE=”eth0″

LO_IFACE=”lo”
LO_IP=”127.0.0.1″

# Save and Restore arguments handled here
if [ “$1” = “save” ]
then
echo -n “Saving firewall to /etc/sysconfig/iptables … ”
$IPTS > /etc/sysconfig/iptables
echo “done”
exit 0
elif [ “$1” = “restore” ]
then
echo -n “Restoring firewall from /etc/sysconfig/iptables … ”
$IPTR < /etc/sysconfig/iptables echo "done" exit 0 fi echo "Loading kernel modules ..." # This enables SYN flood protection. if [ "$SYSCTL" = "" ] then echo "1" > /proc/sys/net/ipv4/tcp_syncookies
else
$SYSCTL net.ipv4.tcp_syncookies=”1″
fi

# This enables source validation by reversed path according to RFC1812.
if [ “$SYSCTL” = “” ]
then
echo “1” > /proc/sys/net/ipv4/conf/all/rp_filter
else
$SYSCTL net.ipv4.conf.all.rp_filter=”1″
fi

# This kernel parameter instructs the kernel to ignore all ICMP
if [ “$SYSCTL” = “” ]
then
echo “1” > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
else
$SYSCTL net.ipv4.icmp_echo_ignore_broadcasts=”1″
fi

# This option can be used to accept or refuse source routed packets.
if [ “$SYSCTL” = “” ]
then
echo “0” > /proc/sys/net/ipv4/conf/all/accept_source_route
else
$SYSCTL net.ipv4.conf.all.accept_source_route=”0″

fi

# However, we’ll ensure the secure_redirects option is on instead.
if [ “$SYSCTL” = “” ]
then
echo “1” > /proc/sys/net/ipv4/conf/all/secure_redirects
else
$SYSCTL net.ipv4.conf.all.secure_redirects=”1″
fi

# This option logs packets from impossible addresses.
if [ “$SYSCTL” = “” ]
then
echo “1” > /proc/sys/net/ipv4/conf/all/log_martians
else
$SYSCTL net.ipv4.conf.all.log_martians=”1″
fi

echo “Flushing Tables …”

# Reset Default Policies
$IPT -P INPUT ACCEPT
$IPT -P FORWARD ACCEPT
$IPT -P OUTPUT ACCEPT

# Flush all rules
$IPT -F

# Erase all non-default chains
$IPT -X

if [ “$1” = “stop” ]
then
echo “Firewall completely flushed! Now running with no firewall.”
exit 0
fi

$IPT -P INPUT DROP
$IPT -P OUTPUT DROP
$IPT -P FORWARD DROP

echo “Create and populate custom rule chains …”

# Create a chain to filter INVALID packets

$IPT -N bad_packets

# Create another chain to filter bad tcp packets

$IPT -N bad_tcp_packets

# Create separate chains for icmp, tcp (incoming and outgoing),
# and incoming udp packets.

$IPT -N icmp_packets

# Used for UDP packets inbound from the Internet
$IPT -N udp_inbound

# Used to block outbound UDP services from internal network
$IPT -N udp_outbound

# Used to allow inbound services if desired
$IPT -N tcp_inbound

# Used to block outbound services from internal network
$IPT -N tcp_outbound

# Drop INVALID packets immediately
$IPT -A bad_packets -p ALL -m state –state INVALID -j LOG –log-prefix ‘fp=bad_packets:1 a=DROP’ –log-level 4

$IPT -A bad_packets -p ALL -m state –state INVALID -j DROP

# Then check the tcp packets for additional problems
$IPT -A bad_packets -p tcp -j bad_tcp_packets

# All good, so return
$IPT -A bad_packets -p ALL -j RETURN

# bad_tcp_packets chain

$IPT -A bad_tcp_packets -p tcp ! –syn -m state –state NEW -j LOG –log-prefix ‘fp=bad_tcp_packets:1 a=DROP’ –log-level 4
$IPT -A bad_tcp_packets -p tcp ! –syn -m state –state NEW -j DROP

$IPT -A bad_tcp_packets -p tcp –tcp-flags ALL NONE -j LOG –log-prefix ‘fp=bad_tcp_packets:2 a=DROP’ –log-level 4
$IPT -A bad_tcp_packets -p tcp –tcp-flags ALL NONE -j DROP

$IPT -A bad_tcp_packets -p tcp –tcp-flags ALL ALL -j LOG –log-prefix ‘fp=bad_tcp_packets:3 a=DROP’ –log-level 4
$IPT -A bad_tcp_packets -p tcp –tcp-flags ALL ALL -j DROP

$IPT -A bad_tcp_packets -p tcp –tcp-flags ALL FIN,URG,PSH -j LOG –log-prefix ‘fp=bad_tcp_packets:4 a=DROP’ –log-level 4
$IPT -A bad_tcp_packets -p tcp –tcp-flags ALL FIN,URG,PSH -j DROP

$IPT -A bad_tcp_packets -p tcp –tcp-flags ALL SYN,RST,ACK,FIN,URG -j LOG –log-prefix ‘fp=bad_tcp_packets:5 a=DROP’ –log-level 4
$IPT -A bad_tcp_packets -p tcp –tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP

$IPT -A bad_tcp_packets -p tcp –tcp-flags SYN,RST SYN,RST -j LOG –log-prefix ‘fp=bad_tcp_packets:6 a=DROP’ –log-level 4
$IPT -A bad_tcp_packets -p tcp –tcp-flags SYN,RST SYN,RST -j DROP

$IPT -A bad_tcp_packets -p tcp –tcp-flags SYN,FIN SYN,FIN -j LOG –log-prefix ‘fp=bad_tcp_packets:7 a=DROP’ –log-level 4
$IPT -A bad_tcp_packets -p tcp –tcp-flags SYN,FIN SYN,FIN -j DROP

# All good, so return
$IPT -A bad_tcp_packets -p tcp -j RETURN

# icmp_packets chain

$IPT -A icmp_packets –fragment -p ICMP -j LOG –log-prefix ‘fp=icmp_packets:1 a=DROP’ –log-level 4
$IPT -A icmp_packets –fragment -p ICMP -j DROP

$IPT -A icmp_packets -p ICMP -s 0/0 –icmp-type 8 -j DROP

# Time Exceeded
$IPT -A icmp_packets -p ICMP -s 0/0 –icmp-type 11 -j ACCEPT

# Not matched, so return so it will be logged
$IPT -A icmp_packets -p ICMP -j RETURN

# Drop netbios calls
$IPT -A udp_inbound -p UDP -s 0/0 –destination-port 137 -j DROP
$IPT -A udp_inbound -p UDP -s 0/0 –destination-port 138 -j DROP

# Not matched, so return for logging
$IPT -A udp_inbound -p UDP -j RETURN

# No match, so ACCEPT
$IPT -A udp_outbound -p UDP -s 0/0 -j ACCEPT

# Web Server

# HTTP
$IPT -A tcp_inbound -p TCP -s 0/0 –destination-port 80 -j ACCEPT

# HTTPS (Secure Web Server)
$IPT -A tcp_inbound -p TCP -s 0/0 –destination-port 443 -j ACCEPT

# sshd
$IPT -A tcp_inbound -p TCP -s 0/0 –destination-port 22 -j ACCEPT

# Not matched, so return so it will be logged
$IPT -A tcp_inbound -p TCP -j RETURN

# No match, so ACCEPT
$IPT -A tcp_outbound -p TCP -s 0/0 -j ACCEPT

echo “Process INPUT chain …”

# Allow all on localhost interface
$IPT -A INPUT -p ALL -i $LO_IFACE -j ACCEPT

# Drop bad packets
$IPT -A INPUT -p ALL -j bad_packets

# Accept Established Connections
$IPT -A INPUT -p ALL -i $INET_IFACE -m state –state ESTABLISHED,RELATED -j ACCEPT

# Route the rest to the appropriate user chain
$IPT -A INPUT -p TCP -i $INET_IFACE -j tcp_inbound
$IPT -A INPUT -p UDP -i $INET_IFACE -j udp_inbound
$IPT -A INPUT -p ICMP -i $INET_IFACE -j icmp_packets

# Drop without logging broadcasts that get this far.
$IPT -A INPUT -m pkttype –pkt-type broadcast -j DROP

# Log packets that still don’t match
$IPT -A INPUT -j LOG –log-prefix “fp=INPUT:99 a=DROP ”

echo “Process FORWARD chain …”

echo “Process OUTPUT chain …”

$IPT -A OUTPUT -m state -p icmp –state INVALID -j DROP

# Localhost
$IPT -A OUTPUT -p ALL -s $LO_IP -j ACCEPT
$IPT -A OUTPUT -p ALL -o $LO_IFACE -j ACCEPT

# To internet
$IPT -A OUTPUT -p ALL -o $INET_IFACE -j ACCEPT

# Log packets that still don’t match
$IPT -A OUTPUT -j LOG –log-prefix ‘fp=OUTPUT:99 a=DROP’ –log-level 4

echo “Load rules for mangle table …”

Referências

Firewall Script

Portsentry decoders and rules issues

Author: alexos