6.4. Проверка доступности TCP/IP сервиса

Описание.  Кандидат BSDA должен уметь определить доступна ли удалённая система через TCP/IP и, если да, уметь при помощи telnet(1) убедиться отвечает ли сервис на клиентские запросы.

Практика. ping(8), traceroute(8), telnet(1), nc(1) на FreeBSD и OpenBSD

Комментарий

6.4.1. ping(8)

Утилита ping предназначена для того, чтобы при помощи отправки ICMP пакетов убедиться в работоспособности хоста. Утилита настолько широкоизвестна, что наверное нет необходимости подробно о ней говорить. Заметим только, то, о чём многие порой забывают: у утилиты ping(8) есть масса разных аргументов, и её можно использовать для разнообразнейшей диагностики. В скриптах полезно бывает применять опцию -c при помощи которой можно сказать после какого числа посланных и принятых (или не принятых) ICMP пакетов работа программы остановится (по умолчанию, она работает бесконечно, пока пользователь не нажмёт сочетание клавиш Ctrl+C). Опция -i позволяет задать интервал времени между пакетами (по умолчанию 1 секунда). Опция -I позволяет задать конкретный интерфейс, с которого будет отправлен пакет (если вопреки таблице маршрутизации его надо послать куда-то в другое место). -S позволяет задать некоторый конкретный IP адрес источника пинга. Очень разрушительная опция, но весьма полезная в диагностике состояния сети — -f позволяет совместно с опцией -c отправить одновременно множество ICMP пакетов (устроить так называемый флуд (flood — наводнение, поток, жарг. болтовня)). В случае наличия помех в сети часть пакетов будет потеряна, при нормальном пинге пакеты скорее всего пройдут полностью. Ниже дан пример такой «атаки» совершённой в сети с некачественным оборудованием (не качественным на физическом уровне).

$ ping -f -c 10000 192.168.0.12
Password:
PING 192.168.0.12 (192.168.0.12): 56 data bytes
...............................................
--- 192.168.0.12 ping statistics ---
10000 packets transmitted, 6301 packets received, 36% packet loss
round-trip min/avg/max/stddev = 0.159/0.173/0.594/0.017 ms
  

Как видим, 36% пакетов было потеряно. В нормальной локальной сети, с хорошими проводами и не перенапряжёнными коммутаторами, такая «атака» не должна приводить к значительным потерям пакетов.

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

Не все хосты обязаны отвечать на запросы программы ping(8). Обычно программа ping(8) посылает пакеты, которые называются ECHO_REQUEST и ожидает получить пакет ECHO_RESPONSE. Однако варианты ответов могут быть разными. Ниже показан вариант с ответом «Destination Port Unreachable». Такой ответ, однако, означает, что на той стороне есть «живая» машина.

$ ping -c1 192.168.25.24
PING cube.mccme.ru (192.168.25.24): 56 data bytes
92 bytes from cube.mccme.ru (192.168.25.24): Destination Port Unreachable
Vr HL TOS Len ID Flg off TTL Pro cks  Src  Dst
 4 5 00 5400 f8d6 0 0000 40 01 fcfe 192.168.25.158 192.168.25.24

--- 192.168.25.24 ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss
  

Немного напоминает знаменитый диалог Винни-Пуха и Пятачка:

Тут он наклонился, сунул голову в нору и крикнул:

— Эй! Кто-нибудь дома?

Вместо ответа послышалась какая-то возня, а потом снова стало тихо.

— Я спросил: «Эй! Кто-нибудь дома?» — повторил Пух громко-громко.

— Нет! — ответил чей-то голос. — И незачем так орать, — прибавил он, — я и в первый раз прекрасно тебя понял.

— Простите!  — сказал Винни-Пух. — А что, совсем-совсем никого нет дома?

— Совсем-совсем никого! — отвечал голос. Тут Винни-Пух вытащил голову из норы и задумался.

