Несколько месяцев тому назад я приступил к решению одной задачи. Я решил доказать, что PowerShell может использоваться как инструмент контроля безопасности. Я закончил работу над своей публикацией, в которой описывается код PowerShell, позволяющий собирать события файловой системы, выполнять некоторые базовые функции анализа, а затем выводить результаты в графическом формате. Возможно, моя платформа безопасности с использованием скриптов (Security Scripting Platform; SSP) не является минимально жизнеспособным продуктом, но она, как мне кажется, полезна в качестве простого инструмента контроля для одиночного каталога файлов.

По завершении проекта я понимал, что определенный функционал можно усовершенствовать. Обработка событий выполнялась неэф-фективно. Передача информации между различными частями платформы SSP требовала оптимизации, а информация, отображаемая с помощью примитивного кода Out-GridView, была представлена в виде прославленной таблицы.

Новые и улучшенные возможности


Я решил повысить жизнеспособность платформы SSP. В первую очередь нужно было оптимизировать процедуру обработки событий. Изначально она была разработана таким образом, что обработчик в моем блоке скрипта Register-EngineEvent (см. информацию о блоках скриптов) извлекал сообщения о событиях файла и отправлял их во внутреннюю очередь, а затем — в основной фрагмент кода, ПО для классификации.

Подумав немного, я понял, что можно отправлять сообщения напрямую с помощью функции Register-EngineEvent -forward из блока скрипта обработки событий и просто удалить ненужный уровень этих жутких очередей.

Ниже представлена упрощенная и оптимизированная версия.

1.	#Count events, detect bursts, forward to main interface
2.	
3.	$cur = Get-Date
4.	$Global:Count=0
5.	$Global:baseline = @{"Monday" = @(1,1,1); "Tuesday" = @(1,.5,1);"Wednesday" = @(4,4,4);"Thursday" = @(7,12,4); "Friday" = @(5,4,6); "Saturday"=@(2,1,1); "Sunday"= @(2,4,2)}
6.	$Global:cnts = @(0,0,0)
7.	$Global:burst = $false
8.	$Global:evarray = New-Object System.Collections.ArrayList
9.	
10.	$action = { 
11.	$Global:Count++ 
12.	$d=(Get-Date).DayofWeek
13.	$i= [math]::floor((Get-Date).Hour/8) 
14.	
15.	$Global:cnts[$i]++ 
16.	
17.	
18.	#event auditing!
19.	
20.	$rawtime = $EventArgs.NewEvent.TargetInstance.LastAccessed.Substring(8,6)
21.	$filename = $EventArgs.NewEvent.TargetInstance.Name
22.	$etime= [datetime]::ParseExact($rawtime,"HHmmss",$null)
23.	
24.	
25.	$msg="$($etime)): Access of file $($filename)"
26.	$msg|Out-File C:\Users\Administrator\Documents\events.log -Append
27.	
28.	New-Event -SourceIdentifier Delta -MessageData "Access" -EventArguments $filename #notify 
29.	
30.	$Global:evarray.Add(@($filename,$etime))
31.	if(!$Global:burst) {
32.	$Global:start=$etime
33.	$Global:burst=$true 
34.	}
35.	else { 
36.	if($Global:start.AddMinutes(15) -gt $etime ) { 
37.	$Global:Count++
38.	#File behavior analytics
39.	$sfactor=2*[math]::sqrt( $Global:baseline["$($d)"][$i])
40.	
41.	if ($Global:Count -gt $Global:baseline["$($d)"][$i] + 2*$sfactor) { #at 95% level of poisson
42.	
43.	
44.	"$($etime): Burst of $($Global:Count) accesses"| Out-File C:\Users\Administrator\Documents\events.log -Append 
45.	$Global:Count=0
46.	$Global:burst =$false
47.	New-Event -SourceIdentifier Delta -MessageData "Burst" -EventArguments $Global:evarray #notify on burst
48.	
49.	$Global:evarray= [System.Collections.ArrayList] @()
50.	}
51.	}
52.	else { $Global:burst =$false; $Global:Count=0; $Global:evarray= [System.Collections.ArrayList] @()}
53.	} 
54.	} 
55.	
56.	Register-EngineEvent -SourceIdentifier Delta -Forward 
57.	Register-WmiEvent -Query "SELECT * FROM __InstanceModificationEvent WITHIN 5 WHERE TargetInstance ISA 'CIM_DataFile' and TargetInstance.Path = '\\Users\\Administrator\\' and targetInstance.Drive = 'C:' and (targetInstance.Extension = 'txt' or targetInstance.Extension = 'doc' or targetInstance.Extension = 'rtf') and targetInstance.LastAccessed > '$($cur)' " -sourceIdentifier "Accessor" -Action $action 
58.	Write-Host "starting engine ..."
59.	
60.	while ($true) {
61.	
62.	Wait-Event -SourceIdentifier Access # just hang on this so I don't exit 
63.	
64.	}

