// question

On a Linux host, review account management activity in Syslog or process event logs to pinpoint which command was executed to create a new local user. What was the name of the tool invoked?

Reference KQL answer and model-by-model comparison.

02a91c34-8a5b-4bed-87af-501103eb5357
question id
17/23
models correct
1
valid answer
74%
success rate
// reference answer

Human-validated correct answer for this question.

useradd
// model comparison
ModelAnswerKQL queryStatusTriesTimeCost
gemini-2.5-flash-preview-04-17
no answer
no query generatedfail50.00s0.0394
gpt-35-turbo
useradd
DeviceProcessEvents
| where ActionType == 'ProcessCreated' and ProcessCommandLine contains 'useradd'
| project ProcessCommandLine
pass14.49s0.0033
gpt-4-turbo-2024-04-09
no answer
no query generatedfail50.00s0.2262
gpt-4.1
useradd
DeviceProcessEvents
| where FileName in~ ("useradd", "adduser", "usermod")
    or ProcessCommandLine has_any ("useradd", "adduser", "usermod")
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine
pass15.22s0.0200
gpt-4.1-finetuned
useradd
DeviceProcessEvents 
| where ProcessCommandLine contains "useradd" 
   or ProcessCommandLine contains "userdel" 
   or ProcessCommandLine contains "usermod" 
| summarize arglist = make_set(ProcessCommandLine) by FileName, DeviceName
| extend timestamp = now(), HostName = DeviceName, ProcessName = FileName
pass214.63s0.0161
gpt-4.1-mini
useradd
DeviceProcessEvents
| where InitiatingProcessCommandLine has_any ("useradd", "adduser", "usermod")
| project TimeGenerated, DeviceName, InitiatingProcessCommandLine, InitiatingProcessFileName
| sort by TimeGenerated desc
pass26.50s0.0063
gpt-4.1-nano
useradd
DeviceProcessEvents
| where FileName in ('bash', 'sh', 'sudo', 'zsh') and ProcessCommandLine has_any ('useradd', 'adduser', 'addgroup', 'groupadd')
| project TimeGenerated, DeviceName, InitiatingProcessCommandLine, InitiatingProcessFileName, InitiatingProcessAccountName
pass13.27s0.0007
gpt-4o
no answer
no query generatedfail50.00s0.0641
gpt-5-high
useradd
let LinuxDevices = DeviceInfo
| where OSPlatform =~ "Linux"
| project DeviceId, DeviceName;
// Process executions likely used to create a local user
let FromProcess = DeviceProcessEvents
| join kind=inner LinuxDevices on DeviceId
| where tolower(ProcessCommandLine) has_any ("useradd", "adduser") or FileName in~ ("useradd", "adduser")
| extend ToolVerb = tolower(extract(@"\b(adduser|useradd)\b", 1, ProcessCommandLine))
| extend FirstTokenPath = extract(@"(?:^|\s)(?:sudo\s+)?(?<tool>[\w\-/\.]+)", 1, ProcessCommandLine)
| extend FirstToken = tolower(extract(@"([^/\s]+)$", 1, FirstTokenPath))
| extend ToolInvoked = coalesce(ToolVerb, FirstToken, tolower(FileName))
| project TimeGenerated, DeviceId, DeviceName, AccountName, ToolInvoked, ProcessCommandLine, InitiatingProcessParentFileName, InitiatingProcessCommandLine;
// File modifications that happen when a user is created; pivot back to the initiating process (and its command line)
let FromFileMod = DeviceFileEvents
| join kind=inner LinuxDevices on DeviceId
| where FolderPath == "/etc" and FileName in~ ("passwd", "shadow", "gshadow", "group")
| where ActionType in~ ("FileModified", "FileCreated", "FileRenamed")
| extend ToolVerb = tolower(extract(@"\b(adduser|useradd)\b", 1, InitiatingProcessCommandLine))
| extend FirstTokenPath = extract(@"(?:^|\s)(?:sudo\s+)?(?<tool>[\w\-/\.]+)", 1, InitiatingProcessCommandLine)
| extend FirstToken = tolower(extract(@"([^/\s]+)$", 1, FirstTokenPath))
| extend ToolInvoked = coalesce(ToolVerb, FirstToken, tolower(InitiatingProcessFileName))
| project TimeGenerated, DeviceId, DeviceName, AccountName=InitiatingProcessAccountName, ToolInvoked, ProcessCommandLine=InitiatingProcessCommandLine, InitiatingProcessParentFileName, InitiatingProcessCommandLine;
union FromProcess, FromFileMod
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), SampleCommandline=any(ProcessCommandLine) by DeviceId, DeviceName, ToolInvoked
| order by LastSeen desc
pass167.91s0.0627
gpt-5-mini-high
no answer
no query generatedfail50.00s0.0191
gpt-5-mini-low
useradd
let userPattern = "(?i)\\b(useradd|adduser|passwd|usermod|groupadd|newusers|addgroup|userdel|chpasswd|pw|adduser\\.sh)\\b";
let fileHint1 = "/etc/passwd";
let fileHint2 = "/etc/shadow";
let fileHint3 = "/etc/group";
let fileHint4 = "/home/";

