Cканируем порты.

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

nmap -P0 192.168.9.240

Посмотреть кто слушает определенный порт.

netstat -anp | grep LISTEN | grep 620

Задача провести сканирование открытых портов на хостах, взятых из списка в файле hostlist.

Далее провести анализ и сравнить с предыдущим сканированием, выяснить изменения и составить отчет.

Разобьем задачу на 3 файла.

scan.sh - будет сканировать порты (от 1 до 1024), принимая на stdin имя хоста;

compare.sh - будет сравнивать результаты с предыдущим сканированием;

report.sh - будет запускать 1 и 2 и формировать файл отчета.

Создание цикла по входящим хостам.

Файл scan.sh

while read HOSTNAME
do
    echo $HOSTNAME
done

read - читает строку из stdin.

Передаем данные из файла hostlist

Файл report.sh

./scan.sh < hostlist

Формируем название файла с результатами сканирования.

scan.sh

printf -v TODAY 'scan_%(%F)T' -1
echo $TODAY

%()T - спецификатор функции printf обозначающий дату-время.

%F - спецификатор системной фукнции strftime описывающий формат “год-месяц-день”

-1 - означает “сейчас”

Изменим scan.sh добавим функцию и ее вызовем.

printf -v TODAY 'scan_%(%F)T' -1

function scan() {
   host=$1
   printf '%s' "$host"
}


while read HOSTNAME
do
    scan $HOSTNAME
done

В bash явно не указываются параметры функции, они забираются через $1,2 и т.д.

printf - не выводит символ переноса строки.

Делаем цикл по портам.

function scan() {
   host=$1
   printf '%s' "$host"
   for ((port=1;port<1024;port++)){
     echo $port
   }
}

Или так

for ((port=1;port<1024;port++)) do echo $port done

Доработаем функцию попыткой создать подключение на сокет

function scan() {
   host=$1
   printf '%s' "$host"
   for ((port=1;port<1024;port++))
   do
     echo >/dev/null 2>&1 < /dev/tcp/${host}/${port}
     if (($? == 0)) ; then printf ' %d' "${port}" ; fi 
   done
   echo
}

cgi

echo без аргументов выведет stdout символ переноса строки и мы его игнорируем переводя в /dev/null.

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

2>&1 < /dev/tcp/${host}/${port} - тут мы переправляем содержимое и ошибки из предполагаемого файлового дескриптора сокет-соединения, причем данные даже не будут считаны т.к. команде echo это не нужно. Просто проверит есть ли файл (дескриптор соединения).

Переправление результатов сканирования в файл.

while read HOSTNAME
do
    scan $HOSTNAME
done > $TODAY

Можно было бы переправить внутри цикла, но так удобней т.к. нам не нужно заботиться об обрезании файла в цикле перенаправлением >. Даже если бы мы использовали >> в цикле, то нужно было бы перед ним обрезать файл.

Сравнение с предыдущим сканированием.

Использование

./compare.sh <file1> <file2>

Определяем функцию поиска порта

function NotInList ()
{
    for port in "$@"
    do
        if [[ $port == $LOOKFOR ]]
        then
            return 1
        fi
    done
    return 0
}

$@ - разворачивает параметры функции по одному отдельными значениями

Напоминаем что ненулевое значение считается ложным.

Читаем содержимое 2 файлов по файловым дескрипторам.

while true
do
    read aline <&4 || break # EOF
    read bline <&5 || break # EOF, для симметрии
    echo $aline
    echo $bline

done 4< $1 5< $2

4< $1 5< $2 - тут мы создаем файловые дескрипторы, переправляя в них содержимое файлов первого и второго параметра

<&4 - & показывает что мы используем дескриптор файла а не файл с именем 4

|| - логическое OR оно выполнить breack только в случае если read вернет ошибку, что происходит на второй итерации цикла, когда достигается конец файла.

Проверим равенство двух строк, и если они равны переходим на новую итерацию цикла.

[[ $aline == $bline ]] && continue;

Извлечение хоста и портов.

HOSTA=${aline%% *}
PORTSA=( ${aline#* } )


HOSTB=${bline%% *}
PORTSB=( ${bline#* } )

${string#substring} - удаляет самое короткое совпадение подстроки от начала.

${string##substring} - удаляет самое длинное совпадение подстроки от начала.

% - работает с конца строки

HOSTA=${aline%% *} - удалит все символы с конца строки до первого пробела

PORTSA=( ${aline#* } ) - создаст массив портов удаляя все символы от начала до первого пробела

Далее мы делаем две проверки на порты, которые были открыты и закрыты.

for porta in ${PORTSA[@]}
do
    LOOKFOR=$porta 
    NotInList ${PORTSB[@]} && echo " closed: $porta"
done
for portb in ${PORTSB[@]}
do
    LOOKFOR=$portb 
    NotInList ${PORTSA[@]} && echo " new: $portb"
done

NotInList ${PORTSB[@]} && echo ” closed: $porta” - выводит только в случае успешного выполнения выражения слева от &&

Формирование отчета

Файл report.sh

./scan.sh < hostlist
FILELIST=$(ls scan_* | tail -2)
FILES=( $FILELIST ) 
./compare.sh ${FILES[0]} ${FILES[1]}

$(ls scan_* | tail -2) - выбираем два последних отчета, ls сортирует файлы в порядке их создания.

Чтобы облегчить разделение на две части, поместим имена в массив.