Затем я взял основной фрагмент кода, где выполняется классификация файлов по наличию номеров социального страхования и прочих важных ключевых слов. При поступлении событий из обработчика запускается реклассификация файлов. Затем этот код периодически отображает некоторые результаты классификации.

В последней версии я удалил классификацию в «реальном времени» и сосредоточился на очистке кода PowerShell и улучшении графики — об этом речь пойдет ниже.

В первоначальной версии я выбрал неправильный путь и доверил модулю блокировки данных PowerShell синхронизацию доступа к данным от параллельных задач, которые я применял для выполнения рутинной работы. Последующее тестирование показало, что бесплатный модуль, который реализует командлет Lock-Object, не работает.

Даже начинающие системные программисты знают, что синхронизацию проще выполнять с помощью сообщений, а не низкоуровневых блокировок. Я изменил код, и теперь, как упоминалось выше, сообщения направляются из обработчика событий непосредственно в основной цикл обработки сообщений. Иными словами: мне удалось обеспечивать обработку асинхронных событий в синхронном режиме.
?

PowerShell и диаграммы .Net Framework


Самое заметное мое достижение за последний месяц — я разобрался, как встраивать диаграммы в стиле Майкрософт в PowerShell. Другими словами, гистограммами, линейными, точечными и иными диаграммами, доступными в Excel и Word, можно управлять на программном уровне в PowerShell. Я сравнительно недавно занимаюсь программированием в PowerShell, поэтому меня это сильно порадовало. Дополнительную информацию об элементах управления .Net Framework см. в этой публикации.

Это классно! Поначалу я даже подумал, что смогу также заменить запутанный код Out-GridView.
Но вскоре стало ясно, что проблема состоит в том, что помимо этого приходится иметь дело с элементами интерактивного программи-рования, связанными с формами Майкрософт. Я же просто хотел отображать свои диаграммы .Net и не писать код для низкоуровневых функций. Существует ли упрощенный способ решения этой задачи?

После активных размышлений я пришел к выводу, что простейшим способом в данной ситуации будет запуск каждой диаграммы в ее собственной функции runspace как отдельной задачи. (Примечание для фанатов: таким образом я избавился от необходимости создания функции обработки сообщений для всех диаграмм, так как каждая из них выполняется отдельно в виде модальных диалогов.)

В этом мне также помог бесплатный модуль PowerShell, который сворачивает запутанные элементы управления диаграмм .Net. Спасибо тебе, Мариус!

Ранее я уже настроил систему управления задачами, чтобы сканировать и классифицировать каждый контролируемый файл в каталоге. И теперь для запуска диаграмм мне нужно было просто обеспечить повторное использование этого кода управления задачами.

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



Моя отличная информационная панель. Неплохо для PowerShell
с диаграммами .Net.


Если кому-то из вас интересен основной фрагмент кода, выполняющего всю работу в моей платформе SSP, то он приведен здесь ниже:

1.	$scan = { #file content scanner
2.	$name=$args[0]
3.	function scan {
4.	Param (
5.	[parameter(position=1)]
6.	[string] $Name
7.	)
8.	$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}' }
9.	
10.	$data = Get-Content $Name
11.	
12.	$cnts= @()
13.	
14.	if($data.Length -eq 0) { return $cnts} 
15.	
16.	foreach ($key in $classify.Keys) {
17.	
18.	$m=$classify[$key].matches($data) 
19.	
20.	if($m.Count -gt 0) {
21.	$cnts+= @($key,$m.Count) 
22.	}
23.	} 
24.	$cnts 
25.	}
26.	scan $name
27.	}
28.	
29.	
30.	
31.	
32.	#launch a .net chart 
33.	function nchart ($r, $d, $t,$g,$a) {
34.	
35.	$task= {
36.	Param($d,$t,$g,$a)
37.	
38.	Import-Module C:\Users\Administrator\Documents\charts.psm1
39.	$chart = New-Chart -Dataset $d -Title $t -Type $g -Axis $a
40.	Show-Chart $chart
41.	
42.	}
43.	$Task = [powershell]::Create().AddScript($task).AddArgument($d).AddArgument($t).AddArgument($g).AddArgument($a)
44.	$Task.RunspacePool = $r
45.	$Task.BeginInvoke()
46.	
47.	}
48.	
49.	Register-EngineEvent -SourceIdentifier Delta -Action {
50.	
51.	if($event.MessageData -eq "Burst") { #just look at bursts
52.	New-Event -SourceIdentifier File -MessageData $event.MessageData -EventArguments $event.SourceArgs 
53.	}
54.	
55.	
56.	Remove-Event -SourceIdentifier Delta
57.	}
58.	
59.	
60.	
61.	
62.	$list=Get-WmiObject -Query "SELECT * From CIM_DataFile where Path = '\\Users\\Administrator\\' and Drive = 'C:' and (Extension = 'txt' or Extension = 'doc' or Extension = 'rtf')" 
63.	
64.	
65.	#long list --let's multithread
66.	
67.	#runspace
68.	$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1,5)
69.	$RunspacePool.Open()
70.	$Tasks = @()
71.	
72.	
73.	
74.	
75.	foreach ($item in $list) {
76.	
77.	$Task = [powershell]::Create().AddScript($scan).AddArgument($item.Name)
78.	$Task.RunspacePool = $RunspacePool
79.	
80.	$status= $Task.BeginInvoke()
81.	$Tasks += @($status,$Task,$item.Name)
82.	}
83.	
84.	
85.	
86.	
87.	#wait
88.	while ($Tasks.isCompleted -contains $false){
89.	
90.	}
91.	
92.	
93.	#Analytics, count number of sensitive content for each file
94.	$obj = @{}
95.	$tdcnt=0
96.	$sfcnt=0
97.	$nfcnt=0
98.	
99.	
100.	for ($i=0; $i -lt $Tasks.Count; $i=$i+3) {
101.	$match=$Tasks[$i+1].EndInvoke($Tasks[$i]) 
102.	
103.	if ($match.Count -gt 0) { 
104.	$s = ([string]$Tasks[$i+2]).LastIndexOf("\")+1
105.	
106.	$obj.Add($Tasks[$i+2].Substring($s),$match)
107.	for( $j=0; $j -lt $match.Count; $j=$j+2) { 
108.	switch -wildcard ($match[$j]) {
109.	'Top*' { $tdcnt+= 1 }
110.	
111.	'Sens*' { $sfcnt+= 1} 
112.	
113.	'Numb*' { $nfcnt+=1} 
114.	
115.	} 
116.	
117.	}
118.	} 
119.	$Tasks[$i+1].Dispose()
120.	
121.	}
122.	
123.	
124.	#Display Initial Dashboard
125.	#Pie chart of sensitive files based on total counts of senstive dat
126.	$piedata= @{}
127.	foreach ( $key in $obj.Keys) {
128.	$senscnt =0
129.	for($k=1; $k -lt $obj[$key].Count;$k=$k+2) {
130.	$senscnt+= $obj[$key][$k]
131.	
132.	}
133.	$piedata.Add($key, $senscnt) 
134.	
135.	}
136.	
137.	
138.	nchart $RunspacePool $piedata "Files with Sensitive Content" "Pie" $false
139.	
140.	#Bar Chart of Total Files, Sensitive vs Total
141.	$bardata = @{"Total Files" = $Tasks.Count}
142.	$bardata.Add("Files w. Top Secret",$tdcnt)
143.	$bardata.Add("Files w. Sensitive", $sfcnt)
144.	$bardata.Add("Files w. SS Numbers",$nfcnt)
145.	
146.	
147.	nchart $RunspacePool $bardata "Sensitive Files" "Bar" $false
148.	
149.	
150.	#run event handler as a seperate job
151.	Start-Job -Name EventHandler -ScriptBlock({C:\Users\Administrator\Documents\evhandler.ps1})
152.	
153.	
154.	while ($true) { #main message handling loop
155.	
156.	[System.Management.Automation.PSEventArgs] $args = Wait-Event -SourceIdentifier File # wait on event
157.	Remove-Event -SourceIdentifier File
158.	#Write-Host $args.SourceArgs 
159.	if ($args.MessageData -eq "Burst") {
160.	#Display Bursty event
161.	$dt=$args.SourceArgs
162.	#time in seconds
163.	[datetime]$sevent =$dt[0][1]
164.	
165.	$xyarray = [ordered]@{}
166.	$xyarray.Add(0,1)
167.	for($j=1;$j -lt $dt.Count;$j=$j+1) {
168.	[timespan]$diff = $dt[$j][1] - $sevent
169.	$xyarray.Add($diff.Seconds,$j+1) 
170.	}
171.	nchart $RunspacePool $xyarray "Burst Event" "StepLine" $true 
172.	} 
173.	
174.	
175.	}#while
176.	
177.	Write-Host "Done!"

Извлеченные уроки


Конечно, в рамках любой задачи главным является не сама цель, а процесс поиска решения. Согласны? В данном случае я узнал главную для себя вещь — PowerShell можно использовать для контроля безопасности. Для одиночного каталога, в небольшой системе. Причем только в случае эпизодического использования.

Я планирую внести определенные улучшения в реализацию, которую я только что представил (добавить графику в режиме реального времени), однако я вовсе не ожидаю, что мое финальное программное обеспече-ние станет чем-то большим, чем просто модельным проектом.

Контроль событий файлов, анализ и графическое отображение информации для всей системы — это крайне сложная задача для одного человека. В принципе, мое решение можно перекодировать, используя C++, но в рамках приложения по-прежнему придется решать проблему задержек и отклонений при обработке низкоуровневых событий. Чтобы сделать это правильно, необходимо иметь некие механизмы глубоко в самой ОС (для начала), а затем выполнять гораздо более серьезный анализ событий файлов, чем тот, что выполняется моим примитивным кодом. Это далеко не просто!

Обычно я заканчиваю подобные публикации категории «сделай сам» словами «вы знаете, о чем речь». Не буду вас разочаровывать.

Вы знаете, о чем речь. Наше собственное решение корпоративного класса является настоящейплатформой безопасности данных (Data Security Platform; DSP), так как оно выполняет функции классификации, анализа, обнаружения угроз и многое другое на уровне целых ИТ-систем.

Не отступайте, пробуйте свои варианты. Может быть, даже на основе этого проекта. Разбирайтесь с реальными возможностями платформы DSP, изучайте ее слабые и сильные стороны.

Есть вопросы? Не стесняйтесь и задавайте их нам!

Комментарии (0)