Skip to content

Commit

Permalink
Update client monitoring tables when relevant artifacts are modified (V…
Browse files Browse the repository at this point in the history
…elocidex#554)

When an event artifact is modified through the GUI we need to
recompile the client monitoring tables, or we risk caching stale data.

This change broadcasts artifact modification events to the
Server.Internal.ArtifactModification artifact. The client event
manager then listens to changes in this artifact and rebuilds the
tables (and increments versions) when the artifact changes.
  • Loading branch information
scudette authored Aug 14, 2020
1 parent 982f69e commit 0fb9c95
Show file tree
Hide file tree
Showing 20 changed files with 510 additions and 109 deletions.
57 changes: 14 additions & 43 deletions api/artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"www.velocidex.com/golang/velociraptor/logging"
"www.velocidex.com/golang/velociraptor/paths"
"www.velocidex.com/golang/velociraptor/result_sets"
"www.velocidex.com/golang/velociraptor/services"
users "www.velocidex.com/golang/velociraptor/users"
"www.velocidex.com/golang/velociraptor/utils"
)
Expand Down Expand Up @@ -115,60 +116,30 @@ func setArtifactFile(config_obj *config_proto.Config,
required_prefix string) (
*artifacts_proto.Artifact, error) {

// First ensure that the artifact is correct.
tmp_repository := artifacts.NewRepository()
artifact_definition, err := tmp_repository.LoadYaml(
in.Artifact, true /* validate */)
if err != nil {
return nil, err
}

if !strings.HasPrefix(artifact_definition.Name, required_prefix) {
return nil, errors.New(
"Modified or custom artifacts must start with '" +
required_prefix + "'")
}

file_store_factory := file_store.GetFileStore(config_obj)
vfs_path := paths.GetArtifactDefintionPath(artifact_definition.Name)

// Load the new artifact into the global repo so it is
// immediately available.
global_repository, err := artifacts.GetGlobalRepository(config_obj)
if err != nil {
return nil, err
}
repository_manager := services.GetRepositoryManager()

switch in.Op {

case api_proto.SetArtifactRequest_DELETE:
global_repository.Del(artifact_definition.Name)
err = file_store_factory.Delete(vfs_path)
return artifact_definition, err

case api_proto.SetArtifactRequest_SET:
// Now write it into the filestore.
fd, err := file_store_factory.WriteFile(vfs_path)
// First ensure that the artifact is correct.
tmp_repository := artifacts.NewRepository()
artifact_definition, err := tmp_repository.LoadYaml(
in.Artifact, true /* validate */)
if err != nil {
return nil, err
}
defer fd.Close()

// We want to completely replace the content of the file.
err = fd.Truncate()
if err != nil {
return nil, err
if !strings.HasPrefix(artifact_definition.Name, required_prefix) {
return nil, errors.New(
"Modified or custom artifacts must start with '" +
required_prefix + "'")
}

_, err = fd.Write([]byte(in.Artifact))
if err != nil {
return nil, err
}
return artifact_definition, repository_manager.DeleteArtifactFile(
artifact_definition.Name)

// Load the artifact into the currently running repository.
// Artifact is already valid - no need to revalidate it again.
_, err = global_repository.LoadYaml(in.Artifact, false /* validate */)
return artifact_definition, err
case api_proto.SetArtifactRequest_SET:
return repository_manager.SetArtifactFile(in.Artifact, required_prefix)
}

return nil, errors.New("Unknown op")
Expand Down
2 changes: 1 addition & 1 deletion artifacts/definitions/Generic/Client/Stats.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ sources:
FROM pslist(pid=getpid())
})

