SIEM Command Line Auditing 4688 - 4689

Hello,

2 of the most interesting event id's in siem are 4688 and 4689, which can be enabled with a gpo and enable us to monitor every command used in your network.

Interesting fields are:

winlog.event_data.NewProcessName
winlog.event_data.ParentProcessName
winlog.event_data.ProcessName

Unfortunately some of the processes audited are located in %USERPROFILE% folder, which means it looks like this:

C:\Users\username\AppData\Local\GitHubDesktop\app-2.2.0\resources\app\git\mingw64\bin\git.exe

It would be nice if the security processor of Winlogbeat could get expanded and the executable itself could be extracted to process.name

That would enable us to do all kinds of siem related interesting analytics on them.

Is this already on a to do? Should I make a GitHub issue?

Grtz

Willem

Some extra documentation => https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4688

It would be even better if fields such as Token Elevation Type and Mandatory Label would be translated to a readable value.

SID RID RID label Meaning
S-1-16-0 0x00000000 SECURITY_MANDATORY_UNTRUSTED_RID Untrusted.
S-1-16-4096 0x00001000 SECURITY_MANDATORY_LOW_RID Low integrity.
S-1-16-8192 0x00002000 SECURITY_MANDATORY_MEDIUM_RID Medium integrity.
S-1-16-8448 0x00002100 SECURITY_MANDATORY_MEDIUM_PLUS_RID Medium high integrity.
S-1-16-12288 0X00003000 SECURITY_MANDATORY_HIGH_RID High integrity.
S-1-16-16384 0x00004000 SECURITY_MANDATORY_SYSTEM_RID System integrity.
S-1-16-20480 0x00005000 SECURITY_MANDATORY_PROTECTED_PROCESS_RID Protected process.

Token Elevation Type : Token elevation is about User Account Control

  • %% 1936 - Type 1 is a full token with no privileges removed or groups disabled. A full token is only used if User Account Control is disabled or if the user is the built-in Administrator account or a service account.
  • %% 1937 - Type 2 is an elevated token with no privileges removed or groups disabled. An elevated token is used when User Account Control is enabled and the user chooses to start the program using Run as administrator. An elevated token is also used when an application is configured to always require administrative privilege or to always require maximum privilege, and the user is a member of the Administrators group.
  • %% 1938 - Type 3 is the normal value when UAC is enabled and a user simply starts a program from the Start Menu. It's a limited token with administrative privileges removed and administrative groups disabled. The limited token is used when User Account Control is enabled, the application does not require administrative privilege, and the user does not choose to start the program using Run as administrator.

Grtz

As a workaround, I'm thinking you might be able to use either the Dissect processor or the script processor to extract the name of the process like you want it. You can use those in the Winlogbeat config.

@tudor Thanks for the tip. Tried using dissect to get the filename, but failed for now.

- dissect:
when:
  equals:
    event.code: 4688
tokenizer: "%{myprocess.path}\\%{myprocess.file}"
field: "winlog.event_data.NewProcessName"

How can I configure the tokenizer so it saves the path untill the last '\'?

Result now:

image

Please note that I don't know beforehand from what path commands will be executed.. So there could be x number of subfolders...

Also tried with:

tokenizer: "%{myprocess.path}\\%{myprocess.file/1}.%{myprocess.file/2}"

WHich is able to extract . extension somehow , but the moment there is a '.' in the path, it fails again (such as C:\Users\mysuer\AppData\Local\GitHubDesktop\app-2.2.1\GitHubDesktop.exe)

image

Starting to doubt this is even possible with dissect, I tried updating the security js file to this:

var securitycommand = (function () {
    var path = require("path");
    var processor = require("processor");
    var winlogbeat = require("winlogbeat");

    var addAuthSuccess = new processor.AddFields({
        fields: {
            "event.category": "authentication",
            "event.type": "authentication_success",
        },
        target: "",
    });

    var addAuthFailed = new processor.AddFields({
        fields: {
            "event.category": "authentication",
            "event.type": "authentication_failure",
        },
        target: "",
    });

    var convertAuthentication = new processor.Convert({
        fields: [
            {from: "winlog.event_data.TargetUserSid", to: "user.id"},
            {from: "winlog.event_data.TargetUserName", to: "user.name"},
            {from: "winlog.event_data.TargetDomainName", to: "user.domain"},
            {from: "winlog.event_data.ProcessId", to: "process.pid", type: "long"},
            {from: "winlog.event_data.ProcessName", to: "process.executable"},
            {from: "winlog.event_data.IpAddress", to: "source.ip", type: "ip"},
            {from: "winlog.event_data.IpPort", to: "source.port", type: "long"},
            {from: "winlog.event_data.WorkstationName", to: "source.domain"},
        ],
        mode: "rename",
        ignore_missing: true,
        fail_on_error: false,
    });

    var setProcessNameUsingExe = function(evt) {
        var name = evt.Get("process.name");
        if (name) {
            return;
        }
        var exe = evt.Get("process.executable");
        evt.Put("process.name", path.basename(exe));
    };

    var setProcessNameUsingNewProcessName = function(evt) {
        var processnamefull = evt.Get("winlog.event_data.NewProcessName");
        evt.Put("process.name", path.basename(processnamefull));
    };

    var logonSuccess = new processor.Chain()
        .Add(addAuthSuccess)
        .Add(convertAuthentication)
        .Add(setProcessNameUsingExe)
        .Build();

    var logonFailed = new processor.Chain()
        .Add(addAuthFailed)
        .Add(convertAuthentication)
        .Add(setProcessNameUsingExe)
        .Build();

    var processCreated = new processor.Chain()
        .Add(setProcessNameUsingNewProcessName)
        .Build();
		
    return {
        // 4624 - An account was successfully logged on.
        4624: logonSuccess.Run,

        // 4625 - An account failed to log on.
        4625: logonFailed.Run,

        // 4648 - A logon was attempted using explicit credentials.
        4648: logonSuccess.Run,
		
		// 4688 - A process was created
		4688: processCreated.Run,

        process: function(evt) {
            var event_id = evt.Get("winlog.event_id");
            var processor = this[event_id];
            if (processor === undefined) {
                return;
            }
            processor(evt);
        },
    };
})();

