Глава 6. Сетевая безопасность посредством Python

На мой взгляд, сетевая безопасность достаточно хитроумная тема для написания. Причём причина не техническая, а скорее, связанная с установкой верной сферы. Границы сетевой безопасности настолько широки, что они затрагивают все семь уровней модели OSI. Начиная с 1 уровня обвязки кабелями вплоть до уязвимости транспортного протокола на 4 уровне и до 7 уровня со спуфингом человека посредине, сетевая безопасность есть везде. Данная проблема усугубляется всеми вновь обнаруживаемыми уязвимостями, которые порой возникают каждый день. Это даже не включает в себя социальный человеческий аспект сетевой безопасности.

Итак, в данной главе я бы хотел установить рамки того, что мы будем обсуждать. Как мы и делали это до сих пор, в основном мы будем сосредоточены на применении Python для безопасности сетевых устройств на OSI уровнях 3 и 4. Мы взглянем на инструменты Python, которые мы сможем применять для управления индивидуальными компонентами, а также применения Python в качестве связующего элемента для соединения различных компонентов, поэтому мы можем рассматривать сетевую безопасность в целостном виде. В данной главе я рассмотрю следующее:

  • Установку лаборатории

  • Scapy Python для проверки безопасности

  • Списки доступа

  • Ретроспективный анализ с помощью системного журнала и UFW и с применением Python

  • Прочие инструменты, такие как список фильтрации MAC адресов, частные VLAN, а также привязка IP таблицы Python

Установка лаборатории

Те устройства, которые будут применяться в данной главе слегка отличаются от применявшихся в предыдущих главах. В своих предыдущих главах мы были изолированы неким определённым устройством, сосредотачиваясь на самом предмете обсуждения. В данной главе мы будем применять слегка больше устройств в своей лаборатории для иллюстрации применяемого нами инструментария.

Мы будем применять то же самое средство Cisco VIRL с четырьмя узлами, два сервера хостов, а также два сетевых устройства. Если вам требуется освежить в памяти Cisco VIRL, не постесняйтесь вернуться назад к Главе 2, Взаимодействие с сетевым устройством на нижнем уровне, в которо мы впервые ввели данный инструментарий:

 

Рисунок 6.1


Топология лаборатории

[Совет]Совет

Отметим, что все перечисленные адреса будут другими в вашей собственной лаборатории. Они приводятся здесь лишь для простоты ссылки по тексту главы.

Как это уже показывалось, мы переименуем свой хост в самой вершине в качестве своего клиента, а нижний хост в свой сервер. Это аналогично клиенту Интернета, пытающемуся получить доступ к некоторому корпоративному серверу внутри нашей сетевой среды. Мы снова воспользуемся имеющейся опцией Shared flat network для своей сети управления чтобы получить доступ к имеющемуся управлению вне полосы:

 

Рисунок 6.2



Для двух своих коммутаторов я выбираю применение OSPF (Open Shortest Path First ) в качестве IGP и помещаю оба устройства в область 0. По умолчанию, BGP включен в обоих имеющихся устройствах с применением AS 1. При автоматической настройке установок все подключаемые к нашим хостам Ubuntu интерфейсы помещаются в область 1 OSPF, поэтому они будут отображаться как маршрутизаторы внутренней области. Установленный настройки NX-OSv отображены здесь, а имеющаяся конфигурация IOS и вывод аналогичны:


interface Ethernet2/1
  description to iosv-1
  no switchport
  mac-address fa16.3e00.0001
  ip address 10.0.0.6/30
  ip router ospf 1 area 0.0.0.0
  no shutdown

interface Ethernet2/2
  description to Client
  no switchport
  mac-address fa16.3e00.0002
  ip address 10.0.0.9/30
  ip router ospf 1 area 0.0.0.0
  no shutdown

nx-osv-1# sh ip route
<пропуск>
10.0.0.12/30, ubest/mbest: 1/0
 *via 10.0.0.5, Eth2/1, [110/41], 04:53:02, ospf-1, intra
192.168.0.2/32, ubest/mbest: 1/0
 *via 10.0.0.5, Eth2/1, [110/41], 04:53:02, ospf-1, intra
<пропуск>
 	   

Имеющийся сосед OSPF и вывод BGP для NX-OSv показан здесь; вывод для IOSv аналогичен:


nx-osv-1# sh ip ospf neighbors
 OSPF Process ID 1 VRF default
 Total number of neighbors: 1
 Neighbor ID Pri State Up Time Address Interface
 192.168.0.2 1 FULL/DR 04:53:00 10.0.0.5 Eth2/1

nx-osv-1# sh ip bgp summary
BGP summary information for VRF default, address family IPv4 Unicast
BGP router identifier 192.168.0.1, local AS number 1
BGP table version is 5, IPv4 Unicast config peers 1, capable peers 1
2 network entries and 2 paths using 288 bytes of memory
BGP attribute entries [2/288], BGP AS path entries [0/0]
BGP community entries [0/0], BGP clusterlist entries [0/0]

Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
192.168.0.2 4 1 321 297 5 0 0 04:52:56 1
		

Все хосты в нашей сетевой среде работают под управлением Ubuntu 14.04, аналогично той ВМ Ubuntu 16.04, которую мы применяли до этого момента:


cisco@Server:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.2 LTS
Release: 14.04
Codename: trusty
		

На обоих наших хостах Ubuntu имеются по два сетевых интерфейса, причём Eth0 подключён к сети управления, а Eth1 соединяется с имеющимися сетевыми устройствами. Все маршрутизаторы для имеющегося устройства петли напрямую подключаются к его сетевому блоку, а наша сетевая среда удалённого хоста соединяет статическим маршрутом Eth1 с маршрутизатором по умолчанию, идущим далее в нашу сеть управления:


cisco@Client:~$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.16.1.2 0.0.0.0 UG 0 0 0 eth0
10.0.0.4 10.0.0.9 255.255.255.252 UG 0 0 0 eth1
10.0.0.8 0.0.0.0 255.255.255.252 U 0 0 0 eth1
10.0.0.8 10.0.0.9 255.255.255.248 UG 0 0 0 eth1
172.16.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
192.168.0.1 10.0.0.9 255.255.255.255 UGH 0 0 0 eth1
192.168.0.2 10.0.0.9 255.255.255.255 UGH 0 0 0 eth1
		