Он подумал так: «Не может быть, чтобы там совсем-совсем никого не было! Кто-то там всё-таки есть — ведь кто-нибудь должен же был сказать: «Совсем-совсем никого!»»

Не путайте ответы «Destination Port Unreachable» и «Destination Host Unreachable». Последний генерируется маршрутизатором (или, в частном случае вашей собственной машиной), если он не знает куда послать пакет:

$ ping -c1 192.168.25.1
PING 192.168.25.1 (192.168.25.1): 56 data bytes
36 bytes from gateway (172.16.0.1): Destination Host Unreachable
Vr HL TOS Len ID Flg off TTL Pro cks  Src  Dst
 4 5 00 5400 9514 0 0000 40 01 5fd9 172.16.0.2 192.168.25.1


--- 192.168.25.1 ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss
  

В первом («Винни-Пуховском») примере мы получили ответ от машины, которую мы и пинговали, следовательно она всё-таки включена. А во втором примере, пакет не был доставлен машине, так как она не была найдена (в приведённом примере не была найдена не только машина, но даже сеть, в которой она должна находиться).

6.4.2. traceroute(1)

Команда traceroute(1) в некоторых случаях позволяет выяснить маршрут от одного компьютера до другого. Для этого она посылает пакеты на целевую машину последовательно увеличивая параметр TTL (time to live). В норме TTL должен уменьшаться на единицу на каждом маршрутизаторе, пока не станет равным нулю. Если он обнулится, пакет будет отброшен, а отославшей его стороне вернётся пакет ICMP TIME_EXCEEDED. В этом пакете будет присутствовать IP маршрутизатора, который его послал. По этой информации traceroute(1) сможет перечислить машины, через которые идут пакеты до целевой машины.

[Замечание]Замечание
Имейте ввиду: не все маршрутизаторы уменьшают TTL. Некоторые из них могут оказаться прозрачными для traceroute(1).

Для примера, попробуем выяснить маршрут к несуществующей сети:

$ traceroute -n 10.0.0.1
traceroute to 10.0.0.1 (10.0.0.1), 64 hops max, 40 byte packets
 1 172.16.0.1 0.418 ms 0.781 ms 0.228 ms
 2 172.16.0.1 1.703 ms !H 0.585 ms !H 0.491 ms !H
  

Здесь мы выполняли команду traceroute(1) на машине 172.16.0.2. Она не знает маршрута к хосту 10.0.0.1 и пересылает пакет на машину 172.16.0.1 — свой маршрутизатор по умолчанию, а тот вернул ответ «Destination Host Unreachable», о чём свидетельствует флаг !H. Эта строка появилась потому, что брандмауэр на маршрутизаторе 172.16.0.1 не выпускает пакеты предназначенные для приватных сетей на свой дефолтный маршрутизатор возвращая ICMP пакет с сообщением об ошибке. Строка с номером 1 это результат работы первого пакета ICMP, в котором TTL был выставлен в 1. Этот пакет достиг машины 172.16.0.1, но дальнейшей маршрутизации не претерпел, так как у него истёк срок жизни, поэтому сообщение об ошибке сгенерировано не было. И только следующий пакет ICMP с TTL=2, породил сообщение об ошибке.

Далее идут несколько умозрительные примеры, почёрпнутые из справки по команде traceroute(1).

$ traceroute -n 192.168.2.1
traceroute to 192.168.5.1 (192.168.5.1), 64 hops max, 40 byte packet
 1 172.16.0.1 0.418 ms 0.781 ms 0.228 ms
 2 192.168.0.1 39 ms 39 ms 19 ms
 3 192.168.0.1 39 ms 39 ms 19 ms
 4 192.168.1.1 39 ms 40 ms 39 ms
 5 192.168.2.1 39 ms 39 ms 39 ms
  