- precondition: SELECT OS From info() where OS = 'linux'
- precondition: SELECT OS From info() where OS != 'windows'
queries:
- SELECT * from foreach(
row={
Expand Down
10 changes: 10 additions & 0 deletions artifacts/definitions/Server/Internal/ArtifactModification.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Server.Internal.ArtifactModification
description: |
This event artifact is an internal event stream over which
notifications of artifact modifications are sent. Interested parties
can watch for new artifact modification events and rebuild caches
etc.
Note: This is an automated system artifact. You do not need to start it.
type: SERVER_EVENT
43 changes: 25 additions & 18 deletions artifacts/definitions/Windows/EventLogs/PowershellModule.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
name: Windows.EventLogs.PowershellModule
description: |
This Artifact will search and extract Module events (Event ID 4103) from
This Artifact will search and extract Module events (Event ID 4103) from
Powershell-Operational Event Logs.
Powershell is commonly used by attackers accross all stages of the attack
Powershell is commonly used by attackers accross all stages of the attack
lifecycle. Although quite noisy Module logging can provide valuable insight.
There are several parameter's availible for search leveraging regex.
- DateAfter enables search for events after this date.
- DateBefore enables search for events before this date.
- ContextRegex enables regex search over ContextInfo text field.
- PayloadRegex enables a regex search over Payload text field.
There are several parameter's availible for search leveraging regex.
- DateAfter enables search for events after this date.
- DateBefore enables search for events before this date.
- ContextRegex enables regex search over ContextInfo text field.
- PayloadRegex enables a regex search over Payload text field.
- SearchVSS enables VSS search
author: Matt Green - @mgreen27

Expand All @@ -35,14 +35,21 @@ parameters:
description: "regex search over Payload text field."
- name: SearchVSS
description: "Add VSS into query."
type: bool

type: bool
- name: LogLevelMap
type: hidden
default: |
Choice,Regex
All,"."
Warning,"3"
Verbose,"5"
sources:
- query: |
-- Build time bounds
LET DateAfterTime <= if(condition=DateAfter,
LET DateAfterTime <= if(condition=DateAfter,
then=timestamp(epoch=DateAfter), else=timestamp(epoch="1600-01-01"))
LET DateBeforeTime <= if(condition=DateBefore,
LET DateBeforeTime <= if(condition=DateBefore,
then=timestamp(epoch=DateBefore), else=timestamp(epoch="2200-01-01"))
-- Parse Log level dropdown selection
Expand All @@ -67,7 +74,7 @@ sources:
FROM foreach(
row=files,
query={
SELECT
SELECT
timestamp(epoch=System.TimeCreated.SystemTime) As EventTime,
System.EventID.Value as EventID,
System.Computer as Computer,
Expand All @@ -90,14 +97,14 @@ sources:
then=ContextInfo=~PayloadRegex,else=TRUE)
})
ORDER BY Source DESC
-- Group results for deduplication
LET grouped = SELECT *
FROM hits
GROUP BY EventRecordID
-- Output results
SELECT
SELECT
EventTime,
EventID,
Computer,
Expand All @@ -110,4 +117,4 @@ sources:
Opcode,
Task,
Source
FROM grouped
FROM grouped
59 changes: 30 additions & 29 deletions artifacts/definitions/Windows/EventLogs/PowershellScriptblock.yaml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
name: Windows.EventLogs.PowershellScriptblock
description: |
This Artifact will search and extract ScriptBlock events (Event ID 4104) from
This Artifact will search and extract ScriptBlock events (Event ID 4104) from
Powershell-Operational Event Logs.
Powershell is commonly used by attackers accross all stages of the attack
lifecycle. A valuable hunt is to search Scriptblock logs for signs of
Powershell is commonly used by attackers accross all stages of the attack
lifecycle. A valuable hunt is to search Scriptblock logs for signs of
malicious content.
There are several parameter's availible for search leveraging regex.
- DateAfter enables search for events after this date.
- DateBefore enables search for events before this date.
- SearchStrings enables regex search over scriptblock text field.
- StringWhiteList enables a regex whitelist for scriptblock text field.
- PathWhitelist enables a regex whitelist for path of scriptblock.
- LogLevel enables searching on type of log. Default is Warning level
which is logged even if ScriptBlock logging is turned off when
suspicious keywords detected in Powershell interpreter. See second
There are several parameter's availible for search leveraging regex.
- DateAfter enables search for events after this date.
- DateBefore enables search for events before this date.
- SearchStrings enables regex search over scriptblock text field.
- StringWhiteList enables a regex whitelist for scriptblock text field.
- PathWhitelist enables a regex whitelist for path of scriptblock.
- LogLevel enables searching on type of log. Default is Warning level
which is logged even if ScriptBlock logging is turned off when
suspicious keywords detected in Powershell interpreter. See second
reference for list of keywords.
- SearchVSS enables VSS search
- SearchVSS enables VSS search
author: Matt Green - @mgreen27

Expand All @@ -40,7 +40,7 @@ parameters:
description: "Regex of string to witelist"
- name: PathWhitelist
description: "Regex of path to whitelist."

- name: LogLevel
description: "Log level. Warning is Powershell default bad keyword list."
type: choices
Expand All @@ -58,14 +58,14 @@ parameters:
Verbose,"5"
- name: SearchVSS
description: "Add VSS into query."
type: bool
type: bool

sources:
- query: |
-- Build time bounds
LET DateAfterTime <= if(condition=DateAfter,
LET DateAfterTime <= if(condition=DateAfter,
then=timestamp(epoch=DateAfter), else=timestamp(epoch="1600-01-01"))
LET DateBeforeTime <= if(condition=DateBefore,
LET DateBeforeTime <= if(condition=DateBefore,
then=timestamp(epoch=DateBefore), else=timestamp(epoch="2200-01-01"))
-- Parse Log level dropdown selection
Expand All @@ -81,7 +81,8 @@ sources:
FROM Artifact.Windows.Search.VSS(SearchFilesGlob=EventLog)
},
else= {
SELECT * FROM glob(globs=EventLog)
SELECT *, FullPath AS Source
FROM glob(globs=EventLog)
})
-- Main query
Expand All @@ -101,19 +102,19 @@ sources:
System.Level as Level,
System.Opcode as Opcode,
System.Task as Task,
if(condition=Source, then=Source, else=FullPath) as Source
Source
FROM parse_evtx(filename=FullPath)
WHERE System.EventID.Value = 4104
AND EventTime < DateBeforeTime
AND EventTime > DateAfterTime
AND EventTime > DateAfterTime
AND format(format="%d", args=System.Level) =~ LogLevelRegex.value[0]
AND if(condition=SearchStrings,
AND if(condition=SearchStrings,
then=ScriptBlockText =~ SearchStrings,
else=TRUE)
AND if(condition=StringWhitelist,
else=TRUE)
AND if(condition=StringWhitelist,
then= NOT ScriptBlockText =~ StringWhitelist,
else=TRUE)
AND if(condition=PathWhitelist,
else=TRUE)
AND if(condition=PathWhitelist,
then= NOT Path =~ PathWhitelist,
else=TRUE)
})
Expand All @@ -139,4 +140,4 @@ sources:
Opcode,
Task,
Source
FROM grouped
FROM grouped
6 changes: 6 additions & 0 deletions bin/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"www.velocidex.com/golang/velociraptor/services/journal"
"www.velocidex.com/golang/velociraptor/services/labels"
"www.velocidex.com/golang/velociraptor/services/launcher"
"www.velocidex.com/golang/velociraptor/services/repository"
"www.velocidex.com/golang/velociraptor/uploads"
"www.velocidex.com/golang/velociraptor/utils"
vql_subsystem "www.velocidex.com/golang/velociraptor/vql"
Expand Down Expand Up @@ -199,6 +200,11 @@ func startEssentialServices(config_obj *config_proto.Config, sm *services.Servic
return err
}

err = sm.Start(repository.StartRepositoryManager)
if err != nil {
return err
}

if config_obj.Datastore != nil {
err = sm.Start(journal.StartJournalService)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package constants
import "regexp"

const (
VERSION = "0.4.7"
VERSION = "0.4.8"
ENROLLMENT_WELL_KNOWN_FLOW = "E:Enrol"
MONITORING_WELL_KNOWN_FLOW = FLOW_PREFIX + "Monitoring"

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ require (
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0
github.com/golang/snappy v0.0.1
github.com/google/rpmpack v0.0.0-20200615183209-0c831d19bd44
github.com/google/uuid v1.1.1 // indirect
github.com/google/uuid v1.1.1
github.com/gookit/color v1.2.7
github.com/gorilla/csrf v1.6.2
github.com/gorilla/schema v1.1.0
Expand Down
23 changes: 23 additions & 0 deletions gui/static/angular-components/artifact/client-event-directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,29 @@ ClientEventController.prototype.showHelp = function() {
return false;
};

ClientEventController.prototype.showClientMonitoringTables = function() {
var self = this;
var url = 'v1/GetClientMonitoringState';

this.error = "";
this.grrApiService_.get(url).then(function(response) {
self.state = response['data'];

var modalScope = self.scope_.$new();
var modalInstance = self.uibModal_.open({
template: '<grr-inspect-json json="json" '+
'on-resolve="resolve()" title="Raw Client Monitoring Tables"/>',
scope: modalScope,
windowClass: 'wide-modal high-modal',
size: 'lg'
});

modalScope["json"] = JSON.stringify(self.state, null, 2);
modalScope["resolve"] = modalInstance.close;
});

return false;
};

// When the label changes, we need to switch the current table.
ClientEventController.prototype.onLabelChange = function() {
Expand Down
7 changes: 7 additions & 0 deletions gui/static/angular-components/artifact/client-event.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
<i class="fa fa-edit"></i>
</button>

<button class="btn btn-default "
tabindex="0" type="button" title="Show client monitoring tables"
ng-click="controller.showClientMonitoringTables()"
>
<span><i class="fa fa-binoculars"></i></span>
</button>

<div class="btn-group" uib-dropdown is-open="status.isopen">
<button id="single-button" type="button"
class="btn btn-default"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ InspectJsonController.prototype.showSettings = function() {
exports.InspectJsonDirective = function() {
return {
scope: {
title: "@",
json: "=",
onResolve: '&',
},
Expand Down
2 changes: 1 addition & 1 deletion gui/static/angular-components/core/inspect-json.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="modal-header">
<h3>Raw Response JSON</h3>
<h3>{{ title || "Raw Response JSON" }}</h3>
</div>

<div class="modal-body d-flex flex-column">
Expand Down
Loading

0 comments on commit 0fb9c95

Please sign in to comment.