Просто для проверки, давайте выполним ping и отследим весь маршрут чтобы убедиться, что обмен между нашими хостами проходит через имеющиеся сетевые устройства вместо нашего маршрутизатора по умолчанию:


## IP нашего сервера 10.0.0.14
cisco@Server:~$ ifconfig
<пропуск>
eth1 Link encap:Ethernet HWaddr fa:16:3e:d6:83:02
inet addr:10.0.0.14 Bcast:10.0.0.15 Mask:255.255.255.252

## от нашего клиента к сервреру
cisco@Client:~$ ping -c 1 10.0.0.14
PING 10.0.0.14 (10.0.0.14) 56(84) bytes of data.
64 bytes from 10.0.0.14: icmp_seq=1 ttl=62 time=6.22 ms

--- 10.0.0.14 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 6.223/6.223/6.223/0.000 ms
cisco@Client:~$ traceroute 10.0.0.14

traceroute to 10.0.0.14 (10.0.0.14), 30 hops max, 60 byte packets
 1 10.0.0.9 (10.0.0.9) 11.335 ms 11.745 ms 12.113 ms
 2 10.0.0.5 (10.0.0.5) 24.221 ms 41.635 ms 41.638 ms
 3 10.0.0.14 (10.0.0.14) 37.916 ms 38.275 ms 38.588 ms
cisco@Client:~$
		

Прекрасно! У нас есть своя лаборатория и мы готовы изучать некоторые инструменты безопасности и системы измерений с помощью Python.

Python Scapy

Scapy является мощной программой интерактивной манипуляции пакетами на основе Python. За исключением некоторых коммерческих программ, очень немногие инструменты могут делать то, что способно выполнять Scapy, это я знаю точно. Основная разница состоит в том, что Scapy позволяет вам вручную обрабатывать ваши собственные пакеты на очень базовом уровне. Имеется возможность подделывать или декодировать сетевые пакеты. Давайте взглянем на этот инструмент.

Установка Scapy

На момент написания книги, Scapy 2.3.1 поддерживает Python 2.7. Хотя и были предприняты попутки и ответвления для поддержки Python 3, они всё ещё являются прототипами (по собственным словам Фила), поэтому мы будем применять Python 2.7 для своего случая. Основная идея в том, что Scapy 3 будет только для Python 3 и не будет обратной совместимости со Scapy 2.x.

[Замечание]Замечание

Если вам интересно попробовать прототип Scapy 3, вот его самая последняя версия. Если вы желаете изучить дополнительные подробности и имеющиеся ответвления, здесь ссылка Bitbucket по данной проблематике.

В нашей лаборатории, так как мы вручную обрабатываем пакеты, приходящие от своего клиента к имеющемуся серверу, Scapy должен быть установлен на самом клиенте:


cisco@Client:~$ sudo apt-get update
cisco@Client:~$ sudo apt-get install git
cisco@Client:~$ git clone https://github.com/secdev/scapy
cisco@Client:~$ cd scapy/
cisco@Client:~/scapy$ sudo python setup.py install
		

Вот некая быстрая проверка для того чтобы убедиться, что все пакеты установлены правильно:


cisco@Client:~/scapy$ python
Python 2.7.6 (default, Mar 22 2014, 22:59:56)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from scapy.all import *
		

Примеры взаимодействия

В своём первом примере мы поработаем руками с неким пакетом ICMP (Internet Control Message Protocol, Межсетевого протокола управляющих сообщений) на своём клиенте и отправим его на наш сервер. На стороне самого сервера мы применим tcpdump в фильтре хоста для просмотра входящего пакета:


## Сторона клиента
cisco@Client:~/scapy$ sudo scapy
<пропуск>
Welcome to Scapy (2.3.3.dev274)
>>> send(IP(dst="10.0.0.14")/ICMP())
.
Sent 1 packets.
>>>

## Сторона сервера
cisco@Server:~$ sudo tcpdump -i eth1 host 10.0.0.10
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes
02:45:16.400162 IP 10.0.0.10 > 10.0.0.14: ICMP echo request, id 0, seq 0, length 8
02:45:16.400192 IP 10.0.0.14 > 10.0.0.10: ICMP echo reply, id 0, seq 0, length 8
		

Как вы можете видеть, обрабатывать руками некий пакет очень просто. Scapy позволяет вам построить сам уровень пакета при помощи символа слэша (/) в качестве необходимого разделителя. Имеющаяся функция send работает на слое 3 уровня, который заботится для вас о маршрутизации и уровне 2. Также имеется альтернатива sendp(), которая функционирует на 2 уровне, что означает, что вам потребуется определять сам интерфейс и протокол канального уровня.

Давайте рассмотрим перехват самого возвращаемого пакета с применением имеющейся функции отправки запроса (sr, send-request). Мы применяем некий особый вариант с названием sr1 этой функции, который возвращает только один пакет из отправляемого ответа на заданный пакет:


>>> p = sr1(IP(dst="10.0.0.14")/ICMP())
>>> p
<IP version=4L ihl=5L tos=0x0 len=28 id=26713 flags= frag=0L ttl=62 proto=icmp chksum=0x71 src=10.0.0.14 dst=10.0.0.10 options=[] |<ICMPtype=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |>>
		

Один момент, которые следует отметить, состоит в том, что сама функция sr возвращает некий кортеж, содержащий отвеченный и безответный списки:


>>> p = sr(IP(dst="10.0.0.14")/ICMP())
>>> type(p)
<type 'tuple'>

## распаковка
>>> ans,unans = sr(IP(dst="10.0.0.14")/ICMP())
>>> type(ans)
<class 'scapy.plist.SndRcvList'>
>>> type(unans)
<class 'scapy.plist.PacketList'>
		

Если вам необходимо только взглянуть на сам список паетов ответа, мы можем просмотреть его в другом кортеже, содержащем те пакеты, которые мы должны отправить помимо самого возвращаемого пакета:


