
В предыдущей заметке этой серии я предложил возможность объединения моих отдельных скриптов — один для обработки событий, другой для классификации — в одну систему. Не замахнуться ли на платформу безопасности на основе одного кода 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 я расскажу в своей следующей заметке, и в заключении скажу несколько слов о том, как мастерить собственные решения.

