В предыдущей заметке этой серии я предложил возможность объединения моих отдельных скриптов — один для обработки событий, другой для классификации — в одну систему. Не замахнуться ли на платформу безопасности на основе одного кода PowerShell?
Проработав некоторые детали, в основном относящиеся к зубодробительным событиям PowerShell, я смог заявить о своей победе и зарегистрировал патент на платформу безопасности на базе скриптов — SSP (Security Scripting Platform ).
Соединенные штаты PowerShell
Пока я получал незабываемый опыт работы с PowerShell, я понял, что некоторые из вас могут не вспомнить мои результаты работы со скриптами.
Давайте вспомним их вместе.
В первой заметке я представил удивительную строку кода PowerShell, которая следит за событиями доступа к файлам и запускает блок скрипта, аналогичный PS, — то есть код скрипта, который выполняется в собственной области памяти.
1. 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 = 'doc' or targetInstance.Extension = 'txt)' and targetInstance.LastAccessed > '$($cur)' " -sourceIdentifier "Accessor" -Action $action
Добавив некоторые элементы скрипта, я получил код, который назвал приложением анализа доступа к файлам. Фактически оно подсчитывает события доступа и отображает некоторые основные статистические данные, а также определяет всплески доступа, которые могут указывать на попытки взлома.
Это упрощенная версия технологии анализа поведения пользователей, которая широко применяется нами здесь в компании Varonis.
Пока все идет хорошо.
Затем в третьей заметке я показал, как с помощью PowerShell можно относительно просто просканировать и классифицировать файлы в папке. Поскольку это действие связано с интенсивным использованием диска, для ускорения классификации имеет смысл использовать функцию многозадачности PowerShell, известную как Runspaces.
В существующих решениях по обработке событий файлов и классификации данных, скажем Data Classification Framework компании Varonis, используется более оптимизированный подход к распределению файлов по категориям путем передачи операций с файлами в модуль классификации.
Почему?
Потому что им не требуется повторно классифицировать содержимое файлов заново: рассматриваются только файлы, которые изменены. Поэтому мой скрипт-классификатор сильно выиграет, если будет получать некоторую информацию о событиях изменения файлов.
Этот подход я реализовал с помощью платформы безопасности с использованием скриптов.
Собственные агенты Varonis, которые перехватывают события файлов Linux или Windows, — это узко специализированный код низкого уровня. Для работы такого типа требуется код, который будет коротким, простым и полностью сфокусированным на сборе сведений о событиях и быстрой передаче этих данных в другие приложения, которые уже будут выполнять обработку более высокого уровня.
Поэтому я взял свой исходный скрипт обработки событий, оптимизировал его и удалил весь код для отображения статистических данных. Затем я переработал классификатор, чтобы он проверял только измененные файлы.
В основном это классическая комбинация внутреннего модуля и внешнего интерфейса.
Вопрос состоит в том, как соединить два скрипта: как сообщить классификатору о произошедшей операции с файлом?
Сообщения и события
После того, как я провел несколько долгих дней за изучением форумов разработчиков, я наконец наткнулся на функцию PowerShell под именем Register-EngineEvent.
Что представляет собой этот командлет PowerShell?
Мне представляется, что это способ передачи сообщений с помощью именованного события, которое можно совместно использовать в двух скриптах. Он работает несколько иначе, чем традиционные системные сообщения и очереди, поскольку получаемое сообщение асинхронно запускает блок скрипта PowerShell. Далее это станет более понятным.
Дело в том, что у register-EngineEvent есть два варианта работы. С параметром -forward этот командлет работает как издатель событий. Без параметра -forward он выполняет роль получателя.
Понятно?
Я использовал событие с именем Delta — которое технически выполняет роль значения идентификатора SourceIdentifer — для координации работы моего скрипта обработки событий, который выдает сообщения о событиях, и моего скрипта-классификатора, который получает эти сообщения.
В первом из этих двух скриптов, отрывок из которого представлен далее, я показываю, как я регистрировал публичное имя события Delta с помощью строки -Register-EngineEvent -forward, а затем ожидал внутренних событий доступа к файлам. Когда такое событие происходило, я отправлял сообщение о внутреннем событии файла — говоря на языке PowerShell, пересылал его — соответствующему командлету Register-EngineEvent в скрипте-классификаторе во втором отрывке кода.
1. Register-EngineEvent -SourceIdentifier Delta -Forward
2. While ($true) {
3. $args=Wait-Event -SourceIdentifier Access # wait on internal file event
4. Remove-Event -SourceIdentifier Access
5. if ($args.MessageData -eq "Access") {
6. #do some plain access processing
7. New-Event -SourceIdentifier Delta -EventArguments $args.SourceArgs -MessageData $args.MessageData #send event to classifier via forwarding
8. }
9. elseif ($args.MessageData -eq "Burst") {
10. #do some burst processing
11. New-Event -SourceIdentifier Delta -EventArguments $args.SourceArgs -MessageData $args.MessageData #send event to classifier via forwarding
12. }
13. }
На стороне получателя я не использовал параметр -forward и вместо этого переходил в блок скрипта PowerShell, который асинхронно обрабатывал событие. Результат вы можете увидеть ниже.
1. Register-EngineEvent -SourceIdentifier Delta -Action {
2.
3. Remove-Event -SourceIdentifier Delta
4. if($event.MessageData -eq "Access") {
5. $filename = $args[0] #got file!
6. Lock-Object $deltafile.SyncRoot{ $deltafile[$filename]=1} #lock&load
7. }
8. elseif ($event.Messagedata -eq "Burst") {
9. #do something
10. }
11.
12. }
Запутались? А я говорил недавно, что обработка событий файлов — это непросто, и что мои учебные скрипты не справятся с обработкой реальных производственных нагрузок?
Путаница возникает, поскольку командлеты New-Event и Wait-Event для внутреннего обмена сообщениями о событиях отличаются от функций внешнего обмена сообщениями о событиях, предоставленных в Register-EngineEvent.
Еще больше путаницы
Полный скрипт классификации представлен далее. Я подробнее расскажу о нем в следующей и последней заметке из этой серии. А пока посмотрите на этот скрипт и обратите внимание на обработку событий и многозадачность.
1. Import-Module -Name .\pslock.psm1 -Verbose
2. function updatecnts {
3. Param (
4. [parameter(position=1)]
5. $match,
6. [parameter(position=2)]
7. $obj
8. )
9.
10. for($j=0; $j -lt $match.Count;$j=$j+2) {
11. switch -wildcard ($match[$j]) {
12. 'Top*' { $obj| Add-Member -Force -type NoteProperty -Name Secret -Value $match[$j+1] }
13. 'Sens*' { $obj| Add-Member -Force -type NoteProperty -Name Sensitive -Value $match[$j+1] }
14. 'Numb*' { $obj| Add-Member -Force -type NoteProperty -Name Numbers -Value $match[$j+1] }
15. }
16.
17. }
18.
19. return $obj
20. }
21.
22. $scan = {
23. $name=$args[0]
24. function scan {
25. Param (
26. [parameter(position=1)]
27. [string] $Name
28. )
29. $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}' }
30.
31. $data = Get-Content $Name
32.
33. $cnts= @()
34.
35. if($data.Length -eq 0) { return $cnts}
36.
37. foreach ($key in $classify.Keys) {
38.
39. $m=$classify[$key].matches($data)
40.
41. if($m.Count -gt 0) {
42. $cnts+= @($key,$m.Count)
43. }
44. }
45. $cnts
46. }
47. scan $name
48. }
49.
50.
51. $outarray = @() #where I keep classification stats
52. $deltafile = [hashtable]::Synchronized(@{}) #hold file events for master loop
53.
54. $list=Get-WmiObject -Query "SELECT * From CIM_DataFile where Path = '\\Users\\bob\\' and Drive = 'C:' and (Extension = 'txt' or Extension = 'doc' or Extension = 'rtf')"
55.
56.
57. #long list --let's multithread
58.
59. #runspace
60. $RunspacePool = [RunspaceFactory]::CreateRunspacePool(1,5)
61. $RunspacePool.Open()
62. $Tasks = @()
63.
64.
65. foreach ($item in $list) {
66.
67. $Task = [powershell]::Create().AddScript($scan).AddArgument($item.Name)
68. $Task.RunspacePool = $RunspacePool
69.
70. $status= $Task.BeginInvoke()
71. $Tasks += @($status,$Task,$item.Name)
72. }
73.
74.
75. Register-EngineEvent -SourceIdentifier Delta -Action {
76.
77. Remove-Event -SourceIdentifier Delta
78. if($event.MessageData -eq "Access") {
79. $filename = $args[0] #got file
80. Lock-Object $deltafile.SyncRoot{ $deltafile[$filename]=1} #lock& load
81. }
82. elseif ($event.Messagedata -eq "Burst") {
83. #do something
84. }
85. }
86.
87. while ($Tasks.isCompleted -contains $false){
88.
89. }
90.
91. #check results of tasks
92. for ($i=0; $i -lt $Tasks.Count; $i=$i+3){
93. $match=$Tasks[$i+1].EndInvoke($Tasks[$i])
94.
95.
96. if ($match.Count -gt 0) { # update clasafication array
97. $obj = New-Object System.Object
98. $obj | Add-Member -type NoteProperty -Name File -Value $Tasks[$i+2]
99. #defaults
100. $obj| Add-Member -type NoteProperty -Name Secret -Value 0
101. $obj| Add-Member -type NoteProperty -Name Sensitive -Value 0
102. $obj| Add-Member -type NoteProperty -Name Numbers -Value 0
103.
104. $obj=updatecnts $match $obj
105. $outarray += $obj
106. }
107. $Tasks[$i+1].Dispose()
108.
109. }
110.
111. $outarray | Out-GridView -Title "Content Classification" #display
112.
113. #run event handler as a separate job
114. Start-Job -Name EventHandler -ScriptBlock({C:\Users\bob\Documents\evhandler.ps1}) #run event handler in background
115.
116.
117. while ($true) { #the master executive loop
118.
119.
120. Start-Sleep -seconds 10
121. Lock-Object $deltafile.SyncRoot { #lock and iterate through synchronized list
122. foreach ($key in $deltafile.Keys) {
123.
124. $filename=$key
125.
126. if($deltafile[$key] -eq 0) { continue} #nothing new
127.
128. $deltafile[$key]=0
129. $match = & $scan $filename #run scriptblock
130. #incremental part
131.
132. $found=$false
133. $class=$false
134. if($match.Count -gt 0)
135. {$class =$true} #found sensitive data
136. if($outarray.File -contains $filename)
137. {$found = $true} #already in the array
138. if (!$found -and !$class){continue}
139.
140. #let's add/update
141. if (!$found) {
142.
143. $obj = New-Object System.Object
144. $obj | Add-Member -type NoteProperty -Name File -Value $Tasks[$i+2]
145. #defaults
146. $obj| Add-Member -type NoteProperty -Name Secret -Value 0
147. $obj| Add-Member -type NoteProperty -Name Sensitive -Value 0
148. $obj| Add-Member -type NoteProperty -Name Numbers -Value 0
149.
150. $obj=updatecnts $match $obj
151.
152. }
153. else {
154. $outarray|? {$_.File -eq $filename} | % { updatecnts $match $_}
155. }
156. $outarray | Out-GridView -Title "Content Classification ( $(get-date -format M/d/yy:HH:MM) )"
157.
158. } #foreach
159.
160. } #lock
161. }#while
162.
163. Write-Host "Done!"
Вкратце, этот классификатор выполняет первоначальное сканирование файлов в папке, сохраняет результаты классификации в $outarray, затем, когда возникает событие изменения файла, он обновляет $outarray, занося в него новые данные классификации. Другими словами, реализуется система добавления данных.
Есть небольшая побочная проблема в необходимости работы с обновлениями в $outarray, которая возникает каждый раз, когда в другой части скрипта классификации выполняется поиск того, что было изменено в этой переменной хеш-таблицы.
Это классическая ситуация состязания. И для обработки этой ситуации я решил использовать синхронизированныепеременные PowerShell.
Более подробно об этой загадочной функции PowerShell я расскажу в своей следующей заметке, и в заключении скажу несколько слов о том, как мастерить собственные решения.