>>> for i in ans:
...     print(type(i))
...
<type 'tuple'>
>>> for i in ans:
...     print i
...
(<IP frag=0 proto=icmp dst=10.0.0.14 |<ICMP |>>, <IP version=4L ihl=5L tos=0x0 len=28 id=27062 flags= frag=0L ttl=62 proto=icmp chksum=0xff13 src=10.0.0.14 dst=10.0.0.10 options=[] |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |>>)
		

Scapy также предоставляет некую конструкцию уровня 7 помимо всего прочего, например, некий запрос DNS. В приводимом ниже примере мы опрашиваем некий открытый сервер DNS для разрешения www.google.com:


>>> p =
sr1(IP(dst="8.8.8.8")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.google.com")))
>>> p
<IP version=4L ihl=5L tos=0x0 len=76 id=21743 flags= frag=0L ttl=128 proto=udp chksum=0x27fa src=8.8.8.8 dst=172.16.1.152 options=[] |<UDP sport=domain dport=domain len=56 chksum=0xc077 |<DNS id=0 qr=1L opcode=QUERY aa=0L tc=0L rd=1L ra=1L z=0L ad=0L cd=0L rcode=ok qdcount=1 ancount=1 nscount=0 arcount=0 qd=<DNSQR qname='www.google.com.' qtype=A qclass=IN |> an=<DNSRR rrname='www.google.com.' type=A rclass=IN ttl=299 rdata='172.217.3.164' |> ns=None ar=None |>>>
>>>
		

Снифинг

Scapy можно также применять для простого захвата пакетов:


>>> a = sniff(filter="icmp and host 172.217.3.164", count=5)
>>> a.show()
0000 Ether / IP / TCP 192.168.225.146:ssh > 192.168.225.1:50862 PA / Raw
0001 Ether / IP / ICMP 192.168.225.146 > 172.217.3.164 echo-request 0 / Raw
0002 Ether / IP / ICMP 172.217.3.164 > 192.168.225.146 echo-reply 0 / Raw
0003 Ether / IP / ICMP 192.168.225.146 > 172.217.3.164 echo-request 0 / Raw
0004 Ether / IP / ICMP 172.217.3.164 > 192.168.225.146 echo-reply 0 / Raw
>>>
		

Мы можем взглянуть на полученные пакеты более подробно, в том числе в сыром виде:


>>> for i in a:
... print i.show()
...
<пропуск>
###[ Ethernet ]###
 dst= <>
 src= <>
 type= 0x800
###[ IP ]###
 version= 4L
 ihl= 5L
 tos= 0x0
 len= 84
 id= 15714
 flags= DF
 frag= 0L
 ttl= 64
 proto= icmp
 chksum= 0xaa8e
 src= 192.168.225.146
 dst= 172.217.3.164
 options
###[ ICMP ]###
 type= echo-request
 code= 0
 chksum= 0xe1cf
 id= 0xaa67
 seq= 0x1
###[ Raw ]###
 load= 'xd6xbfxb1Xx00x00x00x00x1axdcnx00x00x00x00x00x10x11x12x13x14x15x16x17x18x19x1ax1bx1cx1dx1ex1f !"#$%&'()*+,-./01234567'
None
		

Давайте продолжим и посмотрим как мы можем применять Scapy для некоторой общей проверки безопасности.

Сканирование порта TCP

Самым первым шагом для любых потенциального хакеров почти всегда является попытка изучить какие службы открыты в данной сетевой среде с тем, чтобы они могли сосредоточить свои усилия на определённой атаке. Конечно, нам необходимо открыть определённые порты чтобы обслуживать своих потребителей, однако нам также следует закрыть все открытые порты, которые не являются обязательными для снижения имеющегося риска. Мы можем воспользоваться Scapy для осуществления простого сканирования открытых портов.

Мы можем отправить некий пакет SYN и посмотреть ответит ли определённый сервер с помощью SYN-ACK:


>>> p = sr1(IP(dst="10.0.0.14")/TCP(sport=666,dport=23,flags="S"))
>>> p.show()
###[ IP ]###
 version= 4L
 ihl= 5L
 tos= 0x0
 len= 40
 id= 25373
 flags= DF
 frag= 0L
 ttl= 62
 proto= tcp
 chksum= 0xc59b
 src= 10.0.0.14
 dst= 10.0.0.10
 options
###[ TCP ]###
 sport= telnet
 dport= 666
 seq= 0
 ack= 1
 dataofs= 5L
 reserved= 0L
 flags= RA
 window= 0
 chksum= 0x9907
 urgptr= 0
 options= {}
		

Отметим, что в приведённом здесь выводе определённый сервер отвечает RESET+ACK по порту TCP 23. Однако, порт TCP 22 открыт, тем самым возвращается SYN-ACK:


>>> p = sr1(IP(dst="10.0.0.14")/TCP(sport=666,dport=22,flags="S"))
>>> p.show()
###[ IP ]###
 version= 4L
<пропуск>
 proto= tcp
 chksum= 0x28b5
 src= 10.0.0.14
 dst= 10.0.0.10
 options
###[ TCP ]###
 sport= ssh
 dport= 666
<пропуск>
 flags= SA
<пропуск>
		

Мы также можем просканировать некий диапазон портов с 20 по 22; отметим, что мы применяем для отправки- приёма sr() вместо обычного sr1() отправить- принять- один- пакет:


>>> ans,unans = sr(IP(dst="10.0.0.14")/TCP(sport=666,dport=(20,22),flags="S"))
>>> for i in ans:
...     print i
...
(<IP frag=0 proto=tcp dst=10.0.0.14 |<TCP sport=666 dport=ftp_data flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=40 id=4126 flags=DF frag=0L ttl=62 proto=tcp chksum=0x189b src=10.0.0.14 dst=10.0.0.10 options=[] |<TCP sport=ftp_data dport=666 seq=0 ack=1 dataofs=5L reserved=0L flags=RA window=0 chksum=0x990a urgptr=0 |>>)
(<IP frag=0 proto=tcp dst=10.0.0.14 |<TCP sport=666 dport=ftp flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=40 id=4127 flags=DF frag=0L ttl=62 proto=tcp chksum=0x189a src=10.0.0.14 dst=10.0.0.10 options=[] |<TCP sport=ftp dport=666 seq=0 ack=1 dataofs=5L reserved=0L flags=RA window=0 chksum=0x9909 urgptr=0 |>>)
(<IP frag=0 proto=tcp dst=10.0.0.14 |<TCP sport=666 dport=ssh flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=44 id=0 flags=DF frag=0L ttl=62 proto=tcp chksum=0x28b5 src=10.0.0.14 dst=10.0.0.10 options=[] |<TCP sport=ssh dport=666 seq=4187384571 ack=1 dataofs=6L reserved=0L flags=SA window=29200 chksum=0xaaab urgptr=0 options=[('MSS', 1460)] |>>)
>>>
		

Мы также можем определить некую сеть назначения вместо отдельного хоста. Как вы можете видеть из имеющегося блока 10.0.0.8/29 , хосты 10.0.0.9, 10.0.0.13 и 10.0.0.14 возвращаются с SA, что соответствует двум имеющимся сетевым устройствам и одному хосту:


>>> ans,unans = sr(IP(dst="10.0.0.8/29")/TCP(sport=666,dport=(22),flags="S"))
>>> for i in ans:
...     print(i)
...
(<IP frag=0 proto=tcp dst=10.0.0.9 |<TCP sport=666 dport=ssh flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=44 id=7304 flags= frag=0L ttl=64 proto=tcp chksum=0x4a32 src=10.0.0.9 dst=10.0.0.10 options=[] |<TCP sport=ssh dport=666 seq=541401209 ack=1 dataofs=6L reserved=0L flags=SA window=17292 chksum=0xfd18 urgptr=0 options=[('MSS', 1444)] |>>)
(<IP frag=0 proto=tcp dst=10.0.0.14 |<TCP sport=666 dport=ssh flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=44 id=0 flags=DF frag=0L ttl=62 proto=tcp chksum=0x28b5 src=10.0.0.14 dst=10.0.0.10 options=[] |<TCP sport=ssh dport=666 seq=4222593330 ack=1 dataofs=6L reserved=0L flags=SA window=29200 chksum=0x6a5b urgptr=0 options=[('MSS', 1460)] |>>)
(<IP frag=0 proto=tcp dst=10.0.0.13 |<TCP sport=666 dport=ssh flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=44 id=41992 flags= frag=0L ttl=254 proto=tcp chksum=0x4ad src=10.0.0.13 dst=10.0.0.10 options=[] |<TCP sport=ssh dport=666 seq=2167267659 ack=1 dataofs=6L reserved=0L flags=SA window=4128 chksum=0x1252 urgptr=0 options=[('MSS', 536)] |>>)
		

Основываясь на том, что мы изучили на данный момент, мы можем сделать некий простой сценарий для многократного применения, scapy_tcp_scan_1.py. Мы начнём с предложенного импорта Scapy и модуля sys для получения аргументов:


#!/usr/bin/env python2

from scapy.all import *
import sys
		

Наша функция tcp_scan() аналогична тому, что мы видели ранее до этого момента:


def tcp_scan(destination, dport):
    ans, unans = sr(IP(dst=destination)/TCP(sport=666,dport=dport,flags="S"))
        for sending, returned in ans:
            if 'SA' in str(returned[TCP].flags):
                return destination + " port " + str(sending[TCP].dport) + "is open"
            else:
                return destination + " port " + str(sending[TCP].dport) + "is not open"
		

Затем мы можем достать ввод из аргументов, а потом вызвать свою функцию tcp_scan() из main():


New-VM -Name VM01 -Generation 2
		

Помните, что для Scapy необходим доступ с правами root, таким образом нашему сценарию понадобится исполнение в виде sudo:


cisco@Client:~$ sudo python scapy_tcp_scan_1.py "10.0.0.14" 23
<пропуск>
10.0.0.14 port 23 is not open
cisco@Client:~$ sudo python scapy_tcp_scan_1.py "10.0.0.14" 22
<пропуск>
10.0.0.14 port 22 is open
		

Это относительно длинный пример нашего сканирования TCP, который демонстрирует значительную мощность обработки вручную ваших собственных пакетов с помощью Scapy. Он выполняет в определённом интерактивном режиме, а затем завершается использованием некоего простого сценария. Давайте рассмотрим некоторые дополнительные примеры использования Scapy для проверки безопасности.

Сборка ping

Давайте предположим, что наша сетевая среда содержит некий замес машин Windows, Unix и Linux, причём пользователи добавляют свои собственные BYOD (Bring Your Own Device, Приветствуются собственные устройства); они могут поддерживать, а могут и нет ping ICMP. Сейчас мы сконструируем некий файл с тремя типами обычного ping для своей сетевой среды, а именно ping ICMP, TCP и UDP в scapy_ping_collection.py:


#!/usr/bin/env python2
from scapy.all import *

def icmp_ping(destination):
# обычный ICMP ping
    ans, unans = sr(IP(dst=destination)/ICMP())
    return ans

def tcp_ping(destination, dport):
# Сканирование TCP SYN
    ans, unans = sr(IP(dst=destination)/TCP(dport=dport,flags="S"))
    return ans

def udp_ping(destination):
# Ошибка недоступности порта ICMP для закрытого порта
    ans, unans = sr(IP(dst=destination)/UDP(dport=0))
    return ans
 	   

В данном примере мы также применим summary() и sprintf() для своего вывода:


def answer_summary(answer_list):
 # Пример лямбда с удобной печатью
    answer_list.summary(lambda(s, r): r.sprintf("%IP.src% is alive"))
 	   
[Совет]Совет

Если вам интересно что такое лямбда в упомянутой выше функции answer_summary(), это некий способ создания небольшой анонимной функции; а именно, функции без имени. дополнительную информацию можно получить в документации Python.

Затем мы можем исполнить все три типа ping в имеющейся сетевой среде в одном сценарии:


def main():
    print("** ICMP Ping **")
    ans = icmp_ping("10.0.0.13-14")
    answer_summary(ans)
    print("** TCP Ping **")
    ans = tcp_ping("10.0.0.13", 22)
    answer_summary(ans)
    print("** UDP Ping **")
    ans = udp_ping("10.0.0.13-14")
    answer_summary(ans)

if __name__ == "__main__":
    main()
 	   

На данном этапе, как я надеюсь, вы согласитесь со мной в том, что, имея возможность создавать свой собственный пакет, вы можете отвечать за тот тип операций и проверок, которые вы бы желали запустить.

Обычные атаки

В этом примере использования Scapy давайте рассмотрим как мы можем построить свой пакет для проведения некоторых видов атак, таких как Ping of Death (Ping смерти) и LAND Attack (Отказ в обслуживании LAN). Иногда вы вначале вы первоначально платите за некое коммерческое программное обеспечение для тестирования проникновения. С помощью Scapy вы сможете проводить собственную проверку, сопровождаемую полным контролем помимо добавления дополнительных тестов в последующем.

Самая первая атака в основном отправляет определённому хосту получателя некий поддельный заголовок IP, например, с длиной 2 и версией IP 3:


def malformed_packet_attack(host):
    send(IP(dst=host, ihl=2, version=3)/ICMP())
 	   

Атака Ping of Death заключается в постоянной отправке пакета ICMP с полезной нагрузкой, превышающей 65 535 байт:


def ping_of_death_attack(host):
    # https://en.wikipedia.org/wiki/Ping_of_death
    send(fragment(IP(dst=host)/ICMP()/("X"*60000)))
 	   

Атака LAND Attack желает перенаправлять все отклики клиента обратно самому этому клиенту и исчерпывает имеющиеся у данного хоста ресурсы:


def land_attack(host):
    # https://en.wikipedia.org/wiki/Denial-of-service_attack
    send(IP(src=host, dst=host)/TCP(sport=135,dport=135))
 	   

Это достаточно старые уязвимости или классические атаки, к которым больше не восприимчивы все современные операционные системы. Для нашего хоста Ubuntu 14.04 никакая из предыдущих атак не приводит к его падению. Однако, при обнаружении дополнительных проблем безопасности, Scapy является великолепным инструментом для того, чтобы начать проверку вашей собственной сетевой среды без необходимости ожидания вмешательства производителя с предоставлением вам неких средств проверки. Это в особенности верно для атак нулевого дня (появления без предварительного уведомления), которые всё чаще и чаще встречаются во всемирном Интернете.

Ресурсы Scapy

Мы потратили достаточно значительные усилия на работу со Scapy в данной главе и это обусловлено тем, насколько высоко именно я оценивая данный инструмент. Я надеюсь, что вы согласитесь со мной, что Scapy является великолепным инструментом чтобы оставить его в инструментарии любого сетевого инженера. Самая приятная сторона Scapy состоит в том, что он постоянно развивается поглощённых им сообществом пользователей.

[Замечание]Замечание

Я настоятельно рекомендую по крайней мере пройтись по руководству Scapy, а также любой интересующей вас документации.

Списки доступа

Имеющиеся списки сетевого доступа обычно являются самой первой линией обороны против внешнего вмешательства и атак. Говоря в целом, маршрутизаторы и коммутаторы обрабатывают пакеты с намного большей скоростью чем серверы, поскольку они применяют такое оборудование, как TCAM (Ternary Content-Addressable Memory, Троичная ассоциативная память). Им нет необходимости видеть информацию уровня самого приложения, вместо этого они просто изучают информацию 3 и 4 уровней и принимают решение будут ли данные пакеты перенаправлены или нет. По этой причине, мы обычно применяем списки доступа сетевого устройства как самый первый этап в охране наших сетевых ресурсов.

В качестве высеченного на камне правила мы желаем поместить списки доступа настолько близко к своему источнику, насколько это возможно. На самом деле, мы также доверяем своему внутреннему хосту и относимся с подозрением к тем клиентам, которые расположены за пределами нашей сетевой среды. Такой список доступа вследствие этого обычно размещается на нашем входном направлении в развёрнутом вовне интерфейсе (интерфейсах). В сценарии нашей лаборатории это означает, что мы поместим некий входящий список доступа в Ethernet2/2, который напрямую соединён с самим хостом клиента.

Если вы не до конца уверены в имеющемся направлении и размещении такого списка доступа, вам могут помочь здесь несколько моментов:

  • Представляйте себе данный список доступа с точки зрения самого сетевого устройства

  • Упростите все пакеты в исчислении просто до IP источника и получателя и воспользуйтесь для примера каким- то хостом:

    • В нашей лаборатории обмен от нашего сервера будет иметь IP источника 10.0.0.14 с IP получателя 10.0.0.10

    • Соответствующий обмен со стороны клиента будет иметь IP источника 10.0.0.10 и IP получателя 10.0.0.14

Очевидно, что все сети различны и как должен строиться ваш список доступа зависит от предоставляемых вашим сервером служб. Однако, для некоторого пограничного списка доступа вы должны предпринять следующее:

  • Запретить применение особых адресов источника RFC 3030, то есть 127.0.0.0/8

  • Запретить пространство RFC 1918, а именно 10.0.0.0/8

  • Запретить наше собственно пространство в качестве источника, для нас это 10.0.0.12/30

  • Разрешить порт входа TCP 22 (SSH) и 80 (HTTP) для хоста 10.0.0.14

  • ОЗапретить всё остальное

Реализация списков доступа при помощи Ansible

Самым простым способом реализовать данный список будет воспользоваться Ansible. Уже рассматривали Ansible в своих предыдущих двух главах, однако было бы неплохо повторить все преимущества применения Ansible при данных обстоятельствах:

  • Более простое управление: Для некоторого длинного списка доступа мы имеем возможность включать выражения для разбиения такого длинного списка доступа на более управляемые куски. Подобные фрагменты меньшего размера впоследствии могут управляться разными командами или владельцами служб.

  • Идемпотентность (повторяемость результата): Мы можем планировать свой плейбук через регулярные интервалы времени и при этом будут выполняться только необходимые изменения.

  • Каждая задача определяется явно: Мы способны разделять проводимое конструирование всех элементов а также применение полученного списка доступа к соответствующему интерфейсу.

  • Многократная применимость: Впоследствии, когда мы добавим дополнительные развёрнутые вовне интерфейсы, нам всего лишь понадобится добавить данное устройство в тот перечень устройств, которым предназначается данный список доступа.

  • Расширяемость: Вы отметите, что мы можем применять один и тот же плейбук для построения определённого списка доступа и его применения к надлежащему интерфейсу. Мы можем начать с небольшого и рсширяться на отдельные плейбуки в будущем по мере возникновения потребностей.

Файл хоста достаточно будничен:


[nxosv-devices]
nx-osv-1 ansible_host=172.16.1.155 ansible_username=cisco
ansible_password=cisco
		

А теперь давайте объявим необходимые переменные в своём плейбуке:


---
- name: Configure Access List
  hosts: "nxosv-devices"
  gather_facts: false
  connection: local

vars:
  cli:
    host: "{{ ansible_host }}"
    username: "{{ ansible_username }}"
    password: "{{ ansible_password }}"
    transport: cli
		

Чтобы сберечь свободное место, мы проиллюстрируем только запрет пространства RFC 1918. Реализация запрета RFC 3030 и нашего собственного пространства будут идентичны всем шагам, применяемым к нашему пространству RFC 1918. Отметим, что мы не можем запрещать в своём плейбуке 10.0.0.0/8, так как наша конфигурация в настоящее время применяет сетевую среду 10.0.0.0 для адресации. Конечно, мы выполняем вначале разрешение отдельного хоста и запрет 10.0.0.0/8 для полноты, но в данном примере просту выбираем пропуск этого:


tasks:
  - nxos_acl:
    name: border_inbound
    seq: 20
    action: deny
    proto: tcp
    src: 172.16.0.0/12
    dest: any
    log: enable
    state: present
    provider: "{{ cli }}"
  - nxos_acl:
    name: border_inbound
    seq: 40
    action: permit
    proto: tcp
    src: any
    dest: 10.0.0.14/32
    dest_port_op: eq
    dest_port1: 22
    state: present
    log: enable
    provider: "{{ cli }}"
  - nxos_acl:
    name: border_inbound
    seq: 50
    action: permit
    proto: tcp
    src: any
    dest: 10.0.0.14/32
    dest_port_op: eq
    dest_port1: 80
    state: present
    log: enable
    provider: "{{ cli }}"
  - nxos_acl:
    name: border_inbound
    seq: 60
    action: permit
    proto: tcp
    src: any
    dest: any
    state: present
    log: enable
    established: enable
    provider: "{{ cli }}"
  - nxos_acl:
    name: border_inbound
    seq: 1000
    action: deny
    proto: ip
    src: any
    dest: any
    state: present
    log: enable
    provider: "{{ cli }}"
		

Заметим, что мы допускаем само установление соединения с источником от своего сервера внутри, чтобы разрешать ему возвраты. Мы применяем окончательное выражение deny ip any any с неким высоким номером последовательности (1000), поэтому мы можем вставлять любые новые записи после него.

Затем мы можем применить такой список доступа к соответствующему интерфейсу:


- name: apply ingress acl to Ethernet 2/2
  nxos_acl_interface:
    name: border_inbound
    interface: Ethernet2/2
    direction: ingress
    state: present
    provider: "{{ cli }}"
		
[Совет]Совет

Данный список доступа в VIRL NX-OSv поддерживается только в самом интерфейсе управления. Вы можете наблюдать следующее предупреждение: Warning: ACL may not behave as expected since only management interface is supported. (ACL может не вести себя ожидаемым образом, так как поддерживается только интерфейс управления.) Если вы настроили acl через CLI. Это нормально для нашей лаборатории, так как наша цель состоит всего лишь в демонстрации такой автоматизации настройки подобного списка доступа.

Это может показаться чрезмерной работой для отдельного списка доступа. Для опытного инженера выполнение данной задачи отнимет больше времени чем простая регистрация в определённом устройстве и настройке его списка доступа. Однако, Помните, что данный плейбук можно применять многократно в дальнейшем, поэтому он сбережёт ваше время в долговременной перспективе.

На моей практике много раз случалось, что несколько записей будут предназначены для одной службы, какое- то число для другой, и так далее. Такие списки доступа имеют естественную тенденцию расти со временем, и становится очень сложно отслеживать имевшийся первоначально и цели каждой записи. Факт состоит в том, что мы можем разбить его на части, что делает управление длинным списком доступа намного более простым.

Списки доступа MAC

В том случае, когда у нас имеется среда L2 или мы вынуждены применять не- IP протоколы в интерфейсах Ethernet, вы всё ещё можете использовать некий список доступа по MAC адресам чтобы разрешать или запрещать хосты на основании MAC адреса. Такие шаги аналогичны описанному списку доступа IP, но имеют дополнительные особенности. Напомним, что для MAC адресов, или физических адресов, самые первые 6 шестнадцатеричных символов относятся к некоторому Уникальному идентификатору организации (OUI, Organizationally Unique Identifier). Поэтому мы можем применять один и тот же список доступа для пометки шаблона запрета определённой группе хостов.

[Замечание]Замечание

Обратим ваше внимание, что мы проверили это для IOSv с модулем ios_config; благодаря самому внутреннему устройству этого модуля, определённое изменение будет удаляться при всяком отдельном исполнении этого плейбука. Вследствие этого мы теряем одно из преимуществ того, что никакие изменения не делаются если в них нет необходимости Ansible.

Сам файл хоста и вся верхняя часть данного плейбука аналогичны тому, что у нас имеется для списка доступа IP; часть наших задач должна отличаться настройкой модулей и аргументов:


<пропуск>
  tasks:
    - name: Deny Hosts with vendor id fa16.3e00.0000
      ios_config:
        lines:
          - access-list 700 deny fa16.3e00.0000 0000.00FF.FFFF
          - access-list 700 permit 0000.0000.0000 FFFF.FFFF.FFFF
        provider: "{{ cli }}"
    - name: Apply filter on bridge group 1
      ios_config:
        lines:
          - bridge-group 1
          - bridge-group 1 input-address-list 700
        parents:
          - interface GigabitEthernet0/1
        provider: "{{ cli }}"
 	   

По мере того, как виртуальные сети становятся всё более популярными, имеющаяся информация L3 порой становится прозрачной для лежащих ниже виртуальных соединений. В подобных случаях список доступа MAC становится хорошим вариантом когда вам требуется ограничивать доступ к таким соединениям.

Поиск в системных журналах

Имеется множество фиксированных нарушений безопасности, которые имели место за продолжительный период времени. Такие тихие нарушения часто дают основания полагать, что и в журналах сервера, и в журналах сетевой среды имелись признаки и следы, указывающие на подозрительные действия. Такие незамеченные действия не были обнаружены не потому что не было информации, а скорее по той причине что было слишком много информации. Искомая нами критически важная информация обычно глубоко зарыта в горах информации, в которой сложно разобраться.

[Совет]Совет

Помимо системных журналов, другим великолепным источником информации журналов для серверов является Незамысловатый межсетевой экран (UFW, Uncomplicated Firewall). Он является интерфейсом для iptable, который в свою очередь является неким сервером межсетевого экрана. UFW упрощает правила управления межсетевым экраном и регистрирует большой объём информации. Для получения дополнительной информации по UFW обратитесь к разделу Прочие инструменты.

В данном разделе мы попытаемся применить Python для поиска в тексте имеющегося системного журнала чтобы выявить те активности, которые мы разыскиваем. Конечно, точные искомые выражения зависят от того устройства, которое мы применяем. Например, Cisco предоставляет некий список сообщений для поиска в системном журнале для любых допущенных нарушения регистрации в списке доступа.

[Совет]Совет

Для более подробного понимания спсика управления доступа регистрации пройдите на соответствующую ссылку.

В нашем случае мы применяем анонимный системный журнал коммутатора Nexus, содержащий примерно 65 000 строк сообщений регистрации:


$ wc -l sample_log_anonymized.log
65102 sample_log_anonymized.log
		

Мы вставили несколько сообщений системного журнала из документации Cisco в качестве тех сообщений журнала, которые мы будем искать:


2014 Jun 29 19:20:57 Nexus-7000 %VSHD-5-VSHD_SYSLOG_CONFIG_I: Configured from vty by
 admin on console0
2014 Jun 29 19:21:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1, Dst IP: 172.16.10.10, Src Port: 0, Dst Port: 0, Src Intf: Ethernet4/1, Protocol: "ICMP"(1), Hit-count = 2589
2014 Jun 29 19:26:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1, Dst IP: 172.16.10.10, Src Port: 0, Dst Port: 0, Src Intf: Ethernet4/1, Protocol: "ICMP"(1), Hit-count = 4561
 	   

Для своего примера мы будем применять простые образцы с регулярными выражениями. Если вы ещё не знакомы с имеющимися в Python регулярными выражениями, вы можете пропустить остаток данного раздела.

Поиск при помощи регулярных выражений

Для своего первого поиска мы просто используем имеющийся модуль регулярного выражения для поиска того выражения, которое мы ищем. Мы применим простейший цикл следующим образом:


#!/usr/bin/env python3

import re, datetime

startTime = datetime.datetime.now()

with open('sample_log_anonymized.log', 'r') as f:
    for line in f.readlines():
        if re.search('ACLLOG-5-ACLLOG_FLOW_INTERVAL', line):
            print(line)

endTime = datetime.datetime.now()
elapsedTime = endTime - startTime
print("Time Elapsed: " + str(elapsedTime))
 	   

Поиск результата занимает около 6 сотых секунды для просмотра всего файла журнала:


$ python3 python_re_search_1.py
2014 Jun 29 19:21:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,

2014 Jun 29 19:26:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,

Time Elapsed: 0:00:00.065436
		

Рекомендуется компилировать своё выражение поиска для более эффективной работы. Это не окажет существенного воздействия, поскольку мы и так достаточно быстро его выполнили. Фактически, именно внутренне присущая интерактивная природа самого Python на практике делает его более медленным. Однако, это будет иметь разницу, когда вы осуществляете поиск в гораздо большем теле текста, поэтому давайте внесём изменения:


searchTerm = re.compile('ACLLOG-5-ACLLOG_FLOW_INTERVAL')

with open('sample_log_anonymized.log', 'r') as f:
    for line in f.readlines():
        if re.search(searchTerm, line):
            print(line)
 	   

Результирующее время на самом деле медленнее:


Time Elapsed: 0:00:00.081541
		

Давайте слегка расширим свой пример. Предположим, что у нас имеется несколько файлов и множество выражений поиска, мы скопируем первоначальный файл в новый:


$ cp sample_log_anonymized.log sample_log_anonymized_1.log
		

Мы также включим поиск для выражения PAM: Authentication failure . Мы добавим другой цикл для поиска в обоих файлах:


term1 = re.compile('ACLLOG-5-ACLLOG_FLOW_INTERVAL')
term2 = re.compile('PAM: Authentication failure')

fileList = ['sample_log_anonymized.log', 'sample_log_anonymized_1.log']

for log in fileList:
    with open(log, 'r') as f:
        for line in f.readlines():
            if re.search(term1, line) or re.search(term2, line):
                print(line)
 	   

Теперь мы можем почувствовать разницу в производительности помимо расширения своих возможностей поиска:


$ python3 python_re_search_2.py
2016 Jun 5 16:49:33 NEXUS-A %DAEMON-3-SYSTEM_MSG: error: PAM: Authentication failure for illegal user AAA from 172.16.20.170 - sshd[4425]

2016 Sep 14 22:52:26.210 NEXUS-A %DAEMON-3-SYSTEM_MSG: error: PAM: Authentication failure for illegal user AAA from 172.16.20.170 - sshd[2811]

<пропуск>

2014 Jun 29 19:21:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,
2014 Jun 29 19:26:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,

<пропуск>

Time Elapsed: 0:00:00.330697
		

Конечно, когда наступает время настройки производительности, дело никогда не закончится нулём и порой зависит от того оборудования, которое вы применяете. Однако важным моментом является выполнение проверок ваших файлов журнала с применением Python на регулярной основе с тем, чтобы вы могли отловить только что появившиеся признаки любых потенциальных нарушений.

Прочие инструменты

Имеются различные прочие инструменты сетевой безопасности, которые мы можем применять и автоматизировать при помощи Python. Давайте взглянем на несколько из них.

Частные VLAN

Виртуальные локальные сети (VLAN) Virtual Local Area Networks присутствуют достаточно продолжительное время. Они по существу являются неким широковещательным доменом, в котором все хосты могут подключаться к некоторому отдельному коммутатору, однако просят разделения на отличные домены, поэтому мы можем разделять хосты в соответствии с чем хост может видеть прочие через широковещательные запросы. На практике, почти постоянно, VLAN соответствуют подсетям IP. Например, в некотором здании корпорации я, скорее всего, я имею по одной подсети для каждого физического этажа, 192.168.1.0/24 для первого этажа, 192.168.2.0/24 для второго этажа. В таком шаблоне мы применяем блок 1/24 для каждого этажа. Это даёт чёткое разграничение моей физической сети наряду с моей логической сетью. Для всех хостов, желающих взаимодействовать помимо своей собственной подсети понадобится пройти через их шлюз 3 уровня, в котором я могу применять некий список доступа для усиления безопасности.

Что произойдёт, когда различные подразделения располагаются на одном и том же этаже? Может так случиться, что команды финансистов и продавцов обе находятся на втором этаже, причём я бы не желал, чтобы имеющиеся у команды продавцов хосты присутствовали в том же самом домене, что и хосты финансовой бригады. Я могу и дальше делить имеющуюся подсеть вниз, однако это может стать утомительным и нарушать ту стандартную схему подсетей, которая была настроена изначально. Именно здесь приходят на помощь частные VLAN.

Такая частная VLAN на самом деле разбивает существующую VLAN на подсети. Имеются три разновидности частных VLAN:

  • Беспорядочный (Promiscuous, P) порт: такой порт позволяет отправлять и получать кадры 2 уровня от любых портов в данной VLAN; они обычно относятся к тем портам, которые подключаются к имеющемуся маршрутизатору 3 уровня.

  • Изолированный (Isolated, I) порт:этот порт допускает взаимодействие только с портами P, причём они обычно подключаются к хостам, когда вы не желаете взаимодействовать с прочими хостами в той же самой VLAN.

  • Порт сообщества (Community, C): обычно они разрешают взаимодействовать с остальными портами того же самого сообщества и портами P.

Мы снова можем применить Ansible, либо имеющимися другими сценариями Python, введёнными ранее, для осуществления такой задачи. На данный момент у нас должно быть достаточно практического опыта и компетентности для реализации такой функциональности если потребуется применять автоматизацию, поэтому я не буду повторять все шаги здесь. Наличие функциональности VLAN понадобится вам много раз для удобства, когда вам потребуется дополнительно изолировать порты в некоторую L2 VLAN.

UFW через Python

Мы вкратце упоминали UFW в плане определённого интерфейса для iptable в хостах Ubuntu. Вот некий краткий обзор:


$ sudo apt-get install ufw
$ sudo ufw status
$ sudo ufw default outgoing
$ sudo ufw allow 22/tcp
$ sudo ufw allow www
$ sudo ufw default deny incoming
		

Мы можем посмотреть текущее состояние UFW:


$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
80/tcp (v6) ALLOW IN Anywhere (v6)
		

Как вы можете видеть, основное преимущество UFW состоит в некотором простом интерфейсе для построения в противном случае сложных правил таблицы IP. Имеется ряд относящихся к Python инструментов, которые мы можем применять с UFW чтобы сделать вещи ещё более простыми:

  • Мы можем применять модуль UFW Ansible для ускорения своих действий. Так как Ansible написан на Python, мы также можем пройти далее и изучить что находится внутри исходного кода этого модуля Python.

  • Имеются обёртывающие UFW модули Python такие, как некий API. Он может упростить интеграцию если вам требуется динамичное изменение правил UFW на основе определённых событий.

  • UFW сам по себе написан на Python. Пго этой причине вы мыожете применять имеющиеся знания Python если вам ещё понадобится расширить имеющийся в настоящий момент набор команд.

UFW доказал что он является хорошим инструментом для охраны вашего сетевого сервера.

Выводы

В данной главе мы рассмотрели сетевую безопасность с применением Python. Мы использовали инструментарий VIRL Cisco для установки своей лаборатории с присуствием как хостов, так и сетевого оборудования NX-OSv и IOSv. Затем мы предприняли некий экскурс по Scapy, который позволяет нам строить пакеты с нуля. Scapy можно применять в определённом интерактивном режиме для быстрой проверки, причём по окончанию интерактивного режима мы можем разместить все свои шаги в некотором файле для более масштабированного тестирования. Он может применяться для выполнения различных проверок проникновения для известных уязвимостей.

Мы также взглянули на то как мы можем применять и список доступа IP и список доступа MAC для защиты своей сетевой среды. Они обычно являются первой линией обороны при вашей сетевой защите. Применяя Ansible мы также получаем возможность соответствующим образом и быстро развёртывать списки доступа на множество устройств.

Системные журналы и прочие файлы регистрации содержат полезную информацию, которую следует регулярно причёсывать для определения самых ранних признаков нарушений. Используя регулярные выражения Python мы можем систематически выполнять поиск известных записей регистраций, которые могут указывать нам те события безопасности, которые требуют нашего внимания. Помимо тех инструментов, которые мы обсудили, частные VLAN и UFW относятся к другим полезным инструментам, которые мы можем применять для большей защиты безопасности.

В следующей главе мы рассмотрим как применять Python для сетевого мониторинга. Монгиторинг позволяет нам знать что происходит в нашей сетевой среде и само состояние нашей сети.