Заметьте, что строки 2 и 3 совпадают — это происходит потому, что на втором маршрутизаторе имеются ошибки в ядре — система 4.3BSD маршрутизирует пакет с нулевым TTL.

$ traceroute -n 192.168.9.1
traceroute to 192.168.9.1 (192.168.9.1), 64 hops max
 1 172.16.0.1 0.418 ms 0.781 ms 0.228 ms
 2 192.168.0.1 39 ms 39 ms 19 ms
 3 192.168.0.1 39 ms 39 ms 19 ms
 4 192.168.1.1 39 ms 40 ms 39 ms
 5 192.168.2.1 39 ms 39 ms 39 ms
 6 * * *
 7 192.168.4.1 259 ms 499 ms 279 ms
 8 * * *
 9 * * *
10 * * *
11 * * *
12 192.168.9.1 339 ms 279 ms 279 ms
  

Шлюзы 6, 8, 9, 10 и 11 либо не высылают нам ICMP с сообщением «time exceeded», либо у их сообщений слишком маленький TTL и оно нас не достигает. В точности нельзя сказать, что происходит на маршрутизаторе 12. Например, это может быть следствием ошибок в ядре 4.[23]BSD: BSD 4.x (x меньше либо равен 3) высылали сообщение об ошибке используя TTL оригинального пакета. Таким образом, ICMP «time exceeded» принципиально не мог до нас добраться.

У программы traceroute(1) есть ещё один полезный аргумент: -P [TCP|UDP|ICMP|...], с помощью которого можно задать используемый протокол. По умолчанию в системах BSD (и в Linux тоже) traceroute(1) высылает пакеты UDP направленные на абстрактный верхний порт. Такие пакеты могут резать брандмауэры, поэтому и предусмотрена возможность выбора протокола. Опция -I включает протокол ICMP. С её помощью traceroute(1) работает так же, как утилита tracert в Windows.

Ещё большую функциональность предлагает команда hping(8), описанная ниже.

6.4.3. hping(8)

Данная программа не входит в курс BSDA, тем не менее я очень советую с ней поупражняться. Как и программа nmap(1), hping(8) мощное средство тестирования брандмауэров, с её помощью можно генерировать разнообразный «неправильный» трафик.

[Важно]Важно
При помощи команды hping(8) можно сделать так называемую «back door» — чёрный ход. Команда hping(8) может запускаться только с правами суперпользователя. При этом, у неё есть режим в котором она ищет в приходящих пакетах некоторую метку и выполняет команды, которые встретятся после метки. Таким образом, если злоумышленник взломает вашу машину и сможет собрать и запустить hping(8) с нужными аргументами, то потом он сможет выполнять на атакованной системе произвольный код с полномочиями суперпользователя не осуществляя никаких операций связанных с аутентификацией.

Часто машина не отвечает на пакеты ECHO_REQUEST, но при этом на ней успешно работает web-сервер и она отвечает на попытку установить соединение с 80-м портом. Программа hping(8) может «пинговать» хосты не только при помощи ICMP пакетов, которые часто отвергаются брандмауэрами, но так же и при помощи TCP и UDP пакетов.

Так же программа hping(8) позволяет при помощи TCP пакетов с плавно изменяющимся TTL выяснить маршрут к хосту, даже если это не смогла сделать программа traceroute(1). Принцип работы тот, же, но TCP пакеты, мягко говоря, реже уничтожаются брандмауэрами.

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

$ ping -c1 example.org
PING example.org (192.0.34.166): 56 data bytes

--- example.org ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss
# hping -p 80 -S -c 1 example.org
HPING example.org (rl0 192.0.34.166) S set, 40 headers + 0 data bytes
len=46 ip=192.0.34.166 ttl=121 id=47606 sport=80 flags=SA seq=0 win=16384 rtt=18.6 ms