let procMatches = DeviceProcessEvents
| where isnotempty(ProcessCommandLine) or isnotempty(InitiatingProcessCommandLine) or isnotempty(InitiatingProcessFileName)
| extend TriggerCommand = coalesce(ProcessCommandLine, InitiatingProcessCommandLine, InitiatingProcessFileName)
// look for known user-management commands or obvious file edits
| where TriggerCommand matches regex userPattern
    or tostring(InitiatingProcessFileName) matches regex userPattern
    or TriggerCommand contains fileHint1 or TriggerCommand contains fileHint2 or TriggerCommand contains fileHint3 or TriggerCommand contains fileHint4
| extend MatchedKeywords = extract_all(@"(?i)\\b(useradd|adduser|passwd|usermod|groupadd|newusers|addgroup|userdel|chpasswd|pw|adduser\\.sh)\\b", tostring(TriggerCommand))
| extend Hints = pack_array(
    iff(TriggerCommand contains fileHint1, fileHint1, ""),
    iff(TriggerCommand contains fileHint2, fileHint2, ""),
    iff(TriggerCommand contains fileHint3, fileHint3, ""),
    iff(TriggerCommand contains fileHint4, fileHint4, "")
  )
| extend RawTool = coalesce(InitiatingProcessFileName, FileName, tostring(split(TriggerCommand, " ")[0]))
| extend ToolInvoked = tolower(extract(@"([^/\\]+)$", 1, RawTool))
| project TimeGenerated, DeviceName, AccountName, AccountDomain, ToolInvoked, TriggerCommand, MatchedKeywords, Hints, ProcessId, InitiatingProcessId, InitiatingProcessAccountName, InitiatingProcessAccountDomain;

let syslogMatches = DeviceEvents
| where isnotempty(AdditionalFields) or isnotempty(ProcessCommandLine) or FileName contains "syslog" or FolderPath contains "syslog" or FileName contains "message" or FileName contains "messages"
| extend AF = tostring(AdditionalFields)
| extend TriggerCommand = coalesce(ProcessCommandLine, AF)
| where TriggerCommand matches regex userPattern or AF matches regex userPattern or TriggerCommand contains fileHint1 or TriggerCommand contains fileHint2 or TriggerCommand contains fileHint3 or TriggerCommand contains fileHint4
| extend MatchedKeywords = extract_all(@"(?i)\\b(useradd|adduser|passwd|usermod|groupadd|newusers|addgroup|userdel|chpasswd|pw|adduser\\.sh)\\b", tostring(TriggerCommand))
| extend Hints = pack_array(
    iff(TriggerCommand contains fileHint1, fileHint1, ""),
    iff(TriggerCommand contains fileHint2, fileHint2, ""),
    iff(TriggerCommand contains fileHint3, fileHint3, ""),
    iff(TriggerCommand contains fileHint4, fileHint4, "")
  )
