Конкурс C# Прячем бэкапы от шифровальщиков.

Статья для участия в конкурсе Конкурс 2018 года - авторская статья по любой тематике нашего форума!
Добрый день уважаемые форумчане и коллеги по цеху!
С наступающим Вас Новым годом! Уже слышится звон бокалов, в голове ) o_O

Немного о себе:
Работаю главным инженером в сегменте среднего бизнеса, в Ставропольском крае.
И пожалуй как во многих компаниях нашей необъятной Родины на плечи инженеров ложится обязанность "здорового" функционирования серверной части ИТ инфраструктуры фирмы.
Одним из аспектов "здорового функционирования" этой самой инфраструктуры, является своевременное создание бэкапов, проверка работоспособности этих бэкапов, и сохранение их в целостности о чём собственно и пойдёт речь.

Проблема:
О шифровальщиках рассказывать никому не нужно, и потерях которые несет бизнес думаю тоже все в курсе, есть масса историй в сети и ежедневной практике.
Защитные средства, будь то антивирусные решения, крутые NG-шлюзы от топовых производителей не всегда способны своевременно распознать и блокировать как заражение так и сам процесс шифрования.
В случае заражения через эксплоиты нулевого дня - аля WannaCry, практически все защитные навороты оказались бесполезны, конечно ещё нужно обновляться своевременно, но это отдельная тема.
На самом деле именно после этой эпидемии захлестнувшей даже довольно крупных представителей бизнеса с мировым именем, мы с коллегами начали думать как защитить наши бэкапы (подразумевая, что избежать заражения практически невозможно), желательно что-бы решение работало в автоматическом режиме, и не нужно было ничего переключать ручками. Инженеры, чего с нас взять, ленивые по природе своей твари :unsure:.

Решение:
Решение для нас, оказалось довольно простое, чем скорее всего нам и понравилось.
Для примера, можем взять следующий кейс: есть база данных, которая выгружается по определенному расписанию на внешнее сетевое хранилище.
Наша задача сделать так, чтобы это сетевое хранилище было не доступно в сети всё время, кроме того времени которое требуется для копирования базы. (К примеру у нас база весит 50Гб и копируется она 40-45 минут).
Т.е. автоматизировав данный процесс, мы получим 97% времени в сутки недоступный сетевой ресурс с "актуальной" базой данных (по состоянию на вчера) или другими данными, которые в случае заражения какой либо Ransom'варью она попросту не увидит и соответственно не зашифрует.

Код:
Обернуть код было решено в виндовую службу. Вот ссылка на шаблон:
Т.к. сервис будет критичный для нас, мы его будем мониторить через Zabbix (на предмет падения) и кстати через него же будем мониторить доступность порта на NAS'е. Если порт доступен, то это алярма, если нет то всё гуд.

0) Итак, секция USING
C#:
using System;
using System.ServiceProcess;
using System.Threading;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Net.Mail;

1) Метод OnStart запускает службу и необходимые для функционирования потоки.

C#:
        protected override void OnStart(string[] args)
        {
            # Тушим порт на котором у нас NAS, вдруг он включен
            DoCommand("shutdown", "interface GE 1/10");
            # Пишем в логи, шлём письма
            this.eventLog1.WriteEntry("BackupService is OnStart.");
            SendMail("Служба BCP запущенна", "Служба BCP успешно запущенна");
            WriteToLog("Служба BCP успешно запущенна" + DateTime.Now + "\n");
            # Запускаем потоки на исполнение, один из которых делает бэкапы второй следит за статусом службы
            ThreadPool.QueueUserWorkItem(new WaitCallback(ServiceBackupThread));
            ThreadPool.QueueUserWorkItem(new WaitCallback(ServiceWorkerThread));
        }

2) Далее у нас метод в котором и производится вся нагрузка заложенная в службу.
Тут создаются папки для бэкапов, открывается порт на свитче Cisco, производится копирование с замером скорости, закрывается порт.