--- example.org hping statistic ---
1 packets tramitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 18.6/18.6/18.6 ms
$ traceroute example.org
traceroute to example.org (192.0.34.166), 64 hops max, 40 byte packets
 1 ****** (***.***.***.***) 0.432 ms 0.244 ms 4.614 ms
 2 ****** (***.***.***.***) 0.751 ms 10.655 ms 1.809 ms
 3 ****** (***.***.***.***) 0.859 ms 5.817 ms 10.049 ms
 4 ****** (***.***.***.***) 13.455 ms 13.785 ms 17.565 ms
 5 ****** (***.***.***.***) 18.931 ms 19.907 ms 18.807 ms
 6 ****** (***.***.***.***) 25.173 ms 20.845 ms 20.466 ms
 7 * * *
 8 * * *
^C
# hping -p 80 -S -c 8 --traceroute example.org
HPING example.org (rl0 192.0.34.166): S set, 40 headers + 0 data bytes
hop=1 TTL 0 during transit from ip=***.***.***.*** name=******
hop=1 hoprtt=0.4 ms
hop=2 TTL 0 during transit from ip=***.***.***.*** name=******
hop=2 hoprtt=0.7 ms
hop=3 TTL 0 during transit from ip=***.***.***.*** name=******
hop=3 hoprtt=1.4 ms
hop=4 TTL 0 during transit from ip=***.***.***.*** name=******
hop=4 hoprtt=11.9 ms
hop=5 TTL 0 during transit from ip=***.***.***.*** name=******
hop=5 hoprtt=28.3 ms
hop=6 TTL 0 during transit from ip=***.***.***.*** name=******
hop=6 hoprtt=40.0 ms
len=46 ip=192.0.34.166 ttl=121 id=48766 sport=80 flags=SA seq=6 win=16384 rtt=20.3 ms
len=46 ip=192.0.34.166 ttl=121 id=8660 sport=80 flags=SA seq=7 win=16384 rtt=32.5 ms

--- example.org hping statistic ---
8 packets tramitted, 8 packets received, 0% packet loss
round-trip min/avg/max = 0.4/16.9/40.0 ms
  

Как видим, если послать «пинг» при помощи пакета TCP на порт 80 с выставленным флагом SYN, на него приходит ответ. (Иначе не мог бы функционировать магазин). Таким же образом, мы можем выяснить и маршрут до него. При этом ни команда ping(8), ни traceroute(8) с задачей не справляются.

Подробное описание программы hping имеется в работе Николая Малых [url://Malyh-hping2-2005].

6.4.4. telnet(1), nc(1)

Основное назначение программы telnet(1) заключается в том, что она предоставляет удалённый терминал, позволяя управлять машиной на расстоянии. К сожалению, программа telnet(1) не шифрует трафик и даже пароли передаёт в открытом виде. Мы покажем чем это опасно, когда будем обсуждать программу tcpdump(1).

[Важно]Важно
Использовать программу telnet(1) по назначению — дурной тон. Это крайне небезопасно! Вместо неё следует использовать программу ssh(1).

Однако программу telnet(1) можно использовать для того, чтобы провести диалог с удалённым сервером, проверить работу сервиса прослушивающего соответствующий порт. Например, при передаче почты, устанавливается связь с 25-м портом почтового сервера и далее идут команды SMTP протокола. Ниже приведён короткий пример такого диалога. Здесь символ | означает, что на этой строке приведено служебное сообщение программы telnet(1), > — строка которую мы вводим с клавиатуры, т.е. её клиент отсылает серверу, < — ответ сервера.

$ telnet mxs.mail.ru 25
| Trying 194.67.23.20...
| Connected to mxs.mail.ru.
| Escape character is '^]'.
< 220 Mail.Ru ESMTP
> HELO somewhere.org
< 250 mx8.mail.ru ready to serve
> QUIT
< 221 mx8.mail.ru closing connection
| Connection closed by foreign host.
  

Обратите внимание на фразу Escape character is '^]'. Если по каким-то причинам вы не можете дождаться ответа сервера и вам надо разорвать соединение, нажмите клавиши <Ctrl>+] и перед вами появится командная строка telnet(1). В ней вы можете набрать команду quit и завершить сеанс:

