-
Notifications
You must be signed in to change notification settings - Fork 140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Script Activity event class. #1159
Conversation
Note that I could add dozens of entries to the |
Looking great! Any thoughts on
|
Let me investigate the observable idea and get back to you. I'm afraid I'm not really following your point about |
Gah, that's right. Totally forgot |
(added to the issue as well but here for transparency) For what it is worth, there is some script activity that we've successfully been able to translate to 'process activity', since a script runs as a process - namely, Powershell 4104 and 4103 events. The way we've been able to achieve this is by mapping these as process start events with the script information in the process.cmd_line field. If need be, i can share some examples of how these are done. What are the use cases that render process activity as insufficient? |
@mikeradka we do the same, mapping these file/script opened/executed type events that is, into Process Activity That said, I'd much rather have |
Thanks for the question @mikeradka and @jonrau-at-queryai. I had actually anticipated this question and tried to pre-address it in the issue. Sorry if I failed to explain myself clearly there. I'll expand below and try to bring the necessary clarity. First, I want to reiterate a key point which is that I use the word script not just to denote a classic script file (foo.sh, foo.ps1, foo.bat, etc.). Restricting ourselves to that narrow understanding would be next to useless, given that much of the script activity that is interesting from a security perspective is file-less. I therefore use the word script to mean something that can be executed by a script engine, shell, interpreter, etc. With that meaning in mind, I'll come to the main point which is that in the general case script execution does not correlate 1:1 with process lifetime. In other words I don’t think it’s correct to say that "a script runs as a process" as Mike put it. Dynamic Script ExecutionMost scripting languages support this concept. PowerShell has
You’ll see (at least) two 4104 events for the above. The first describes execution of the full text of the The existence of dynamic script execution means that even a simple command can result in multiple scripts being run within the same process. Visibility into all of these scripts is important. In fact, seeing only the top-level script is often useless from a security perspective because, as I mentioned above, it is frequently just a tiny “loader” for the main payload. Interactive ShellsWhen a script host like Bash or PowerShell is being used interactively, each command typed by the user is a separate script execution, i.e. in the case of PowerShell, each command raises a separate 4104 event. Interactive shell instances can run for weeks, during which time hundreds or even thousands of commands may be executed. When you consider that an interactive shell instance may be used by a hands-on-keyboard attacker back at the C2, it’s clear that visibility into every command executed can be critical. Again, there is no 1:1 correlation here between scripts execution and process lifetime. Script-enabled ApplicationsScripts are not necessarily executed by the “classic” script host for the language in question. For example, any .NET application (Windows, Linux, and macOS) can use the System.Management.Automation assembly to execute PowerShell script directly within the application process itself. This is often used by .NET application developers to provide a customisation point to customers and integrators. It is even used within at least one security product that I’m familiar with to enable customers to implement custom posture assessment rules. JavaScript engines like V8 are also frequently integrated into applications for customisability purposes. I can only assume the same is true for Python - I just have no personal experience of that. A vulnerability in any of these usages may enable an attacker to execute arbitrary script so visibility is important. There is no 1:1 correlation between script execution and process lifetime. Office ApplicationsA special case of script-enabled applications is the MS Office suite. As I'm sure we all know, the VBA scripting engine within Word, Excel, etc. is very frequently used by attackers to initiate an attack on an endpoint. Security products can get visibility into every VBA macro execution but, again, there is no 1:1 correlation with process lifetime. |
Signed-off-by: Dave McCormack <[email protected]>
I've just pushed a commit which gives |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
@jonrau-at-queryai and @davemcatcisco That could be one approach, or i wonder if we could even get away with a |
What is the use case for this and do you have an example log? |
The use case is reporting script execution by a process, much like we have an event class to report other process activity. This is an extemely common technique in attack chains. In the MITRE ATT&CK taxonomy it is T1059. This technique has a number of sub-techniques corresponding to specific scripting languages. I don't have an example log. Remember that I'm an endpoint guy, i.e. I am the log. But if it helps, here's an example of a raw OS event describing the execution of a script that writes "pwned!" to stdout. <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-PowerShell" Guid="{a0c1853b-5c40-4b15-8766-3cf1c58f985a}" />
<EventID>4104</EventID>
<Version>1</Version>
<Level>5</Level>
<Task>2</Task>
<Opcode>15</Opcode>
<Keywords>0x0</Keywords>
<TimeCreated SystemTime="2024-08-16T19:34:29.5678744Z" />
<EventRecordID>1241</EventRecordID>
<Correlation ActivityID="{113dd862-efc9-0005-aee5-3d11c9efda01}" />
<Execution ProcessID="10816" ThreadID="5496" />
<Channel>Microsoft-Windows-PowerShell/Operational</Channel>
<Computer>REDACTED</Computer>
<Security UserID="S-1-5-21-REDACTED" />
</System>
<EventData>
<Data Name="MessageNumber">1</Data>
<Data Name="MessageTotal">1</Data>
<Data Name="ScriptBlockText">echo pwned!</Data>
<Data Name="ScriptBlockId">cf189926-fffc-458d-8739-0d6f852f39c4</Data>
<Data Name="Path">C:\Users\david\badfile.ps1</Data>
</EventData>
</Event> |
I've already actioned Jonathan's suggestion and made the I'm not following your suggestion about "getting away with a script object of some sort" because this PR does add a |
This class only contains a single activity, {
"activity_id": 1,
"activity_name": "Launch",
"actor": {
"process": {
"pid": -1
},
"user": {
"account_type": "Windows Account",
"account_type_id": 2,
"uid": "S-1-5-21-REDACTED"
}
},
"category_name": "System Activity",
"category_uid": 1,
"class_name": "Process Activity",
"class_uid": 1007,
"device": {
"hostname": "REDACTED",
"os": {
"name": "Windows",
"type": "Windows",
"type_id": 100
},
"type": "Unknown",
"type_id": 0
},
"message": "Powershell Script Block Logging",
"metadata": {
"event_code": "4104",
"log_name": "Microsoft-Windows-PowerShell/Operational",
"log_provider": "Microsoft-Windows-PowerShell",
"log_version": "1",
"original_time": "2024-08-16T19:34:29.5678744Z",
"product": {
"feature": {
"uid": "{a0c1853b-5c40-4b15-8766-3cf1c58f985a}"
},
"name": "Microsoft Windows",
"vendor_name": "Microsoft"
},
"profiles": [
"host"
],
"sequence": 1241,
"uid": "ce6e42aa-3ad8-43b1-95d4-beb2bd914135",
"version": "1.0.0-rc.2"
},
"process": {
"cmd_line": "echo pwned!",
"file": {
"name": "badfile.ps1",
"parent_folder": "C:\\Users\\david",
"path": "C:\\Users\\david\\badfile.ps1",
"type": "Regular File",
"type_id": 1
},
"pid": -1,
"uid": "cf189926-fffc-458d-8739-0d6f852f39c4"
},
"severity": "Other",
"severity_id": 99,
"status": "0x0",
"status_id": 99,
"time": 1723836869567,
"type_name": "Process Activity: Launch",
"type_uid": 100701,
"unmapped": {
"Correlation": {
"ActivityID": "{113dd862-efc9-0005-aee5-3d11c9efda01}"
},
"EventData": {
"MessageNumber": "1",
"MessageTotal": "1"
},
"Execution": {
"ProcessID": "10816",
"ThreadID": "5496"
},
"Opcode": "15",
"Task": "2"
}
} Here is another practical example: {
"activity_id": 1,
"activity_name": "Launch",
"actor": {
"process": {
"pid": -1
},
"user": {
"account_type": "Windows Account",
"account_type_id": 2,
"uid": "S-1-5-21-1568124518-47167176-2301812064-500"
}
},
"category_name": "System Activity",
"category_uid": 1,
"class_name": "Process Activity",
"class_uid": 1007,
"device": {
"hostname": "win-dc-683.attackrange.local",
"os": {
"name": "Windows",
"type": "Windows",
"type_id": 100
},
"type": "Unknown",
"type_id": 0
},
"message": "Powershell Script Block Logging",
"metadata": {
"event_code": "4104",
"log_name": "Microsoft-Windows-PowerShell/Operational",
"log_provider": "Microsoft-Windows-PowerShell",
"log_version": "1",
"original_time": "2021-03-26T11:04:21.820812700Z",
"product": {
"feature": {
"uid": "{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"
},
"name": "Microsoft Windows",
"vendor_name": "Microsoft"
},
"profiles": [
"host"
],
"sequence": 71872,
"uid": "2c2f3073-0a62-4abb-959a-e18c3b47c646",
"version": "1.0.0-rc.2"
},
"process": {
"cmd_line": "New-Item -Path \"C:\\Users\\Public\\apt_dir\" -ItemType Directory\n\nCopy-Item \"C:\\Windows\\System32\\cmd.exe\" C:\\Users\\Public\\apt_dir\\cons.exe\n\n$encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes({(Get-Service)}))\n\npowershell -EncodedCommand KABHAGUAdAAtAFMAZQByAHYAaQBjAGUAKQA=\n\nTest-Path \"C:\\Users\\Public\\apt_dir\\\"\n\nRemove-Item \"C:\\Users\\Public\\apt_dir\\b64.ps1\"\n\nGet-CimInstance Win32_OperatingSystem | select -expand Caption\n\n\n\n$sys_info = Get-CimInstance Win32_OperatingSystem | select -expand Caption*\n\nWrite-Host \"UserName: $env:UserName\"\n\nWrite-Host \"Domain: $env:Domain\"\n\nWrite-Host \"HostName: $env:ComputerName\"\n\nWrite-Host \"OS: $sysinfo\"",
"file": {
"name": "test.ps1",
"parent_folder": "C:\\Users\\Public",
"path": "C:\\Users\\Public\\test.ps1",
"type": "Regular File",
"type_id": 1
},
"pid": -1,
"uid": "497088bf-655b-4c87-9a3c-985faf7bd4cf"
},
"severity": "Warning",
"severity_id": 99,
"status": "0x0",
"status_id": 99,
"time": 1616756661820,
"type_name": "Process Activity: Launch",
"type_uid": 100701,
"unmapped": {
"Correlation": {
"ActivityID": "{B000CE73-222C-0002-6BD7-00B02C22D701}"
},
"EventData": {
"MessageNumber": "1",
"MessageTotal": "1"
},
"Execution": {
"ProcessID": "4104",
"ThreadID": "3220"
},
"Message": "Creating Scriptblock text (1 of 1):\nNew-Item -Path \"C:\\Users\\Public\\apt_dir\" -ItemType Directory\n\nCopy-Item \"C:\\Windows\\System32\\cmd.exe\" C:\\Users\\Public\\apt_dir\\cons.exe\n\n$encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes({(Get-Service)}))\n\npowershell -EncodedCommand KABHAGUAdAAtAFMAZQByAHYAaQBjAGUAKQA=\n\nTest-Path \"C:\\Users\\Public\\apt_dir\\\"\n\nRemove-Item \"C:\\Users\\Public\\apt_dir\\b64.ps1\"\n\nGet-CimInstance Win32_OperatingSystem | select -expand Caption\n\n\n\n$sys_info = Get-CimInstance Win32_OperatingSystem | select -expand Caption*\n\nWrite-Host \"UserName: $env:UserName\"\n\nWrite-Host \"Domain: $env:Domain\"\n\nWrite-Host \"HostName: $env:ComputerName\"\n\nWrite-Host \"OS: $sysinfo\"\n\n\n\nScriptBlock ID: 497088bf-655b-4c87-9a3c-985faf7bd4cf\nPath: C:\\Users\\Public\\test.ps1",
"Opcode": "On create calls",
"RenderingInfo": {
"Culture": "en-US"
},
"Task": "Execute a Remote Command"
}
} What I am having trouble seeing here is why is a new class necessary? In this example, the powershell process (test.ps1) is running a set of instructions in |
I'm thinking you haven't read my long comment above in which I explain why script execute is different to process start. To recap, a single process can execute an unlimited number of scripts over its lifetime. Moreover, that lifetime may be weeks. Let me put it this way... Imagine that an attacker starts PowerShell with the following command line. Let's further imagine that the effect of this is to download a payload script and execute it. The payload connects to a C2 and runs for two weeks before it starts to receive and execute arbitrary PowerShell commands under the control of the attacker. All of this happens within the single instance of PowerShell that you see launched below. This is not a contrived example. This is how real world file-less PowerShell attacks work. Explain to me how all of this activity could be represented with process start events?
|
In that example by mapping to It is not required for the |
Sorry but that's close to useless from a security perspective. The interesting script that the incident responder, threat hunter, or detection content writer would want to see is the script that was downloaded and dynamically executed. It's not the tiny little downloader script that would be captured on your command line. I specialised in PowerShell defence two jobs ago and believe me I know this for certain. Nobody's intersted in the initial command line. It's what happens subsequently that's interesting. Edit: And remember that there isn't any command line in the case where the script is piped into PowerShell! |
Something a script needs in order to run is an interpreter - be it bash, zsh, python, powershell, etc. In those cases, the interpreter is the process launched to run the script. The script is the set of instructions given to the interpreter. If those are all true statements, then the instructions given to the interpreter is essentially details surrounding the process that was launched. The I think this is worthy of a discussion in our OCSF calls so we can get folks walking through scenarios to best model each scenario out adequately. |
If you think that "script" is purely the set of instructions given directly to an interpreter, then I completely disagree with that. An EDR which can only provide visibility to script specified directly to the interpreter is severely lacking in that area. In all EDR products that I've worked on in the last five years, the product provided full visibility into all script executed by PowerShell, irrespective of how the script got to PowerShell. We'll have to discuss Tuesday. |
Mitre D3fend uses this as a definition: https://d3fend.mitre.org/dao/artifact/d3f:ExecutableScript/ If you are able to gather some EDR examples, that could be helpful for the upcoming conversation on it. I am hesitant to add a new class with a single activity and without other examples since 4104 can pretty successfully be mapped to |
You can't just look at a single 4104 to get the full picture. EDRs with good support for PowerShell correlate multiple related 4104 events using the Correlation -> ActivityID. In the example above, you would see two separate 4104 events with a common ActivityID. The first event will describe execution of the |
I agree that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved to get the ball rolling on this since we addressed previous remarks in Dave's thorough demo.
Just one nit, which we could consider either within this PR or consider in a subsequent PR: it might be useful to update the description for parent_uid
to lead with what it identifies and then give the condition, i.e:
This attribute identifies the parent script when a script is a dynamically executed sub-script and when the underlying script engine supports use of the <code>uid</code> attribute.
(example or two if any related raw fields exist)
Sure. Let me have a think about it. I'm actually thinking of making 1-2 other changes based on some offline feedback. Might just be changes to descriptions but it'll take a few days due to other stuff I have going on. |
Converting to a draft, feel free to mark as ready when you feel it's good to go. |
I approved, merged from main, converted to ready, and merged.
…On Thu, Aug 22, 2024 at 10:23 AM Rajas ***@***.***> wrote:
Sure. Let me have a think about it. I'm actually thinking of making 1-2
other changes based on some offline feedback. Might just be changes to
descriptions but it'll take a few days due to other stuff I have going on.
Converting to a draft, feel free to mark as ready when you feel it's good
to go.
—
Reply to this email directly, view it on GitHub
<#1159 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AS5LBZQC6DFCVOJ43XK6G3LZSYNBTAVCNFSM6AAAAABMSGMSP2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMBVGI3TINJTGA>
.
You are receiving this because your review was requested.Message ID:
***@***.***>
|
Thanks! In a response to some feedback above, I was intending to make a few tweaks to the documentation/descriptions. I also realised yesterday that I had somehow forgotten to include VBA in the enum of script types - a big oversight because its use in MS Office macros means it is frequently the first 'Execution' technique employed by an attacker on Windows. There's no need to revert this commit however. Instead I'll open a new PR to cover this. |
Related Issue:
#1156
Description of changes:
Added a Script Activity event class to the System category as described in the related issue.