function process(evt) {
    return securitycommand.process(evt);
}

But the result is that process.name seems to be null all the time..

Any advice to get this working is welcome. :slight_smile:

Tx

Willem

Seems I gave up too early.. The events are forwarded with WEF and have some latency..

So this works perfectly:

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

var securitycommand = (function () {
    var path = require("path");
    var processor = require("processor");
    var winlogbeat = require("winlogbeat");

    var addAuthSuccess = new processor.AddFields({
        fields: {
            "event.category": "authentication",
            "event.type": "authentication_success",
        },
        target: "",
    });

    var addAuthFailed = new processor.AddFields({
        fields: {
            "event.category": "authentication",
            "event.type": "authentication_failure",
        },
        target: "",
    });

    var convertAuthentication = new processor.Convert({
        fields: [
            {from: "winlog.event_data.TargetUserSid", to: "user.id"},
            {from: "winlog.event_data.TargetUserName", to: "user.name"},
            {from: "winlog.event_data.TargetDomainName", to: "user.domain"},
            {from: "winlog.event_data.ProcessId", to: "process.pid", type: "long"},
            {from: "winlog.event_data.ProcessName", to: "process.executable"},
            {from: "winlog.event_data.IpAddress", to: "source.ip", type: "ip"},
            {from: "winlog.event_data.IpPort", to: "source.port", type: "long"},
            {from: "winlog.event_data.WorkstationName", to: "source.domain"},
        ],
        mode: "rename",
        ignore_missing: true,
        fail_on_error: false,
    });

    var setProcessNameUsingExe = function(evt) {
        var name = evt.Get("process.name");
        if (name) {
            return;
        }
        var exe = evt.Get("process.executable");
        evt.Put("process.name", path.basename(exe));
    };

    var setProcessNameUsingNewProcessName = function(evt) {
        var processnamefull = evt.Get("winlog.event_data.NewProcessName");
        evt.Put("process.name", path.basename(processnamefull));
    };

    var logonSuccess = new processor.Chain()
        .Add(addAuthSuccess)
        .Add(convertAuthentication)
        .Add(setProcessNameUsingExe)
        .Build();

    var logonFailed = new processor.Chain()
        .Add(addAuthFailed)
        .Add(convertAuthentication)
        .Add(setProcessNameUsingExe)
        .Build();

    var processCreated = new processor.Chain()
        .Add(setProcessNameUsingNewProcessName)
        .Build();
		
    return {
        // 4624 - An account was successfully logged on.
        4624: logonSuccess.Run,

        // 4625 - An account failed to log on.
        4625: logonFailed.Run,

        // 4648 - A logon was attempted using explicit credentials.
        4648: logonSuccess.Run,
		
		// 4688 - A process was created
		4688: processCreated.Run,

        process: function(evt) {
            var event_id = evt.Get("winlog.event_id");
            var processor = this[event_id];
            if (processor === undefined) {
                return;
            }
            processor(evt);
        },
    };
})();

function process(evt) {
    return securitycommand.process(evt);
}

Result:

Is this something that could be added to the security module? Imho knowing what commands are launched in your network is very useful for SIEM. Let me know if I should make a feature request?

Grtz

Willem

4688 and 4689 would be excellent additions to the Security module. And what you're attempting should be possible with the path module via basename(path).

The module already has some definitions for the token elevation codes. You might just need to do a lookup in the module to use them. https://github.com/elastic/beats/blob/93d32099b83b86f1208ca10ad3d4563eafa82a36/x-pack/winlogbeat/module/security/config/winlogbeat-security.js#L173-L175

Yeah, please open up a pull request and we can discuss the changes a bit more.

And regarding WEF there's a known issue with the host.name value is the value of the forwarder and not the machine that produced the event. https://github.com/elastic/beats/issues/13920 As a work-around we have been copying the computer_name field over to the host.name for the ForwardedEvents channel via processor config.

@andrewkroh Check my latest response, I just needed a little patience. Should I make a feature request in the Beats repo to add 4688 and 4689 to the security module?

@andrewkroh Seems we crossed paths again... :slight_smile:

I'll start with opening an enhancement request. Afaik you guys are already working on enhancing the security module, see https://github.com/elastic/beats/pull/13530

Not sure yet how my changes should fit into the full story atm..

Your changes should fit in well. You might want to wait until we hit merge on that PR just so that we don't run into a bunch of merge conflicts.

@andrewkroh For now I created https://github.com/elastic/beats/issues/14038

Once PR 13530 has been done, I'll see if I can create a PR if I find some spare time