$ telnet mxs.mail.ru 25
Trying 194.67.23.20...
Connected to mxs.mail.ru.
Escape character is '^]'.
220 Mail.Ru ESMTP
^]
telnet> quit
Connection closed.
  

Работа с telnet(1) может быть неудобна тем, что вы не всегда можете отредактировать свой текст в случае ошибки, к тому же некоторые сервисы, расчитанные на работу с роботом могут завершить соединение по таймауту, если вы набираете команды недостаточно быстро. Наконец, telnet(1) невозможно использовать в скриптах.

Чтобы преодолеть эти недостатки, вместо telnet(1) можно использовать программу nc(1) (netcat). В следующем примере, мы делаем тоже, что и в предыдущем, но две команды SMTP (HELO example.org и QUIT) мы посылаем на стандартный ввод команде nc(1), а на STDOUT мы получаем ответы почтового сервера (вместо команды echo, использована команда printf(1), для того, чтобы напечатать команды HELO и QUIT на двух разных строках):

$ printf 'HELO example.org
QUIT' | nc mxs.mail.ru 25
220 Mail.Ru ESMTP
250 mx19.mail.ru ready to serve
  

Конечно программой nc(1) можно пользоваться и интерактивно, если не назначить ей STDIN, она, как и положено любой UNIX программе, будет брать STDIN с клавиатуры:

$ nc msx.mail.ru 25
< 220 Mail.Ru ESMTP
> HELO somehere.ru
< 250 mx18.mail.ru ready to serve
> QUIT
< 221 mx18.mail.ru closing connection
  

В качестве примера скрипта использующего программу nc(1) приведём программу, которая пытается определить правильность некоторого адреса email. Приведённая программа первым делом разбирает адрес email, находя в нём часть отвечающую за имя сервера. Затем делает запрос к серверу DNS при помощи программы dig(1) определяя имя почтового сервера. И, наконец, при помощи nc(1) проводит диалог с этим сервером пытаясь при помощи команды SMTP RCPT определить существование почтового адреса на данной машине. Почтовый сервер на эту команду должен дать положительный ответ (код больше двухсот, но меньше трёхсот) и надпись <Recipient ok>. Но может и не дать никакого ответа по соображениям безопасности (смотря как настроен почтовый сервер). Тонкости работы команды dig(1) рассмотрены в Раздел 6.5, «Запрос к серверу DNS». Программирование в Bourne shell в Раздел 7.7, «Написание несложных Bourne-скриптов».

#!/bin/sh

if [ $# -lt 1 ]
then
 echo "Usage mailtest.sh user@server"
 exit 1
fi

# Вычленяем имя сервера из почтового адреса
server=`echo $1 | sed s/.*@//`

# Определяем почтовый сервер ответственный за передачу писем на
# сервер $server
mxserver=$( dig $server MX |
  egrep 'MX[[:space:]]+[0-9]+[[:space:]]+[^[:space:]]+.' |
  sort -n -k5,5 |
  head -1 | awk '{print $6}' | sed 's/.$//' )

echo Investigate: $1
echo Server:  $server
echo MX server: $mxserver

# Пытаемся провести SMTP диалог с почтовым сервером
nc $mxserver 25 << EOF
HELO example.org
MAIL From: postmaster@example.org
RCPT To: $1
QUIT
EOF
  

Программа nc(1) штатно присутствует в FreeBSD и OpenBSD, а в DragonFly BSD и NetBSD её надо ставить отдельно, как сторонний продукт.

В Раздел C.2.1.6.4.3, «TCP proxy» можно найти пример в котором nc(1) используется в качестве прокси-сервера.


Обновлено: 12.03.2015