C#:
private void ServiceBackupThread(object state)
        {
            while (!this.stopping)
            {
                int Errors = 0;
                string files = "\n";

                while (1 == 1)
                {
                    # Исполнение задания в 3 утра по времени станции
                    if (DateTime.Now.Hour == 3 && DateTime.Now.Minute == 0)
                    {
                        # Тут у нас получение текущей даты и расположения бэкапов
                        string Year = DateTime.Now.Year.ToString();
                        string Month = GetMonth(DateTime.Now.Month.ToString());
                        string BCPath = "\\\\backups\\SQL\\";
                        string BCPath2 = "\\backups\\SQL\\";
                       
                        # Создаём папки на NAS'е для бэкапов
                        try
                        {
                            Directory.CreateDirectory(BCPath2 + Year + "\\");
                            Directory.CreateDirectory(BCPath2 + Year + "\\" + Month + "\\");
                        }
                        catch(Exception ex)
                        {
                            WriteToLog("ОШИБКА: " + ex.Message + "\n");
                        }

                        WriteToLog("\n" + "НАЧАЛО РЕЗЕВНОГО КОПИРОВАНИЯ " + DateTime.Now + "\n");
                        try
                        {
                            DoCommand("no shutdown", "interface GE 1/10");
                            WriteToLog("ШАГ 1: Порт успешно открыт" + "\n");
                        }
                        catch (Exception ex)
                        {
                            WriteToLog("ОШИБКА НА ШАГЕ 1: " + ex.Message + "\n");
                            //SendMail("ОШИБКА НА ШАГЕ 1", "ОШИБКА НА ШАГЕ 1: " + ex.Message + "\n");
                            Errors++;
                        }

                        try
                        {
                            WriteToLog("ШАГ 2: Начало копированя файлов" + "\n");

                            List<FileInfo> FilesInNovember = GetFiles("D:\\backup\\SQL");
                            System.Collections.IList list = FilesInNovember;
                            for (int i = 0; i < list.Count; i++)
                            {
                                string file = list[i].ToString();
                                files = files + file + "\n";
                                string SoucePath = "D:\\backup\\SQL\\" + file;
                                string DestPath = BCPath + Year + "\\" + Month + "\\" + file;
                                WriteToLog("Copy file from " + SoucePath + " to " + DestPath + "\n");
                                MoveTime(SoucePath, DestPath);
                            }
                            WriteToLog("ШАГ 2: Файлы успешно скопированы" + "\n" + files);
                        }
                        catch (Exception ex)
                        {
                            WriteToLog("ОШИБКА НА ШАГЕ 2: " + ex.Message + "\n" + files);
                            //SendMail("ОШИБКА НА ШАГЕ 2", "ОШИБКА НА ШАГЕ 2: " + ex.Message + "\n");
                            Errors++;
                        }

                        try
                        {
                            DoCommand("shutdown", "interface GE 1/10");
                            WriteToLog("ШАГ 3: Порт успешно закрыт" + "\n");
                        }
                        catch (Exception ex)
                        {
                            WriteToLog("ОШИБКА НА ШАГЕ 3: " + ex.Message + "\n");
                            //SendMail("ОШИБКА НА ШАГЕ 3", "ОШИБКА НА ШАГЕ 3: " + ex.Message + "\n");
                            Errors++;
                        }

                        if (Errors == 0)
                        {
                            WriteToLog("РЕЗЕВНОЕ КОПИРОВАНИЕ ВЫПОЛНЕНО БЕЗ ОШИБОК " + DateTime.Now + "\n");
                            SendMail("РК ВЫПОЛНЕНО", "РЕЗЕРВНОЕ КОПИРОВАНИЕ ФАЙЛОВ " + files + " ВЫПОЛНЕНО БЕЗ ОШИБОК \n");
                        }
                        else
                        {
                            WriteToLog("РЕЗЕВНОЕ КОПИРОВАНИЕ ВЫПОЛНЕНО С " + Errors + " ОШИБКАМИ " + DateTime.Now + "\n");
                            SendMail("РК НЕ ВЫПОЛНЕНО", "РЕЗЕРВНОЕ КОПИРОВАНИЕ ВЫПОЛНЕНО С " + Errors + " ОШИБКАМИ \n" + files);
                            Errors = 0;
                        }
                        files = "\n";
                    }
                    Thread.Sleep(50000);
                }
            }

            this.stoppedEvent.Set();
        }

3) Отправка оповещения на почту
C#:
        public static void SendMail(string Body, string Text)
        {
            try
            {
                MailMessage mail = new MailMessage("backups@company.ru", "youtname@company.ru");
                SmtpClient client = new SmtpClient();
                client.Port = 25;
                client.DeliveryMethod = SmtpDeliveryMethod.Network;
                client.UseDefaultCredentials = false;
                client.Host = "mailserv.company.ru";
                mail.Subject = Body;
                mail.Body = Text;
                client.Send(mail);
            }
            catch (Exception ex)
            {
                WriteToLog("ОШИБКА ОТПРАВКИ ПИСЬМА: " + "\n" + ex + "\n");
            }
        }

4) Следующие два метода получают файлы в заданной директории которые были создан сегодня. Запись в лог файл.
C#:
        static private List<FileInfo> GetFiles(string directoryPath)
        {
            DirectoryInfo dir = new DirectoryInfo(directoryPath);
            FileInfo[] theFiles = dir.GetFiles("*", SearchOption.TopDirectoryOnly);
            return theFiles.Where(fl => fl.CreationTime.Date == DateTime.Today).ToList();
        }

        public static void WriteToLog(string Text)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(Text);
            File.AppendAllText("C:\\servicepath\\LogBackup.txt", sb.ToString());
            sb.Clear();
        }

5) Метод подключение к свитчу, и выполнение действий с портом.
C#:
public static void DoCommand(string Command, string Port)
        {
            TelnetConnection tc = new TelnetConnection("192.168.0.200", 23);

            string s = tc.Login("cisco", "SuperSecretPASS", 1000);
            Console.Write(s);

            string prompt = s.TrimEnd();

            prompt = "";

            System.Threading.Thread.Sleep(1000);

            prompt = "conf";
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            System.Threading.Thread.Sleep(1000);

            prompt = Port;
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            System.Threading.Thread.Sleep(1000);

            prompt = Command;
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            System.Threading.Thread.Sleep(1000);

            prompt = "end";
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            System.Threading.Thread.Sleep(1000);

            prompt = "write";
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            System.Threading.Thread.Sleep(1000);

            prompt = "Y";
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            System.Threading.Thread.Sleep(1000);

            prompt = "exit";
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            System.Threading.Thread.Sleep(10000);

            prompt = "";
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            System.Threading.Thread.Sleep(10000);

            prompt = "exit";
            tc.WriteLine(prompt);
            Console.Write(tc.Read());

            Console.WriteLine("***DISCONNECTED" + "\n");
            //WriteToLog("Command '" + Command + "' was executed for port: " + Port + "\n");
        }

6) Методы копирования и замера скорости копирования. Взяты где-то на просторах сети, вроде codeproject, ссылку не смог найти.
C#:
public static void MoveTime(string source, string destination)
        {
            DateTime start_time = DateTime.Now;
            FMove(source, destination);
            long size = new FileInfo(destination).Length;
            int milliseconds = 1 + (int)((DateTime.Now - start_time).TotalMilliseconds);
            long tsize = size * 3600000 / milliseconds;
            tsize = tsize / (int)Math.Pow(2, 30);
            Console.WriteLine("Speed of copy " + tsize + "GB/hour" + "\n");
            WriteToLog("Speed of copy " + tsize + "GB/hour" + "\n");
        }

        static void FMove(string source, string destination)
        {
            int array_length = (int)Math.Pow(2, 19);
            byte[] dataArray = new byte[array_length];
            using (FileStream fsread = new FileStream
            (source, FileMode.Open, FileAccess.Read, FileShare.None, array_length))
            {
                using (BinaryReader bwread = new BinaryReader(fsread))
                {
                    using (FileStream fswrite = new FileStream
                    (destination, FileMode.Create, FileAccess.Write, FileShare.None, array_length))
                    {
                        using (BinaryWriter bwwrite = new BinaryWriter(fswrite))
                        {
                            for (; ; )
                            {
                                int read = bwread.Read(dataArray, 0, array_length);
                                if (0 == read)
                                    break;
                                bwwrite.Write(dataArray, 0, read);
                            }
                        }
                    }
                }
            }
        }

7) Метод для присвоения имени папкам в зависимости от месяца.
C#:
public static string GetMonth(string Month)
        {
            if (Month == "1")
                Month = "01 JAN";
            if (Month == "2")
                Month = "02 FEB";
            if (Month == "3")
                Month = "03 MRT";
            if (Month == "4")
                Month = "04 APR";
            if (Month == "5")
                Month = "05 MAY";
            if (Month == "6")
                Month = "06 JUNE";
            if (Month == "7")
                Month = "07 JULE";
            if (Month == "8")
                Month = "08 AUG";
            if (Month == "9")
                Month = "09 SEP";
            if (Month == "10")
                Month = "10 OKT";
            if (Month == "11")
                Month = "11 NOV";
            if (Month == "12")
                Month = "12 DEC";
            return (Month);
        }

Ну вот вроде и всё. Далее всё это дело компилируем, добавляем исполняемый файл в службы через PowerShell например:

C#:
if (Get-Service MoveBakups -ErrorAction SilentlyContinue) {
  $service = Get-WmiObject -Class Win32_Service -Filter "name='MoveBakups'"
  $service.StopService()
  Start-Sleep -s 1
  $service.delete()
}

