Последний раз с помощью нескольких строк кода PowerShell я запустил совершенно новую категорию программного обеспечения — анализ доступа к файлам. Мои 15 минут славы почти закончились, но я смог отметить, что PowerShell предоставляет практические возможности для мониторинга событий доступа к файлам. В этой заметке я завершу работу над инструментом анализа доступа к файлам и перейду к классификации данных PowerShell.
Аналитика на основе событий
Хочу напомнить, что в своем скрипте анализа для просмотра событий доступа к файлам в определенной папке я использовал командлет Register-WmiEvent. Также я создал мифическое эталонное значение для интенсивности событий, чтобы сравнивать с ним текущие данные. (Для тех, кто основательно этим интересуется, есть целая область средств измерения таких показателей — посещения веб-сайтов, звонки в центр телефонного обслуживания, поток людей у кофемашин, и первым это начал изучать вот этот парень.)
Когда число доступов к файлам превышает стандартные показатели, я запускал программно создаваемое событие, которое вызывается другой частью кода и отображает информационную панель анализа доступа к файлам.
Этот запуск осуществляется командлетом New-Event, который позволяет отправлять событие вместе с другой информацией получателю. Для чтения данных о событии есть командлет WMI-Event. В роли получающей стороны может быть даже другой скрипт, поскольку оба командлета событий используют одинаковый идентификатор SourceIdentifier — в нашем случае это Bursts.
Это все основные идеи в отношении операционной системы: фактически, PowerShell обеспечивает простую систему передачи сообщений. И довольно успешно, все-таки мы используем просто подающий сигналы командный язык.
Как бы то ни было, далее представлен удивительный полный код.
1. $cur = Get-Date
2. $Global:Count=0
3. $Global:baseline = @{"Monday" = @(3,8,5); "Tuesday" = @(4,10,7);"Wednesday" = @(4,4,4);"Thursday" = @(7,12,4); "Friday" = @(5,4,6); "Saturday"=@(2,1,1); "Sunday"= @(2,4,2)}
4. $Global:cnts = @(0,0,0)
5. $Global:burst = $false
6. $Global:evarray = New-Object System.Collections.ArrayList
7.
8. $action = {
9. $Global:Count++
10. $d=(Get-Date).DayofWeek
11. $i= [math]::floor((Get-Date).Hour/8)
12.
13. $Global:cnts[$i]++
14.
15. #event auditing!
16.
17. $rawtime = $EventArgs.NewEvent.TargetInstance.LastAccessed.Substring(0,12)
18. $filename = $EventArgs.NewEvent.TargetInstance.Name
19. $etime= [datetime]::ParseExact($rawtime,"yyyyMMddHHmm",$null)
20.
21. $msg="$($etime)): Access of file $($filename)"
22. $msg|Out-File C:\Users\bob\Documents\events.log -Append
23.
24.
25. $Global:evarray.Add(@($filename,$etime))
26. if(!$Global:burst) {
27. $Global:start=$etime
28. $Global:burst=$true
29. }
30. else {
31. if($Global:start.AddMinutes(15) -gt $etime ) {
32. $Global:Count++
33. #File behavior analytics
34. $sfactor=2*[math]::sqrt( $Global:baseline["$($d)"][$i])
35.
36. if ($Global:Count -gt $Global:baseline["$($d)"][$i] + 2*$sfactor) {
37.
38.
39. "$($etime): Burst of $($Global:Count) accesses"| Out-File C:\Users\bob\Documents\events.log -Append
40. $Global:Count=0
41. $Global:burst =$false
42. New-Event -SourceIdentifier Bursts -MessageData "We're in Trouble" -EventArguments $Global:evarray
43. $Global:evarray= [System.Collections.ArrayList] @();
44. }
45. }
46. else { $Global:burst =$false; $Global:Count=0; $Global:evarray= [System.Collections.ArrayList] @();}
47. }
48. }
49.
50. Register-WmiEvent -Query "SELECT * FROM __InstanceModificationEvent WITHIN 5 WHERE TargetInstance ISA 'CIM_DataFile' and TargetInstance.Path = '\\Users\\bob\\' and targetInstance.Drive = 'C:' and (targetInstance.Extension = 'txt' or targetInstance.Extension = 'doc' or targetInstance.Extension = 'rtf') and targetInstance.LastAccessed > '$($cur)' " -sourceIdentifier "Accessor" -Action $action
51.
52.
53. #Dashboard
54. While ($true) {
55. $args=Wait-Event -SourceIdentifier Bursts # wait on Burst event
56. Remove-Event -SourceIdentifier Bursts #remove event
57.
58. $outarray=@()
59. foreach ($result in $args.SourceArgs) {
60. $obj = New-Object System.Object
61. $obj | Add-Member -type NoteProperty -Name File -Value $result[0]
62. $obj | Add-Member -type NoteProperty -Name Time -Value $result[1]
63. $outarray += $obj
64. }
65.
66.
67. $outarray|Out-GridView -Title "FAA Dashboard: Burst Data"
68. }
Пожалуйста, не бейте свой ноутбук во время просмотра.
Я понимаю, что продолжаю показывать отдельные представления таблиц и есть более подходящие способы обработки графики. С помощью PowerShell у вас есть доступ ко всей платформе .NET Framework, поэтому можно создавать объекты — списки, диаграммы и т. д., — получать к ним доступ, а затем обновлять при необходимости. Пока я оставлю это для вас в качестве домашнего задания.
Классификация играет очень важную роль в обеспечении безопасности данных
Давайте отложим мониторинг событий файлов и рассмотрим тему классификации данных в PowerShell.
В компании Varonis мы по объективным причинам используем принцип «осведомленности о ваших данных». Чтобы разработать полезную программу защиты данных, одним из первых этапов должно быть изучение того, где расположены критически важные или конфиденциальные данные — номера кредитных карт, адреса заказчиков, конфиденциальные юридические документы, проприетарный код.
Целью, конечно, является защита ценных цифровых данных компании, но сначала их следует определить их состав. Кстати, это не просто хорошая идея, множество законов и нормативов по защите данных (например, HIPAA), а также промышленные стандарты (PCI DSS) требуют идентификации активов при реальной оценке рисков.
У PowerShell большой потенциал для использования в приложениях классификации данных. Может ли PS непосредственно получать доступ к файлам и считывать их? Да. Может ли он выполнять сравнение по образцу для текста? Да. Может ли он делать это эффективно в больших масштабах? Да.
Нет, скрипт классификации PowerShell, который я получил в конечном итоге, не заменит Varonis Data Classification Framework. Но для сценария, который я подразумевал, — ИТ-администратор, которому требуется следить за папкой с конфиденциальными данными, — мой пробный скрипт PowerShell показывает не просто удовлетворительный результат, его можно оценить, скажем, на 4 с плюсом!
WQL и CIM_DataFile
Давайте вернемся к языку WQL, который я упоминал в первой заметке о мониторинге событий.
Так же, как я использовал этот язык запросов для поиска событий файлов в каталоге, я могу изменить этот скрипт, чтобы получить список всех файлов из заданного каталога. Как и раньше, я использую класс CIM_DataFile, но в этот раз мой запрос затрагивает саму папку, а не связанные с ней события.
1. $Get-WmiObject -Query "SELECT * From CIM_DataFile where Path = '\\Users\\bob\\' and Drive = 'C:' and (Extension = 'txt' or Extension = 'doc' or Extension = 'rtf')"
Превосходно! Эта строка кода будет выдавать массив с именами путей к файлам.
Для считывания содержимого каждого из файлов в переменную в PowerShell предоставляется командлет Get-Content. Спасибо Microsoft.
Мне нужна еще одна составляющая для моего скрипта — сопоставление с образцом. Неудивительно, что в PowerShell есть стандартный модуль выражений. Для моих целей это является избыточным, но я однозначно сэкономлю время.
При разговоре со специалистами по безопасности они часто говорили мне, что компаниям следует явно отмечать документы или презентации с производственной или конфиденциальной информацией соответствующим образом — например, словом «секретный» или «конфиденциальный». Это является рекомендацией и очевидно помогает в процессе классификации данных.
В моем скрипте я создал хеш-таблицу PowerShell с возможными маркирующими текстами и соответствующим регулярным выражением для сопоставления. Для документов, которые явным образом не отмечены таким способом, я также добавил специальные имена проектов — в моем случае, snowflake — которые также проверяются. А для отбрасывания я добавил регулярное выражение для номеров социального страхования.
Блок кода, который я использовал для чтения и сопоставления с образцом, приведен ниже. Имя файла для считывания и проверки передается как параметр.
1. $Action = {
2.
3. Param (
4.
5. [string] $Name
6.
7. )
8.
9. $classify =@{"Top Secret"=[regex]'[tT]op [sS]ecret'; "Sensitive"=[regex]'([Cc]onfidential)|([sS]nowflake)'; "Numbers"=[regex]'[0-9]{3}-[0-9]{2}-[0-9]{3}' }
10.
11.
12. $data = Get-Content $Name
13.
14. $cnts= @()
15.
16. foreach ($key in $classify.Keys) {
17.
18. $m=$classify[$key].matches($data)
19.
20. if($m.Count -gt 0) {
21.
22. $cnts+= @($key,$m.Count)
23. }
24. }
25.
26. $cnts
27. }
Великолепная поддержка многопоточности
Я могу упростить свой проект, взяв представленный выше код, добавив связующие элементы и запустив результат через командлет Out-GridView.
Есть мысль, которую я хочу донести. Даже в одной папке в корпоративной файловой системе могут быть сотни и даже тысячи файлов.
Вы на самом деле хотите ждать, пока скрипт последовательно прочитает каждый файл?
Конечно, нет!
Приложения, где осуществляется ввод-вывод для большого количества файлов, как в случае классификации, хорошо подходят для многопоточной обработки — можно параллельно запустить множество действий с файлами, тем самым значительно сократив время получения результата.
PowerShell обладает практически пригодной (хотя и неуклюжей) системой фоновой обработки, известной как Jobs. Но есть также поразительная и изящная функция с поддержкой многопоточности — Runspaces.
После некоторых манипуляций и заимствования кода у нескольких пионеров в использовании Runspaces я был действительно впечатлен.
Runspaces позволяет обрабатывать всю запутанную механику синхронизации и параллелизма. Это не то, что можно быстро понять, и даже блогеры Scripting Guys, сотрудничающие с Microsoft, еще только разбираются в этой многопоточной системе.
Как бы то ни было, я смело продолжил двигаться вперед и использовал Runspaces для параллельного чтения файлов. Далее представлена часть кода для запуска потоков: для каждого файла в каталоге я создал поток, который запускает указанный выше блок скрипта, возвращающий совпадающие шаблоны в массив.
1. $RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, 5)
2.
3. $RunspacePool.Open()
4.
5. $Tasks = @()
6.
7.
8. foreach ($item in $list) {
9.
10. $Task = [powershell]::Create().AddScript($Action).AddArgument($item.Name)
11.
12. $Task.RunspacePool = $RunspacePool
13.
14. $status= $Task.BeginInvoke()
15.
16. $Tasks += @($status,$Task,$item.Name)
17. }
Давайте сделаем глубокий вдох — мы рассмотрели много материала.
В следующей заметке я представлю полный скрипт и обсужу некоторые (неприятные) подробности. Тем временем, после присвоения некоторым файлам текста отметки, я создал следующий вывод с помощью Out-GridView.
Классификация содержимого практически даром!
В то же время следует подумать о том, как соединить друг с другом два скрипта: скрипт мониторинга активности файлов и скрипт классификации, который представлен в этой заметке.
Все-таки скрипт классификации должен передавать данные о том, что стоит отслеживать в скрипте активности файлов, а скрипт активности в теории может сообщать скрипту классификации, когда создается новый файл, чтобы его можно было классифицировать — иными словами, реализовать систему добавления данных.
Звучит так, как будто я предлагаю, не побоюсь этого слова, платформу контроля безопасности на основе PowerShell. Мы начнем работу над осуществлением этого в следующий раз.