Splunk Search

How to make multivalue comparisons against multivalue fields without mvexpand?

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=$ foldername!=null
| lookup lookup
wildfolder folderlookup AS foldername, server AS host OUTPUT grouplookup userlookup fileexceptions
| eval user=if(isnull(ObjectName),null,user)
| eval user=if(match(Object
Name,folderlookup),null,user)
| eval user=if(match(user
lookup,user),null,user)
| eval user=if(match(ObjectName,fileexceptions),null,user)
| table user host folderlookup foldername ObjectName Message grouplookup
| ldapfilter domain=default search="(sAMAccountName=$user$)" attrs="memberOf"
| rex field=memberOf "^CN=(?[^,]+)"
| eval allowedgroups=split(grouplookup,"::")
| *
eval user=if(match(groupname,allowedgroups),null,user)**
| search user!=null
| table user host ObjectName allowedgroups group_name

lookupwildfolder CSV structure
server,folderlookup,folderout,grouplookup,userlookup,file_exceptions
SERVERNAME,D:\Docstore*,D:\Docstore,GROUP1::Group2,::USER1::USER2::USER3::,

0 Karma
1 Solution

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 groupname and allowedgroups mv fields and removed duplicates from merged field.
b) If there is no common group between groupname (user's groups) and allowedgroups, then no of values in temp will exactly as sum of no of values in each of groupname and allowedgroups field. So, unauthorized access.

View solution in original post

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 groupname and allowedgroups mv fields and removed duplicates from merged field.
b) If there is no common group between groupname (user's groups) and allowedgroups, then no of values in temp will exactly as sum of no of values in each of groupname and allowedgroups field. So, unauthorized access.

View solution in original post

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

SplunkTrust
SplunkTrust

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

0 Karma

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

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

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 lookupwildfolder CSV structure is still the same:
server,folderlookup,folderout,grouplookup,userlookup,file_exceptions
SERVERNAME,D:Docstore*,D:Docstore,GROUP1::Group2,::USER1::USER2::USER3::,