$workdir = Split-Path $MyInvocation.MyCommand.Path

New-Service -name MoveBakups `
  -displayName MoveBakups `
  -binaryPathName "`"C:\servicepasth\MoveBakups.exe`""

Код конечно не изящный, но тут важна сама концепция, вы всегда можете допилить так как вам нужно.
Опционально добавляем сервер и NAS в систему мониторинга. Держим руку на пульсе.
Ещё раз всех с наступающим, желаю что-бы у вас всегда были бэкапы и не было факапов! ;)
 

pr0phet

Platinum
02.04.2018
358
496
BIT
12
В случае заражения через эксплоиты нулевого дня - аля WannaCry
Это был сильно не 0-day. Практически любой коммерческий IPS на момент эпидемии имел сигнатуру. Даже суриката/снорт бы выручили, не говоря уже о "крутых UTM решениях" (про апдейты молчим - кому они нужны вообще :) )
А в целом задумка интересная!
 

puff

One Level
03.11.2016
7
4
BIT
0
Мне кажется можно сделать все проще, зачем тратить деньги на NAS? Поднимаем фтп-сервер на дебиане или убунте и туда льем бэкапы)
 

chuvak75squad

One Level
02.10.2018
3
11
BIT
0
Это был сильно не 0-day. Практически любой коммерческий IPS на момент эпидемии имел сигнатуру. Даже суриката/снорт бы выручили, не говоря уже о "крутых UTM решениях" (про апдейты молчим - кому они нужны вообще :) )
А в целом задумка интересная!
Согласен с 0-day погорячился :)) Но никто не может гарантировать, что в каком то ближайшем будущем не произойдет эпидемия перед которой все защиты падут, у меня есть даже товарищ который бы очень хотел на это посмотреть, он так надеялся на WanaCry, но к счастью все обошлось на этот раз ))

Мне кажется можно сделать все проще, зачем тратить деньги на NAS? Поднимаем фтп-сервер на дебиане или убунте и туда льем бэкапы)
NAS - это не обязательно дорогая железка с RAID контроллером за дорого. К примеру у нас я юзаю FreeNAS (FreeBSD NAS) на обычном системнике даже не помню что там за железо, но этот системник долго стоял без дела, а теперь трудится на благо, там тебе хоть SMB (самое простое, мы же ленивые) хоть FTP,SFTP, можно и по SSH туда лить бэкапы, в общем считаю что транспорт не особо важен (т.к. большую часть времени система не в сети), главное что бы работало стабильно.

Добавлю для тех кому это актуально (чужие статьи к нам нет смысла копировать) по этому даю ссылку:
может и автор chuvak75squad возьмет от FSRM в Windows Server что то к своему коду ;-)

pr0phet можете указать ссылкой на решения о которых Вы писали (правда для многих актуально).
Спасибо, полезно ))
 
  • Нравится
Реакции: pr0phet

pr0phet

Platinum
02.04.2018
358
496
BIT
12
Согласен с 0-day погорячился :)) Но никто не может гарантировать, что в каком то ближайшем будущем не произойдет эпидемия перед которой все защиты падут, у меня есть даже товарищ который бы очень хотел на это посмотреть, он так надеялся на WanaCry, но к счастью все обошлось на этот раз ))
В таких массовых масштабах как ванакрай вряд ли. 0-day оставят для таргета. А вот если, допустим, конкуренты решили целенаправленно убить Вашу инфраструктуру (бизнес) со всеми бэкапами, и игра стоит свеч и цен на 0-day - то Ваш метод очень даже поможет. По крайней мере неплохо усложнит атаку.

Добавлю для тех кому это актуально (чужие статьи к нам нет смысла копировать) по этому даю ссылку:
может и автор chuvak75squad возьмет от FSRM в Windows Server что то к своему коду ;-)

pr0phet можете указать ссылкой на решения о которых Вы писали (правда для многих актуально).
Без проблем.

Бесплатные:


(есть коммерческая версия)


Платные программно-аппаратные решения:




 
Последнее редактирование:

alexoron

Green Team
20.05.2018
11
1
BIT
0
Плохое решение.
Если бекап будет доступен системе хотя-бы несколько секунд - большая вероятность порчи шифратором.
Самый безопасный вариант можно подсмотреть у Оракла - база данных и бекапы располагаются в неформатирванной области жесткого диска.
Туда не имеет доступа ни одна программа, пользователь. Система даже не знает что там что-то есть, соответственно и шифраторы.
Так что пока скопируете свой 50гб файл, шифратор успеет зашифровать все копии.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!