Мониторинг подключения USB накопителей и логирование операций с файлами

Дано:
  • Пользовательские ПК, под управлением linux (suse 11, mint 16);
  • Пользователи работают удаленно с терминалами, куда пробрасываются USB накопители;
  • Пользователи не имеют на персональных компьютерах никаких прав, кроме запуска rdesktop/freerdp.

Задачи:
  • отслеживать факты подключения USB накопителей;
  • отслеживать факты записи и изменения файлов на USB накопителях.


Необходимое отступление. Я не буду обсуждать моральные аспекты подобной слежки за сотрудниками. Есть корпоративные стандарты и требования корпоративной службы безопасности с которыми ознакомлены сотрудники.

1. Отслеживаем факт подключения USB устройства


Для выполнения данной задачи я использовал свойство udev, которое позволяет выполнять скрипт при наступлении какого-либо события. Создадим правило, которое будет отвечать за подключение и отключение usb устройств:
touch /etc/udev/rules.d/usb.rules

Содержимое файла usb.rules:

ACTION=="add", SUBSYSTEM=="block", ENV{ID_BUS}=="usb|mmc|memstick|ieee1394", RUN+="/bin/bash /etc/udev/usb_on.sh %E{ID_SERIAL_SHORT} %E{ID_MODEL} %E{ID_VENDOR}"
ACTION=="remove", SUBSYSTEM=="block", ENV{ID_BUS}=="usb|mmc|memstick|ieee1394", RUN+="/bin/bash /etc/udev/usb_off.sh %E{ID_SERIAL_SHORT} %E{ID_MODEL} %E{ID_VENDOR}"

Где:
  • ACTION – отслеживаемое действие, add – подключение устройств, remove – отключение;
  • ENV – перечень отслеживаемых устройств по типу;
  • RUN – исполняемое действие. В данном случае, в зависимости от события, запускаются скрипты usb_on.sh и usb_off.sh.

Скриптам usb_on.sh и usb_off.sh udev передает следующие данные:
  • %E{ID_SERIAL_SHORT} – серийный номер USB устройства;
  • %E{ID_MODEL} – модель USB устройства;
  • %E{ID_VENDOR} – производитель USB устройства.


Скрипт usb_on.sh:
/usr/bin/curl -d "host=$(hostname)&serial=$1&name=$2&vendor=$3&file=on&sub=true" --noproxy 10.0.40.16 http://10.0.40.16/usb/input.php

Скрипт usb_off.sh:
/usr/bin/curl -d "host=$(hostname)&serial=$1&name=$2&vendor=$3&file=off&sub=true" --noproxy 10.0.40.16 http://10.0.40.16/usb/input.php

Данные скрипты выполняют единственное действие – посылают http POST запрос по адресу 10.0.40.16/usb/input.php (директива noproxy 10.0.40.16 нужна для того, что бы запрос отсылался минуя прокси-сервер). В данном POST запросе передаются следующие переменные:
  • host – имя компьютера результат выполнения команды hostname;
  • serial — серийный номер USB устройства (%E{ID_SERIAL_SHORT})
  • name — модель USB устройства (%E{ID_MODEL})
  • vendor — производитель USB устройства (%E{ID_VENDOR})
  • file – действие. on – подключение, off — отключение (file — название поля выбрано по той причине, что сюда же будет вноситься и информация о файлах, над которыми производились какие-либо действия)
  • sub=true – имитация нажатия кнопки на веб-форме.


2.Сервер сбора информации

Как видно из запускаемых скриптов usb_on.sh и usb_off.sh – вся информация передается на php скрипт, который состоит из простой веб-формы и обработчика запроса.

<form action="" method="post" enctype="multipart/form-data">
<input type="submit" name="sub" value="Сохранить">
<input type="text" name="host" maxlength=255 size=100>
<input type="text" name="serial" maxlength=255 size=100>
<input type="text" name="name" maxlength=255 size=100>
<input type="text" name="vendor" maxlength=255 size=100>
<textarea id="file" name="file"></textarea>
</form>


