Splunk Search

How to make multivalue comparisons against multivalue fields without mvexpand?

pdoconnell
Path Finder

I have an alert designed to examine Windows event logs (event 560 or 4663) for file access by unauthorized users. The search works by slowly checking the file location, files that aren't examined, and then finally the user's group membership compared to that allowed for the file. The final comparison is where I have problems, as I need to look to see that at least one of the user's groups is in the list of groups allowed access to the file. Because of that, and how it is structured as a reductionary search, I can't explode using mvexpand, because anything remaining would be indicative of an alert, so there would have to be 100% match as opposed to the !=0% match I am looking for. That is why solutions such as those found at https://answers.splunk.com/answers/11287/comparing-multivalue-fields.html won't work. Right now the search below will always alert if a sensitive file is accessed, but it is not appropriately skipping users who have permission.

Search:
index=wineventlog sourcetype=WinEventLog:Security EventCode=560 OR EventCode=4663 OR EventCode=5145 NOT user=$ folder_name!=null
| lookup lookup_wild_folder folder_lookup AS folder_name, server AS host OUTPUT group_lookup user_lookup file_exceptions
| eval user=if(isnull(Object_Name),null,user)
| eval user=if(match(Object_Name,folder_lookup),null,user)
| eval user=if(match(user_lookup,user),null,user)
| eval user=if(match(Object_Name,file_exceptions),null,user)
| table user host folder_lookup folder_name Object_Name Message group_lookup
| ldapfilter domain=default search="(sAMAccountName=$user$)" attrs="memberOf"
| rex field=memberOf "^CN=(?[^,]+)"
| eval allowed_groups=split(group_lookup,"::")
| **eval user=if(match(group_name,allowed_groups),null,user)
*
| search user!=null
| table user host Object_Name allowed_groups group_name

lookup_wild_folder CSV structure
server,folder_lookup,folder_out,group_lookup,user_lookup,file_exceptions
SERVERNAME,D:\Docstore*,D:\Docstore,GROUP1::Group2,::USER1::USER2::USER3::,

0 Karma
1 Solution

somesoni2
SplunkTrust
SplunkTrust

Try something like this

Your current search till  | eval allowed_groups=split(group_lookup,"::")
| eval temp=mvdedup(mvappend(group_name,allowed_groups))
| eval user=if(mvcount(temp)=(mvcount(group_name) + mvcount(allowed_groups)),user,null)
| search user!=null
| table user host Object_Name allowed_groups group_name

Explanation:
a) Created a field temp in which I merged group_name and allowed_groups mv fields and removed duplicates from merged field.
b) If there is no common group between group_name (user's groups) and allowed_groups, then no of values in temp will exactly as sum of no of values in each of group_name and allowed_groups field. So, unauthorized access.

View solution in original post

somesoni2
SplunkTrust
SplunkTrust

Try something like this

Your current search till  | eval allowed_groups=split(group_lookup,"::")
| eval temp=mvdedup(mvappend(group_name,allowed_groups))
| eval user=if(mvcount(temp)=(mvcount(group_name) + mvcount(allowed_groups)),user,null)
| search user!=null
| table user host Object_Name allowed_groups group_name

Explanation:
a) Created a field temp in which I merged group_name and allowed_groups mv fields and removed duplicates from merged field.
b) If there is no common group between group_name (user's groups) and allowed_groups, then no of values in temp will exactly as sum of no of values in each of group_name and allowed_groups field. So, unauthorized access.

pdoconnell
Path Finder

Unfortunately this is still showing false positives. I had tried something similar to this on an initial version of the search but never was able to make it work.

0 Karma

somesoni2
SplunkTrust
SplunkTrust

Can you provide some sample data, which will available after | eval allowed_groups=split...?

0 Karma

pdoconnell
Path Finder

This sanitized event will be a false positive, triggering the alert when it should not:

03/01/2016 10:42:26 AM
LogName=Security
SourceName=Microsoft Windows security auditing.
EventCode=4663
EventType=0
Type=Information
ComputerName=SERVER.DOMAIN.COM
TaskCategory=File System
OpCode=Info
RecordNumber=100125574
Keywords=Audit Success
Message=An attempt was made to access an object.

Subject:
    Security ID:        DOMAIN\USERNAME
    Account Name:       USERNAME
    Account Domain:     DOMAIN
    Logon ID:       0xc93b0ff0

Object:
    Object Server:  Security
    Object Type:    File
    Object Name:    D:\FOLDER\PROTECTED.XLSX    
    Handle ID:  0x1418

Process Information:
    Process ID: 0x4
    Process Name:   

Access Request Information:
    Accesses:   ReadData (or ListDirectory)

    Access Mask:    0x1

It triggers with the following statistics:

user: USERNAME  
host: SERVER.DOMAIN.COM
Object_Name: D:\FOLDER\PROTECTED.XLSX   
allowed_groups: ALLOWED_GROUP1
ALLOWED_GROUP2
group_names: RANDOM_GROUP1
RANDOM_GROUP2
ALLOWED_GROUP2
RANDOM_GROUP3

You can see that the user is a member of ALLOWED_GROUP2, which is one of the two allowed groups to this file.

0 Karma

somesoni2
SplunkTrust
SplunkTrust

Can you also post what is the value of field temp after executing this. Since the check for alert is based on temp, value in this will help determine what's going on.

 | eval temp=mvdedup(mvappend(group_name,allowed_groups))
0 Karma

pdoconnell
Path Finder

Actually, after recopying your suggestion into the search, it appears to be working correctly. Its possible that I somehow botched the search syntax the first time. For the record, this is the search that is working now:

index=wineventlog sourcetype=WinEventLog:Security EventCode=560 OR EventCode=4663 OR EventCode=5145 NOT user=*$
| lookup lookup_wild_folder folder_lookup AS folder_name, server AS host OUTPUT group_lookup user_lookup file_exceptions
| eval user=if(isnull(Object_Name),null,user)
| eval user=if(match(Object_Name,folder_lookup),null,user)
| eval user=if(match(user_lookup,user),null,user)
| eval user=if(match(Object_Name,file_exceptions),null,user)
| table user host folder_lookup folder_name Object_Name Message group_lookup
| ldapfilter domain=default search="(sAMAccountName=$user$)" attrs="memberOf"
| rex field=memberOf "^CN=(?[^,]+)"
| eval allowed_groups=split(group_lookup,"::")
| eval temp=mvdedup(mvappend(group_name,allowed_groups))
| eval user=if(mvcount(temp)=(mvcount(group_name) + mvcount(allowed_groups)),user,null)
| search user!=null
| table user host Object_Name allowed_groups group_name temp

The lookup_wild_folder CSV structure is still the same:
server,folder_lookup,folder_out,group_lookup,user_lookup,file_exceptions
SERVERNAME,D:Docstore*,D:Docstore,GROUP1::Group2,::USER1::USER2::USER3::,

Get Updates on the Splunk Community!

Index This | I am a number, but when you add ‘G’ to me, I go away. What number am I?

March 2024 Edition Hayyy Splunk Education Enthusiasts and the Eternally Curious!  We’re back with another ...

What’s New in Splunk App for PCI Compliance 5.3.1?

The Splunk App for PCI Compliance allows customers to extend the power of their existing Splunk solution with ...

Extending Observability Content to Splunk Cloud

Register to join us !   In this Extending Observability Content to Splunk Cloud Tech Talk, you'll see how to ...