| extend RawTool = coalesce(InitiatingProcessFileName, FileName, tostring(split(TriggerCommand, " ")[0]))
| extend ToolInvoked = tolower(extract(@"([^/\\\s]+)$", 1, RawTool))
| project TimeGenerated, DeviceName, AccountName, AccountDomain, ToolInvoked, TriggerCommand, MatchedKeywords, Hints, ProcessId = InitiatingProcessId, InitiatingProcessAccountName, InitiatingProcessAccountDomain;

union isfuzzy=true procMatches, syslogMatches
| where array_length(MatchedKeywords) > 0 or array_length(Hints) > 0
| extend ToolInvoked = iff(isempty(ToolInvoked) or ToolInvoked == "", tolower(extract(@"^([^\s/\\]+)", 1, tostring(TriggerCommand))), ToolInvoked)
| summarize Count = count(), Earliest = min(TimeGenerated), Latest = max(TimeGenerated), AnyCommand = make_list(TriggerCommand, 5), Devices = make_set(DeviceName), Accounts = make_set(AccountName), InitiatingAccounts = make_set(InitiatingProcessAccountName) by ToolInvoked
| order by Earliest desc
| project ToolInvoked, Count, Earliest, Latest, Devices, Accounts, InitiatingAccounts, AnyCommand
pass468.30s0.0200
gpt-5-mini-medium
no answer
no query generatedfail50.00s0.0249
gpt-5-nano-high
useradd
let LinuxAccountCreateCommands = materialize(
(
DeviceEvents
| where TimeGenerated >= ago(1d)
| where InitiatingProcessCommandLine has_any ("useradd","adduser","/usr/sbin/useradd","/usr/bin/useradd","/usr/sbin/adduser","/usr/bin/adduser")
| extend ToolInvoked = coalesce(InitiatingProcessFileName, "Unknown")
| project TimeGenerated, DeviceName, AccountName, ToolInvoked, InitiatingProcessCommandLine, InitiatingProcessFileName
)
);
let LinuxProcEvents = materialize(
(
DeviceProcessEvents
| where TimeGenerated >= ago(1d)
| where InitiatingProcessCommandLine has_any ("useradd","adduser","/usr/sbin/useradd","/usr/bin/useradd","/usr/sbin/adduser","/usr/bin/adduser")
| extend ToolInvoked = coalesce(InitiatingProcessFileName, "Unknown")
| project TimeGenerated, DeviceName, AccountName = InitiatingProcessAccountName, ToolInvoked, InitiatingProcessCommandLine
)
);
LinuxAccountCreateCommands
| union (LinuxProcEvents)
| sort by TimeGenerated desc
| take 200
pass133.06s0.0024
gpt-5-nano-medium
useradd
union DeviceProcessEvents, DeviceEvents
| extend CmdLine = tolower(coalesce(InitiatingProcessCommandLine, ProcessCommandLine, ""))
| where CmdLine contains "useradd" or CmdLine contains "adduser" or InitiatingProcessFileName in ("useradd", "adduser")
| extend ToolName = case(CmdLine contains "useradd", "useradd", CmdLine contains "adduser", "adduser", "unknown")
| extend Username = tostring(extract(@"(?:^|\\s)(?:useradd|adduser)(?:\\s+[^-\\s]+)*\\s+([^\\s]+)", 1, CmdLine))
| project TimeGenerated, DeviceName, DeviceId, ToolName, Username, InitiatingProcessFileName, InitiatingProcessCommandLine, ProcessCommandLine
| sort by TimeGenerated desc
| take 100
pass277.19s0.0045
grok-3-beta
useradd
DeviceProcessEvents
| where ProcessCommandLine has_any ("useradd", "adduser")
| project DeviceName, ProcessCommandLine, InitiatingProcessFileName, ProcessCreationTime, AccountName
| order by ProcessCreationTime desc
pass19.23s0.0305
grok-3-mini-beta
useradd
let linuxDevices = DeviceInfo | where OSPlatform contains "Linux" | distinct DeviceId;
DeviceProcessEvents
| where DeviceId in (linuxDevices)
| where ProcessCommandLine contains "useradd" or ProcessCommandLine contains "adduser"
| project Timestamp, DeviceName, ProcessCommandLine, ToolName=FileName
| order by Timestamp desc
| limit 10
pass113.30s0.0018
o1-high
useradd
let LinuxDevices = DeviceInfo
| where OSPlatform == "Linux"
| distinct DeviceId;
DeviceProcessEvents
| where DeviceId in (LinuxDevices)
| where ActionType == "ProcessCreated"
| where isnotempty(ProcessCommandLine)
| where ProcessCommandLine has_any ("useradd", "adduser")
| project TimeGenerated, DeviceName, FileName, ProcessCommandLine, AccountDomain, AccountName, InitiatingProcessAccountName
pass123.92s0.2374
o1-low
useradd
DeviceProcessEvents
| where ActionType == "ProcessCreated"
| where FileName in~ ("useradd", "adduser")
| join kind=inner (DeviceInfo
    | where OSPlatform == "Linux"
) on DeviceId
| project TimeGenerated, DeviceName, FileName, FolderPath, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by TimeGenerated desc
pass237.90s0.3520
o3-high
useradd
// Time filter will be injected by the portal/time picker
let linuxDevices = DeviceInfo
    | where OSPlatform =~ "Linux"
    | distinct DeviceId;