Обработчик делает одно – складывает принятые данные в таблицу usb БД MySQL
mysql_query("INSERT INTO `usb` 
(`id`,  `date`, `host`, `ip`, `serial`, `name`, `vendor`, `file`) VALUES (NULL,  '$time', '$_POST[host]', '$_SERVER[REMOTE_ADDR]', '$_POST[serial]', '$_POST[name]', '$_POST[vendor]', '$_POST[file]')");


Структура таблицы:

CREATE TABLE IF NOT EXISTS `usb` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date` bigint(20) NOT NULL,
  `ip` varchar(15) COLLATE utf8_bin NOT NULL,
  `host` varchar(50) COLLATE utf8_bin NOT NULL,
  `serial` varchar(100) COLLATE utf8_bin NOT NULL,
  `name` varchar(100) COLLATE utf8_bin NOT NULL,
  `vendor` varchar(100) COLLATE utf8_bin NOT NULL,
  `file` text COLLATE utf8_bin,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


3. Мониторинг операций с файлами

Для выполнения функции отслеживания операций, которые выполнялись на файлами на подключенном носителе был написан следующий скрипт:
touch /var/tmp/usb.check # обновляем время модификации файла
check=1
while [ $check ] # бесконечный цикл 
do
    if [ -f /var/tmp/usb.check ] # если существует файл
        then
    		ACCESS='' # обнуляем переменные
        	CREATE=''
		MODIFY=''
		# Далее, ищем в папке /media файлы, значения времен создания, изменения, доступа которых больше, чем только что созданным файлом /var/tmp/usb.check
        	ACCESS=`find /media -anewer /var/tmp/usb.check -type f`  
        	CREATE=`find /media -cnewer /var/tmp/usb.check -type f`
       		MODIFY=`find /media -newer /var/tmp/usb.check -type f`
        if [ ! -z "$ACCESS" ]
        then
         ACCESS="access "$ACCESS
	  # Передача информации на сервер
         /usr/bin/curl -d "host=$(hostname)&serial=$2&name=$3&vendor=$4&file=$ACCESS&sub=true" --noproxy 10.0.40.16 http://10.0.40.16/usb/input.php
        fi
        if [ ! -z "$CREATE" ]
        then
          CREATE="create "$CREATE
          /usr/bin/curl -d "host=$(hostname)&serial=$2&name=$3&vendor=$4&file=$CREATE&sub=true" --noproxy 10.0.40.16 http://10.0.40.16/usb/input.php
        fi
        if [ ! -z "$MODIFY" ]
        then
            MODIFY="modify "$MODIFY
           /usr/bin/curl -d "host=$(hostname)&serial=$2&name=$3&vendor=$4&file=$MODIFY&sub=true" --noproxy 10.0.40.16 http://10.0.40.16/usb/input.php
        fi
    fi
        touch /var/tmp/usb.check # обновляем время
        sleep 5 # пауза
done


Данный скрипт добавляется в автозапуск и работает в фоновом режиме.
Для получения выборки сохраненной информации написан php скрипт, выбирающий из БД записи за указанный период и по выбранным ПК. Пример вывод информации:
image

4. Распростренение по ПК

Т.к. ПК с сети расположено достаточно много, было решено сделать это в полу-автоматическом режиме. А именно: написан скрипт, который считывает из файла список ПК для распространения и передает на них необходимые файлы. Выполняется это с помощью утилит sshpass (для автоматического ввода пароля) и scp (копирование).
Для начала в /etc/ssh/ssh_config меняем директиву StrictHostKeyChecking:
StrictHostKeyChecking no 

Делается это для того, что бы ssh ключи автоматически добавлялись в список доверенных.

Непосредственно сам скрипт распространения:
#!/bin/bash
while read line; do
    array[$index]="$line"
        index=$(($index+1))
    done < hosts.conf
for ((a=0; a < ${#array[*]}; a++))
do
    remote=`echo ${array[$a]} | awk '{ print $1 }'`
    echo "$remote begin";
    /usr/bin/sshpass -p "root_password" /usr/bin/scp /home/user/usb_mon/usb.rules admin@$remote:/etc/udev/rules.d/usb.rules
    /usr/bin/sshpass -p "root_password" /usr/bin/scp /home/user/usb_mon/*.sh admin@$remote:/etc/udev/
    /usr/bin/sshpass -p "root_password" /usr/bin/scp /home/user/usb_mon/boot.local admin@$remote:/etc/rc.d/boot.local
    echo "$remote end";
done


Т.к. некоторые ПК могут быть выключенными, запуск скрипта выполняется с выводом всей информации в лог и последующим анализом:
./deploy.sh &> deploy.log &


PS. Планы на будущее

Что нужно доработать:
  • безопасность передачи информации при POST запросе
  • убрать логирование служебных файлов (например Thumbs.db)
  • теневое копирование файлов, над которыми были произведены какие-либо действия.

Комментарии 18

    +6
    Ох уж эти энтерпрайзные баш-скрипты…

    Например, в части вызовов curl'а параметр называется file, в части — action.

    У вас, кстати, есть явный race condition.

    Ещё SQLi в скрипте:
    /usr/bin/curl -d "host=fake&serial=1&name=2&vendor=3&file=');+DROP+DATABASE+usb;+SELECT+('qwerty&sub=true" --noproxy 10.0.40.16 http://10.0.40.16/usb/input.php
    
      0
      Например, в части вызовов curl'а параметр называется file, в части — action.

      mea culpa. Это уже при написании статьи сначала решилл убрать не совсем логичный filе, а потом не углядел везде поправить.

      У вас, кстати, есть явный race condition.

      Да. Например — пользователь успеет после записи вытащить флешку. Тогда запись не будет отслежена.

      Ещё SQLi в скрипте:

      Да, знаю. По безопасности там еще работать и работать.
      0
      ACCESS=`find /media -anewer /var/tmp/usb.check -type f` 
      

      Оно ж вернёт все файлы, к которым был доступ (access), даже обычное открытие. Если у вас есть нечто, сканирующее каталоги/файлы (к примеру, locate обновит свою БД и добавит туда файлы с флешки), то скрипту будет ой как тяжело.
      Естественно, файловая система флешки должна поддерживать access time.

      Плюс с cnewer/newer будут проблемы, т.к. пользователь может дома залить что-то на флешку уже после момента запуска скрипта. Скрипт эти файлы также посчитает.
      В общем, если есть желание сделать работающее решение, то нужно копать в сторону FUSE.
      Тогда всю работу с флешкой можно будет пропускать через собственное приложение.

      Ну и раскидайте свой открытый ключ по всем машинкам, негоже в скриптах указывать рутовый пароль :)
        0
        В современном мире relatime используется по умолчанию. Т. е. atime обновляется при модификации файла и при простом доступе 1 раз в сутки.

        man mount on centos 6.5
        relatime
        Update inode access times relative to modify or change time. Access time is only updated if the previous access time was earlier than the current modify or change time. (Similar to noatime, but doesn’t break mutt or other applications that need to know if a file has been read since the last time it was modified.)

        Since Linux 2.6.30, the kernel defaults to the behavior provided by this option (unless noatime was specified), and the strictatime option is required to obtain traditional semantics. In addition, since Linux 2.6.30, the file’s last access time is always updated if it is more than 1 day old.
          0
          Плюс с cnewer/newer будут проблемы, т.к. пользователь может дома залить что-то на флешку уже после момента запуска скрипта. Скрипт эти файлы также посчитает.

          Почему, если скрипт в самом начале своей работы обновляет время служебного файла?

          В общем, если есть желание сделать работающее решение, то нужно копать в сторону FUSE.

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

          Ну и раскидайте свой открытый ключ по всем машинкам, негоже в скриптах указывать рутовый пароль :)

          Соглашусь. В оправдание скажу, что это, на данный момент был первый случай, когда нужно было что-то массово распространять. Но не факт, что не придется в дальнейшем.
            +1
            Вы говорите о FUSE так, будто это файловая система. Вам предлагают сделать прослойку между реальной ФС через FUSE, чтобы за всеми файлами уследить можно было.

            Вообще, логичней было бы использовать inotify, как вам ниже предложили.
              0
              За совет спасибо, буду читать манулы
              0
              Почему, если скрипт в самом начале своей работы обновляет время служебного файла?

              Скрипт запускается ведь не при монтировании флешки, а при старте компа, верно?
              Комп включили вчера, флешку принесли сегодня. Или комп включили утром, а пользователь пришел на работу в обед, предварительно поработав с флешкой.
              И не исключаем ситуации, что у пользователя на домашнем компьютере время банально улетело вперёд.

              Форматировать флешки в FUSE?

              Зачем? Проксировать доступ к файлам с логированием, а при необходимости — блокировать.
              Сейчас меня забросают гнилыми помидорами, но… можно даже на PERL'е написать модуль для FUSE.
                0
                Скрипт запускается ведь не при монтировании флешки, а при старте компа, верно?
                Комп включили вчера, флешку принесли сегодня. Или комп включили утром, а пользователь пришел на работу в обед, предварительно поработав с флешкой.
                И не исключаем ситуации, что у пользователя на домашнем компьютере время банально улетело вперёд.

                Согласен. В первых редакциях скрипта я даже сам попался на такой ситуации.
            +5
            Зачем find если есть inotify?
              0
              Иди даже какой-нибудь Gamin.
                0
                find на клиентских ПК доступен «из коробки». Нет нужды что-то дополнительно устанавливать.
                Хотя мысль правильная — в планах заменить.
                  0
                  Для установки удобно использовать Ansible. А там и пристроите его к более глобальным целям.
                +3
                Если пользователь запустит скрипт который в цикле будет делать touch с выставлением atime, mtime, то с большой вероятностью вы эти файлы пропустите. Поэтому, как уже писали, единственное правильное решение использовать inotify.

                А во-вторых, правильно ли я понимаю, что если перед подключением usb накопителя «выдернуть ethernet шланг», то события у вас не залоггируются?
                  0
                  Поэтому, как уже писали, единственное правильное решение использовать inotify.

                  Да, в планах перейти.

                  А во-вторых, правильно ли я понимаю, что если перед подключением usb накопителя «выдернуть ethernet шланг», то события у вас не залоггируются?

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

                    Не ясно почему? Пользователь может все файлы из сети положить хоть куда, например в /tmp, потом отключиться от сети и записать на флешку?
                      0
                      Читаем внимательно вводные данные:
                      Пользователи не имеют на персональных компьютерах никаких прав, кроме запуска rdesktop/freerdp.
                  0
                  очень хорошая доступная идея.
                  еще чуть чуть допилить и получиться альтернатива DeviceLock
                  а вы не шаманили еще дальше?

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое