From 055bb008830c93524ff09e73fa0e742b49ca2b5f Mon Sep 17 00:00:00 2001 From: Wes Moskal-Fitzpatrick Date: Fri, 10 May 2019 13:03:11 +0100 Subject: [PATCH] Version 1.2 * Added new pattern for WSL Discovery * Fixed bugs with TSAK * Added DeviceGuard and Local Groups discovery to TSAK --- tsak_license.tpl => traversys_License.tpl | 9 +- traversys_TSAK.tpl | 245 ++++++++++++++++++++++ traversys_WSL.tpl | 123 +++++++++++ tsak.tpl | 222 -------------------- 4 files changed, 373 insertions(+), 226 deletions(-) rename tsak_license.tpl => traversys_License.tpl (99%) create mode 100644 traversys_TSAK.tpl create mode 100644 traversys_WSL.tpl delete mode 100644 tsak.tpl diff --git a/tsak_license.tpl b/traversys_License.tpl similarity index 99% rename from tsak_license.tpl rename to traversys_License.tpl index 59245ee..4fa1217 100644 --- a/tsak_license.tpl +++ b/traversys_License.tpl @@ -3,9 +3,10 @@ tpl 1.15 module TSAKLicense; metadata - __name:='Traversys Swiss Army Knife (TSAK)'; + __name :='Traversys License'; + origin :='Traversys'; description:='GPL 3.0 License File'; - tree_path:='Traversys', 'Extensions', 'TSAK GPL License'; + tree_path :='Traversys', 'Extensions', 'TSAK GPL License'; end metadata; configuration gpl_license 1.0 @@ -688,5 +689,5 @@ configuration gpl_license 1.0 """ "Accept License" accept_gpl := true; - -end configuration; \ No newline at end of file + +end configuration; diff --git a/traversys_TSAK.tpl b/traversys_TSAK.tpl new file mode 100644 index 0000000..29d0ff9 --- /dev/null +++ b/traversys_TSAK.tpl @@ -0,0 +1,245 @@ +// (C) 2019 Traversys Limited +// Licensed under GPL-3.0-or-later + +tpl 1.15 module TSAK; + +metadata + __name :='TPL Swiss Army Knife (TSAK)'; + origin :='Traversys'; + description:='Lots of additional discovery'; + tree_path :='Traversys', 'Extensions', 'TSAK'; +end metadata; + +from TSAKLicense import gpl_license 1.0; + +pattern TSAK_Host 1.2 + """ + Author: Wes Moskal-Fitzpatrick + + Get lots of useful additional data from Host systems. + + Change History: + 2019-04-23 1.0 WMF : Created. + 2019-04-24 1.1 WMF : Fixed ECA error caused by reg query failure with last_online. + Fixed dn.result > dn.value. + Updated Last Online to get registry key list. + Fixed anaconda file parse. + Added alternative commands for Linux DNS and Host uptime. + Added License File. + 2019-05-08 1.2 WMF : Local Groups and Members powershell command. + Fixed uptime typo. + Added DeviceGuard policy capture. + + Troubleshooting Windows commands (remquery): + 1) Run cmd as Administrator + 2) psexec \\localhost -i -u "NT AUTHORITY\SYSTEM" cmd + + """ + overview + tags traversys, tsak; + end overview; + + triggers + on h:= Host created, confirmed; + end triggers; + + body + + if gpl_license.accept_gpl = false then + stop; + end if; + + if h.os_type = "Windows" then + + // Get Device LDAP Details + dn := discovery.registryKey(h, raw "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Distinguished-Name"); + if dn and dn.value then + h.tsak_distinguished_name := dn.value; + end if; + + // Get Installation, Last Boot + boot := discovery.wmiQuery(h, 'SELECT InstallDate, LastBootUpTime FROM Win32_OperatingSystem', 'root\CIMV2'); + if boot then + h.tsak_install_date := boot[0].InstallDate; + h.tsak_last_boot := boot[0].LastBootUpTime; + end if; + + // Get Build Date + build := discovery.runCommand(h, 'systeminfo | find /i "date"'); + if build and build.result then + h.tsak_build_date := build.result; + end if; + + // Get DNS Servers + dns := discovery.wmiQuery(h, 'SELECT DNSServerSearchOrder FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=1', 'root\CIMV2'); + if dns then + h.tsak_dns_servers := dns[0].DNSServerSearchOrder; + end if; + + // Get BIOS Version + bios := discovery.wmiQuery(h, 'SELECT SMBIOSBIOSVersion FROM Win32_BIOS', 'root\CIMV2'); + if bios then + h.tsak_bios_version := bios[0].SMBIOSBIOSVersion; + end if; + + // Windows System Info + sysinfo := discovery.runCommand(h, 'systeminfo | findstr /B /C:"OS Name" /C:"OS Version" /C:"OS Manufacturer"'); + if sysinfo and sysinfo.result then + h.tsak_sysinfo := sysinfo.result; + end if; + + // Alternative OS Lookup + productName := discovery.registryKey(h, raw "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName"); + releaseId := discovery.registryKey(h, raw "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ReleaseId"); + if productName and productName.value then + h.tsak_os := productName.value; + end if; + if releaseId and releaseId.value then + h.tsak_os_release := releaseId.value; + end if; + + // Last Patch Info + lastPatchPs := discovery.runCommand(h, + "powershell \"Get-HotFix | sort InstalledOn -Descending | select HotFixID, @{Name='Installed'; Expression={'{0:dd MMMM yyyy}' -f [datetime]$_.InstalledOn.Tostring()}} -First 1\"" + ); + lastPatchReg := discovery.registryKey(h, raw "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install\LastSuccessTime"); + lastOnlineList := discovery.listRegistry(h, raw "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\LastOnlineScanTimeForAppCategory"); + if lastPatchPs and lastPatchPs.result then + h.tsak_last_patch := lastPatchPs.result; + elif lastPatchReg and lastPatchReg.value then + h.tsak_last_patch := lastPatchReg.value; + elif lastOnlineList then + for key in lastOnlineList do + lastOnline := discovery.registryKey(h, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\LastOnlineScanTimeForAppCategory\\%key%"); + if lastOnline and lastOnline[0].value then + h.tsak_last_online := lastOnline[0].value; + end if; + end for; + end if; + + // Logged Users + users := discovery.wmiQuery(h, 'select LastLogon, Name, UserType from Win32_NetworkLoginProfile', 'root\CIMV2'); + loggedUsers := []; + for row in users do + user := "%row.Name%, %row.LastLogon%"; + list.append(loggedUsers, user); + end for; + h.tsak_logged_users := loggedUsers; + + // Registered Owner + regOwner := discovery.registryKey(h, raw "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\RegisteredOwner"); + if regOwner and regOwner.value then + h.tsak_registered_owner := regOwner.value; + end if; + + // Get Groups and Members + psGroups := discovery.runCommand(h, + //'powershell "foreach ($LocalGroup in Get-LocalGroup){ Get-LocalGroupMember $LocalGroup -ErrorAction SilentlyContinue | select $LocalGroup.name, Name | convertTo-Json}"' + //'powershell "Get-LocalGroup"' + //'powershell "Get-LocalGroup | foreach { $_.Name }"' + 'powershell "Get-LocalGroup | foreach { Get-LocalGroupMember $_.Name -ErrorAction SilentlyContinue | select $_.Name, Name | Write-Host }"' + ); + if psGroups and psGroups.result then + groups:= regex.extractAll(psGroups.result, regex "@\{(.*?)=;\sName=(.*?)}"); + log.debug("Groups = %groups%"); + h.tsak_group_membership:= groups; + end if; + + deviceGuard := discovery.runCommand(h, 'powershell "get-cipolicyinfo"'); + if deviceGuard and deviceGuard.result then + h.tsak_device_guard:= deviceGuard.result; + end if; + + else // Non-Windows + + // Get DNS Servers + dns := discovery.runCommand(h, "nmcli dev show | grep DNS"); + if dns and dns.result then + if dns.result matches "Error:" then + dns := discovery.runCommand(h, "cat /etc/resolv.conf | grep 'nameserver'"); + end if; + h.tsak_dns_servers := dns.result; + end if; + + // Get Host Uptime + up := discovery.runCommand(h, "uptime -p"); + if up and up.result matches "usage:" then + up := discovery.runCommand(h, "uptime"); + end if; + h.tsak_uptime := up.result; + + // Last Reboot + lastBoot := discovery.runCommand(h, "who -b"); + if lastBoot and lastBoot.result then + h.tsak_last_boot := lastBoot.result; + end if; + + // Get Build Date + build:= none; + anacondaFile := discovery.runCommand(h, 'ls -ld --time-style=long-iso /var/log/anaconda 2> /dev/null || ls -ld --time-style=long-iso /var/log/installer 2> /dev/null'); + if anacondaFile and anacondaFile.result then + buildDate:= regex.extract(anacondaFile.result, regex "(\d{4}-\d+-\d+\s\d+:\d+)", raw "\1", no_match:= anacondaFile.result); + h.tsak_build_date := buildDate; + end if; + if h.os_type matches "AIX" then + build := discovery.runCommand(h, 'lslpp -h | grep -p bos.rte'); + elif h.os_type matches "Solaris" then + build := discovery.runCommand(h, 'pkg info kernel'); + elif h.os_type matches "HP-UX" then + build := discovery.runCommand(h, '/opt/ignite/bin/print_manifest | more'); + end if; + if build and build.result then + h.tsak_build_date := build.result; + end if; + + end if; + + end body; + +end pattern; + + +pattern TSAK_UID 1.1 + """ + Author: Wes Moskal-Fitzpatrick + + Pattern for correcting usernames is set to UID. + + Change History: + 2019-04-23 1.0 WMF : Created. + 2019-04-24 1.0 WMF : Fixed ECA error for UID. + + """ + + overview + tags traversys, tsak; + end overview; + + triggers + on p:= DiscoveredProcess where username = none or username matches regex "^\d+$"; + end triggers; + + body + + if gpl_license.accept_gpl = false then + stop; + end if; + + if text.toNumber(p.username) = p.uid then + da := discovery.access(p); + if da.device_summary has subword "Windows" then + // Skip + stop; + else + pwd := discovery.fileGet(da, "/etc/passwd"); + if pwd and pwd.content then + rx := raw "(\w+):x:" + p.username + ":"; + uid := regex.extract(pwd.content, rx, raw "\1", no_match:= p.username); + p.tsak_username := uid; + end if; + end if; + end if; + + end body; + +end pattern; diff --git a/traversys_WSL.tpl b/traversys_WSL.tpl new file mode 100644 index 0000000..534a6a6 --- /dev/null +++ b/traversys_WSL.tpl @@ -0,0 +1,123 @@ +// (C) 2019 Traversys Limited +// Licensed under GPL-3.0-or-later + +tpl 1.15 module WSL; + +metadata + __name :='Windows Subsystem for Linux'; + origin :='Traversys'; + description:='Windows Subsystem for Linux'; + tree_path :='Traversys', 'Software', 'WSL'; +end metadata; + +from TSAKLicense import gpl_license 1.0; + +pattern WSL 1.0 + + """ + Author: Wes Moskal-Fitzpatrick + + Models the Windows Subsystem for Linux as a RuntimeEnviroment. + infers additional SoftwareInstance if the Linux Distro is running. + + WSL runs under user/administrator. Commands will not run as system user + so this is recognition only. + + Change History: + 2019-05-06 1.0 WMF : Created. + + """ + + overview + tags traversys, wsl, windows, subsystem, linux; + end overview; + + constants + type := "Windows Subsystem for Linux"; + ls_type := "Linux Kernel (WSL)"; + end constants; + + triggers + on p:= DiscoveredProcess where cmd matches windows_cmd "wslhost"; + end triggers; + + body + if gpl_license.accept_gpl = false then + stop; + end if; + + host := model.host(p); + instance:= regex.extract(p.args, regex "\{(\S+)\}", raw "\1"); + + rte := model.RuntimeEnvironment( + key := instance, + type := type, + name := "%type% on %host.name%", + instance := text.lower(instance), + _traversys:= true + ); + + childs:= discovery.descendents(p); + for child in childs do + inference.associate(rte, child); + end for; + + parent:= discovery.parent(p); + inference.associate(rte, parent); + linuxDistro:= discovery.parent(parent); + + if linuxDistro then + + path := linuxDistro.cmd; + rte.path := linuxDistro.cmd; + + kernel := none; + version := none; + prVersion := none; + distro := none; + + name := "%ls_type% on %host.name%"; + + + kernelBin := regex.extract(path, regex "\\.+\\(.+)\.exe$", raw "\1"); + if kernelBin then + log.debug("Kernal Binary: %kernelBin%"); + // This code won't work because WSL won't run as system user + kernelCmd := discovery.runCommand(host, '"%path%" -c uname -r'); + if kernelCmd and kernelCmd.result then + kernel := kernelCmd.result; + end if; + release := discovery.runCommand(host, '"%path%" -c cat /etc/*-release'); + if release and release.result then + version := regex.extract(release.result, regex 'VERSION="(.*)"', raw '\1'); + distro := regex.extract(release.result, regex 'PRETTY_NAME="(.*)"', raw '\1'); + if distro then + name:= "%distro% (WSL) on %host.name%"; + end if; + else + // Use the binary name as identifier + distro := kernelBin; + name:= "%ls_type% (%distro%) on %host.name%"; + end if; + end if; + + prVersion := regex.extract(version, regex '^(\d+(?:\.\d+)?)', raw '\1', no_match := version); + + si:= model.SoftwareInstance( + key := instance, + type := ls_type, + name := name, + instance := text.lower(instance), + kernel := kernel, + path := path, + distribution := distro, + version := version, + product_version := prVersion, + _traversys := true + ); + + end if; + + end body; + +end pattern; diff --git a/tsak.tpl b/tsak.tpl deleted file mode 100644 index 32738b4..0000000 --- a/tsak.tpl +++ /dev/null @@ -1,222 +0,0 @@ -// (C) 2019 Traversys Limited -// Licensed under GPL-3.0-or-later - -tpl 1.15 module TSAK; - -metadata - __name:='Traversys Swiss Army Knife (TSAK)'; - description:='Lots of additional discovery'; - tree_path:='Traversys', 'Extensions', 'TSAK'; -end metadata; - -from TSAKLicense import gpl_license 1.0; - -pattern TSAK_Host 1.1 - """ - Author: Wes Moskal-Fitzpatrick - - The Swiss Army Knife of additional discovery. - - Change History: - 2019-04-23 1.0 WMF : Created. - 2019-04-24 1.1 WMF : Fixed ECA error caused by reg query failure with last_online. - Fixed dn.result > dn.value. - Updated Last Online to get registry key list. - Fixed anaconda file parse. - Added alternative commands for Linux DNS and Host uptime. - Added License File. - - """ - - overview - tags traversys, tsak; - end overview; - - triggers - on h:= Host created, confirmed; - - end triggers; - - body - - if gpl_license.accept_gpl = false then - stop; - end if; - - if h.os_type = "Windows" then - - // Get Device LDAP Details - dn := discovery.registryKey(h, raw "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Distinguished-Name"); - if dn and dn.value then - h.tsak_distinguished_name := dn.value; - end if; - - // Get Installation, Last Boot - boot := discovery.wmiQuery(h, 'SELECT InstallDate, LastBootUpTime FROM Win32_OperatingSystem', 'root\CIMV2'); - if boot then - h.tsak_install_date := boot[0].InstallDate; - h.tsak_last_boot := boot[0].LastBootUpTime; - end if; - - // Get Build Date - build := discovery.runCommand(h, 'systeminfo | find /i "date"'); - if build and build.result then - h.tsak_build_date := build.result; - end if; - - // Get DNS Servers - dns := discovery.wmiQuery(h, 'SELECT DNSServerSearchOrder FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=1', 'root\CIMV2'); - if dns then - h.tsak_dns_servers := dns[0].DNSServerSearchOrder; - end if; - - // Get BIOS Version - bios := discovery.wmiQuery(h, 'SELECT SMBIOSBIOSVersion FROM Win32_BIOS', 'root\CIMV2'); - if bios then - h.tsak_bios_version := bios[0].SMBIOSBIOSVersion; - end if; - - // Windows System Info - sysinfo := discovery.runCommand(h, 'systeminfo | findstr /B /C:"OS Name" /C:"OS Version" /C:"OS Manufacturer"'); - if sysinfo and sysinfo.result then - h.tsak_sysinfo := sysinfo.result; - end if; - - // Alternative OS Lookup - productName := discovery.registryKey(h, raw "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName"); - releaseId := discovery.registryKey(h, raw "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ReleaseId"); - if productName and productName.value then - h.tsak_os := productName.value; - end if; - if releaseId and releaseId.value then - h.tsak_os_release := releaseId.value; - end if; - - // Last Patch Info - lastPatchPs := discovery.runCommand(h, - "powershell \"Get-HotFix | sort InstalledOn -Descending | select HotFixID, @{Name='Installed'; Expression={'{0:dd MMMM yyyy}' -f [datetime]$_.InstalledOn.Tostring()}} -First 1\"" - ); - lastPatchReg := discovery.registryKey(h, raw "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install\LastSuccessTime"); - lastOnlineList := discovery.listRegistry(h, raw "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\LastOnlineScanTimeForAppCategory"); - if lastPatchPs and lastPatchPs.result then - h.tsak_last_patch := lastPatchPs.result; - elif lastPatchReg and lastPatchReg.value then - h.tsak_last_patch := lastPatchReg.value; - elif lastOnlineList then - for key in lastOnlineList do - lastOnline := discovery.registryKey(h, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\LastOnlineScanTimeForAppCategory\\%key%"); - if lastOnline and lastOnline[0].value then - h.tsak_last_online := lastOnline[0].value; - end if; - end for; - end if; - - // Logged Users - users := discovery.wmiQuery(h, 'select LastLogon, Name, UserType from Win32_NetworkLoginProfile', 'root\CIMV2'); - loggedUsers := []; - for row in users do - user := "%row.Name%, %row.LastLogon%"; - list.append(loggedUsers, user); - end for; - h.tsak_logged_users := loggedUsers; - - // Registered Owner - regOwner := discovery.registryKey(h, raw "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\RegisteredOwner"); - if regOwner and regOwner.value then - h.tsak_registered_owner := regOwner.value; - end if; - - else // Non-Windows - - // Get DNS Servers - dns := discovery.runCommand(h, "nmcli dev show | grep DNS"); - if dns and dns.result then - if dns.result matches "Error:" then - dns := discovery.runCommand(h, "cat /etc/resolv.conf | grep 'nameserver'"); - end if; - h.tsak_dns_servers := dns.result; - end if; - - // Get Host Uptime - up := discovery.runCommand(h, "uptime -p"); - if up and up.result matches "usage:" then - if up.result matches "usage:" then - up := discovery.runCommand(h, "uptime"); - end if; - h.tsak_uptime := up.result; - end if; - - // Last Reboot - lastBoot := discovery.runCommand(h, "who -b"); - if lastBoot and lastBoot.result then - h.tsak_last_boot := lastBoot.result; - end if; - - // Get Build Date - anacondaFile := discovery.runCommand(h, 'ls -ld --time-style=long-iso /var/log/anaconda 2> /dev/null || ls -ld --time-style=long-iso /var/log/installer 2> /dev/null'); - if anacondaFile and anacondaFile.result then - buildDate:= regex.extract(anacondaFile.result, regex "(\d{4}-\d+-\d+\s\d+:\d+)", raw "\1", no_match:= anacondaFile.result); - h.tsak_build_date := buildDate; - end if; - if h.os_type matches "AIX" then - build := discovery.runCommand(h, 'lslpp -h | grep -p bos.rte'); - elif h.os_type matches "Solaris" then - build := discovery.runCommand(h, 'pkg info kernel'); - elif h.os_type matches "HP-UX" then - build := discovery.runCommand(h, '/opt/ignite/bin/print_manifest | more'); - end if; - if build and build.result then - h.tsak_build_date := build.result; - end if; - - end if; - - end body; - -end pattern; - -pattern TSAK_UID 1.1 - """ - Author: Wes Moskal-Fitzpatrick - - Pattern for correcting usernames is set to UID. - - Change History: - 2019-04-23 1.0 WMF : Created. - 2019-04-24 1.0 WMF : Fixed ECA error for UID. - - """ - - overview - tags traversys, tsak; - end overview; - - triggers - on p:= DiscoveredProcess where username = none or username matches regex "^\d+$"; - - end triggers; - - body - - if gpl_license.accept_gpl = false then - stop; - end if; - - if text.toNumber(p.username) = p.uid then - da := discovery.access(p); - if da.device_summary has subword "Windows" then - // Skip - stop; - else - pwd := discovery.fileGet(da, "/etc/passwd"); - if pwd and pwd.content then - rx := raw "(\w+):x:" + p.username + ":"; - uid := regex.extract(pwd.content, rx, raw "\1", no_match:= p.username); - p.tsak_username := uid; - end if; - end if; - end if; - - end body; - -end pattern; \ No newline at end of file