DeviceProcessEvents
| where DeviceId in (linuxDevices)
// Typical Linux utilities used to add or modify local users
| where FileName in~ ("useradd", "adduser", "newusers", "usermod", "chpasswd")
      or ProcessCommandLine matches regex @"\b(useradd|adduser|newusers|usermod|chpasswd)\b"
| project TimeGenerated,
          DeviceName,
          CreatedByAccount = tostring(InitiatingProcessAccountName),
          ToolInvoked = tostring(FileName),
          CommandLine   = ProcessCommandLine
| summarize Executions = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by ToolInvoked
| order by Executions desc
pass134.86s0.0238
o3-mini-high
useradd
union DeviceEvents, DeviceProcessEvents
| where isnotempty(InitiatingProcessCommandLine)
| where InitiatingProcessCommandLine has_any ("useradd", "adduser")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine
| summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by DeviceName, InitiatingProcessFileName
| sort by FirstSeen desc
pass113.98s0.0091
o3-mini-low
useradd
DeviceProcessEvents
| where InitiatingProcessCommandLine has "useradd" or InitiatingProcessCommandLine has "adduser"
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine
| sort by TimeGenerated desc
pass113.20s0.0135
o4-mini-high
no answer
no query generatedfail50.00s0.0590
o4-mini-low
useradd
// Identify syslog entries where a user‐add command was recorded
let SyslogUserAdd = DeviceEvents
| where SourceSystem == "Syslog"
| where AdditionalFields.syslog_message has_any ("useradd","adduser")
| extend Program = tostring(AdditionalFields.syslog_program),
         Command = tostring(AdditionalFields.syslog_message)
| project TimeGenerated, DeviceName, Program, Command;

// Identify kernel‐level process events where the user‐add binary was executed
let ProcUserAdd = DeviceProcessEvents
| where FileName has_any ("useradd","adduser")
| extend Program = FileName,
         Command = ProcessCommandLine
| project TimeGenerated, DeviceName, Program, Command;

// Combine and display
SyslogUserAdd
| union ProcUserAdd
| sort by TimeGenerated desc
pass127.61s0.0122