Do keep in mind, as I write these rules, this is basically from a learning perspective. Putting these into production, does not mean you will have the same detection. This is primarily because these values can be changed by the threat actor, thus resulting in an evasion of your IDS/IPS. This is being done primarily from my lab's perspective.
First verifying the version of Snort being used.
┌──(rootđź’€securitynik)-[~/snort-files/snort3-3.1.18.0/build] └─# snort --version ,,_ -*> Snort++ <*- o" )~ Version 3.1.18.0 '''' By Martin Roesch & The Snort Team http://snort.org/contact#team Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved. Copyright (C) 1998-2013 Sourcefire, Inc., et al. Using DAQ version 3.0.5 Using LuaJIT version 2.1.0-beta3 Using OpenSSL 1.1.1l 24 Aug 2021 Using libpcap version 1.10.1 (with TPACKET_V3) Using PCRE version 8.44 2020-02-12 Using ZLIB version 1.2.11 Using FlatBuffers 2.0.5 Using Hyperscan version 5.4.0 2021-12-11 Using LZMA version 5.2.5
Creating my configuration file (snort.lua) and our rule files (local.rules) to use for this post.
┌──(rootđź’€securitynik)-[~/packets] └─# touch snort.lua && touch local.rules
Testing the configuration and rule file along with some command line options, I will be using.
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A cmg --tweaks talos --pcap-show -k none -d -R local.rules -v -c snort.lua --daq pcap -T -------------------------------------------------- o")~ Snort++ 3.1.18.0 -------------------------------------------------- Loading snort.lua: hosts host_cache active packets decode so_proxy trace search_engine process network host_tracker output daq alerts Finished snort.lua: Loading rule args: Loading local.rules: Finished local.rules: Finished rule args: -------------------------------------------------- Network Policy : policy id 0 : snort.lua -------------------------------------------------- Inspection Policy : policy id 0 : snort.lua -------------------------------------------------- so_proxy: -------------------------------------------------- pcap DAQ configured to read-file. -------------------------------------------------- host_cache memcap: 8388608 bytes Snort successfully validated the configuration (with 0 warnings). o")~ Snort exiting
Ok rather than continuing to learn more about Snort, let's jump right into our configuration file and the rules. See the reference for one of my previous blogs on building Snort3. I will add comments to the snort.lua and local.rules to ensure we understand what my rules are doing. Additionally, I will be taking advantage of both the service rules as well as the traditional snort2 rules structure.
Somethings to keep in mind, when writing rules, focus on the vulnerability not the exploit. Focus on what you have control over, not what the attackers do. Focus on what is leaving your network more than what is entering your network.
Let's go!
Here is the custom snort.lua configuration file I am using.
-- take advantage of Snort3 defaults include '/usr/local/etc/snort/snort_defaults.lua' -- take advantage of snort default classifications classifications = default_classifications -- Tell Snort3 what is our protected network so it can monitor it. HOME_NET = [[10.0.0.110/32]] -- This represents every network other than our protected network EXTERNAL_NET = '!$HOME_NET' local_variables = { nets = { HOME_NET = HOME_NET, EXTERNAL_NET = EXTERNAL_NET, } } ips = { variables = local_variables } -- Be able to profile the activities, get statistics profiler = { } -- Enable the Stream Inspector stream = { } -- Reasemmebe TCP stream_ip = { policy = "windows"; } -- Reasemmebe TCP stream_tcp = { policy = "windows"; require_3whs = 300; track_only = false; } -- setup a binder wizard = default_wizard binder = { { when = { proto = 'tcp', ports = [[80 443]] }, use = { type = 'http_inspect' } }, { when = { service = 'http' }, use = { type = 'http_inspect' } }, { use = { type = 'wizard' } }, } -- Define the HTTP inspector http_inspect = { } event_filter = { -- alerts once per 60 seconds then ignore any additional events during the 60 seconds. { gid = 1, sid = 4000001, type = 'both', track = 'by_src', count = 1, seconds = 60}, { gid = 1, sid = 4000002, type = 'both', track = 'by_src', count = 1, seconds = 60}, { gid = 1, sid = 4000003, type = 'both', track = 'by_src', count = 1, seconds = 60}, { gid = 1, sid = 4000004, type = 'both', track = 'by_src', count = 1, seconds = 60}, }
Below represents the contents of my local.rules file after the first rule is developed.
alert tcp $HOME_NET any -> $EXTERNAL_NET 443 ( rem: "This rule is looking for Powershell Empire /news.php, hence the 'msg' option"; msg: "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected"; rem: "Looking for HTTP method GET. Using the Hex values instead of the string in the first 3 bytes of the payload"; http_method; content: "|47 45 54|", offset 0, depth 3; rem: "find the string /news.php in the path. "; http_uri: path; content: "/news.php", distance 0, within 9, nocase; rem: "Looking for HTTP Version 1.1"; http_version: request; content: "1.1"; rem: "Look in the HTTP header for Connection: Keep-Alive"; http_header: field Connection; content: "Keep-Alive", nocase; rem: "Also look for a cookie that ends with '=', suggesting base64 encoded content"; http_header: field Cookie; regex: "/.*=$/i"; classtype: malware-cnc; reference: url, http://www.securitynik.com; sid:4000001; rev: 10; )
When this rule is run against the pcap.
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A cmg --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 11/20-13:03:59.632367 [**] [1:4000001:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected" [**] [Classification: Known malware command and control traffic] [Priority: 1] {TCP} 10.0.0.110:1650 -> 10.0.0.107:443 http_inspect.http_method[3]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 47 45 54 GET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_version[8]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 48 54 54 50 2F 31 2E 31 HTTP/1.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_uri[9]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2F 6E 65 77 73 2E 70 68 70 /news.ph p - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_header[93]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 55 73 65 72 2D 41 67 65 6E 74 3A 20 73 65 63 75 User-Age nt: secu 72 69 74 79 6E 69 6B 2D 6C 61 75 6E 63 68 65 72 ritynik- launcher 2D 62 61 74 2D 55 73 65 72 2D 41 67 65 6E 74 0D -bat-Use r-Agent. 0A 48 6F 73 74 3A 20 31 30 2E 30 2E 30 2E 31 30 .Host: 1 0.0.0.10 37 3A 34 34 33 0D 0A 43 6F 6E 6E 65 63 74 69 6F 7:443..C onnectio 6E 3A 20 4B 65 65 70 2D 41 6C 69 76 65 n: Keep- Alive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_cookie[61]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 53 65 63 75 72 69 74 79 4E 69 6B 2D 48 54 54 50 Security Nik-HTTP 2D 4C 69 73 74 65 6E 65 72 2D 43 6F 6F 6B 69 65 -Listene r-Cookie 3D 5A 4F 6B 4D 77 38 59 6A 6B 57 32 34 4C 30 41 =ZOkMw8Y jkW24L0A 78 61 63 49 4C 65 38 65 72 4C 38 73 3D xacILe8e rL8s= - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Good stuff, looking at the summary.
rule profile (all, sorted by total_time) # gid sid rev checks matches alerts time (us) avg/check avg/match avg/non-match timeouts suspends = === === === ====== ======= ====== ========= ========= ========= ============= ======== ======== 1 14000001 10 12 3 3 556 46 145 13 0 0
We made progress there. Moving to the second rule within the local.rules file, looking for the POST. I will copy most of the content above while making this into a service rule. Additionally, I will look for data at 420 bytes within the body of the HTTP message.
alert http ( rem: "This rule is looking for Powershell Empire /news.php, hence the 'msg' option"; msg: "POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected"; rem: "Looking for HTTP method POST. Using the Hex values instead of the string in the first 3 bytes of the payload"; http_method; content: "|50 4F 53 54|", offset 0, depth 4; rem: "find the string /news.php in the path. "; http_uri: path; content: "/news.php", distance 0, within 9, nocase; rem: "Looking for HTTP Version 1.1"; http_version: request; content: "1.1"; rem: "Looking for content greater than 400 bytes in the body of the message"; http_client_body; isdataat: 420, relative; classtype: malware-cnc; reference: url, http://www.securitynik.com; sid:4000002; rev: 10; )
When executed, here is what I see.
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A cmg --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 11/20-13:04:02.701119 [**] [1:4000002:10] "POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected" [**] [Classification: Known malware command and control traffic] [Priority: 1 ] {TCP} 10.0.0.110:1650 -> 10.0.0.107:443 http_inspect.http_method[4]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 50 4F 53 54 POST - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_version[8]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 48 54 54 50 2F 31 2E 31 HTTP/1.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_uri[9]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2F 6E 65 77 73 2E 70 68 70 /news.ph p - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_header[90]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 55 73 65 72 2D 41 67 65 6E 74 3A 20 73 65 63 75 User-Age nt: secu 72 69 74 79 6E 69 6B 2D 6C 61 75 6E 63 68 65 72 ritynik- launcher 2D 62 61 74 2D 55 73 65 72 2D 41 67 65 6E 74 0D -bat-Use r-Agent. 0A 48 6F 73 74 3A 20 31 30 2E 30 2E 30 2E 31 30 .Host: 1 0.0.0.10 37 3A 34 34 33 0D 0A 43 6F 6E 74 65 6E 74 2D 4C 7:443..C ontent-L 65 6E 67 74 68 3A 20 34 36 32 ength: 4 62 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_client_body[462]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 8B 8D 84 5C 4A D7 63 63 B5 F8 11 9C 17 CE 19 A2 ...\J.cc ........ 4C 6F F2 79 7F CE BD 41 A3 F3 D5 DC AD BA AF 00 Lo.y...A ........ C1 CC 4E 71 AC C9 7D 56 D0 E7 CF 67 28 B8 62 0D ..Nq..}V ...g(.b. C2 3C 58 E7 38 68 84 38 7A C9 B7 0A 11 A8 1A FB .<X.8h.8 z....... 09 54 56 8E 4C 5F C8 6B 40 87 D2 94 4D C0 8E 76 .TV.L_.k @...M..v 86 4C B0 7D E0 AD 56 70 0E 69 D4 4F 34 07 62 EE .L.}..Vp .i.O4.b. D2 0B F4 30 BF 56 8F EA 1A 66 2E 77 9D BB 49 1F ...0.V.. .f.w..I. C0 1E AF 58 04 06 91 BD 34 4C 01 37 6E EC C1 2B ...X.... 4L.7n..+ CF 57 D3 9B 19 99 16 96 65 6C 09 8E 85 CD 5A B6 .W...... el....Z. 0B BE E7 36 38 9B DB 4B 7F B4 00 7C B9 D8 B8 38 ...68..K ...|...8 1E DA 91 C6 33 8C B7 29 7B A3 F9 79 33 A2 DE BB ....3..) {..y3... D5 AB 22 3E F3 9D 8E FA 47 CE E2 E0 BF 70 90 89 ..">.... G....p.. E4 1D B8 62 A2 2C F6 DB C1 90 3A 3C 78 59 4B 54 ...b.,.. ..:<xYKT 47 9B EC 15 9C CA C8 D8 C1 98 A8 37 7C 24 38 59 G....... ...7|$8Y E7 27 71 AC BC 87 A1 1F E4 00 96 F6 4C 90 3D 25 .'q..... ....L.=% 78 85 75 11 80 00 A1 AC 03 3C 4D 9D 09 75 8A 46 x.u..... .<M..u.F B8 54 85 86 2F D0 99 C8 F9 7A 5D 50 6F 61 D7 A7 .T../... .z]Poa.. 06 FF F6 70 9F AB 57 2C A1 BD CA B4 4F 10 B7 D1 ...p..W, ....O... E5 E6 F4 F1 63 C9 6D 6C F5 41 8F 31 3F 3B 90 3E ....c.ml .A.1?;.> 31 EE CA 64 2C 43 50 44 03 A3 51 2D 06 FD 74 49 1..d,CPD ..Q-..tI A4 68 12 10 4D FF 2E EB 36 3B 1A C7 D2 D9 B1 09 .h..M... 6;...... 60 07 30 BB 05 BC 11 B2 3A CB E1 7A 0E F9 72 F6 `.0..... :..z..r. 68 58 E4 B9 64 EB B4 D7 90 0D BD D9 72 A6 D1 A0 hX..d... ....r... 89 99 2D 15 8A A8 04 CB 7D 50 90 3B 4B AC 6F 41 ..-..... }P.;K.oA --More--
Going back to none service rules. Looking for /admin/get.php.
Here is the rule.
alert tcp $HOME_NET any -> $EXTERNAL_NET 443 ( rem: "This rule is looking for Powershell Empire /admin/get.php, hence the 'msg' option"; msg: "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected"; rem: "Looking for HTTP method GET. Using the Hex values instead of the string in the first 3 bytes of the payload"; http_method; content: "|47 45 54|", offset 0, depth 3; rem: "find the string /news.php in the path. "; http_uri: path; content: "/admin/get.php", distance 0, within 14, nocase; rem: "Looking for HTTP Version 1.1"; http_version: request; content: "1.1"; rem: "Also look for a cookie that ends with '=', suggesting base64 encoded content"; http_header: field Cookie; regex: "/.*=$/i"; classtype: malware-cnc; reference: url, http://www.securitynik.com; sid:4000003; rev: 10; )
Here is the result from that rule.
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A cmg --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 11/20-13:05:06.697538 [**] [1:4000003:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected" [**] [Classification: Known malware command and control traffic] [Priority : 1] {TCP} 10.0.0.110:1650 -> 10.0.0.107:443 http_inspect.http_method[3]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 47 45 54 GET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_version[8]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 48 54 54 50 2F 31 2E 31 HTTP/1.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_uri[14]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2F 61 64 6D 69 6E 2F 67 65 74 2E 70 68 70 /admin/g et.php - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_header[102]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 55 73 65 72 2D 41 67 65 6E 74 3A 20 4D 6F 7A 69 User-Age nt: Mozi 6C 6C 61 2F 35 2E 30 20 28 57 69 6E 64 6F 77 73 lla/5.0 (Windows 20 4E 54 20 36 2E 31 3B 20 57 4F 57 36 34 3B 20 NT 6.1; WOW64; 54 72 69 64 65 6E 74 2F 37 2E 30 3B 20 72 76 3A Trident/ 7.0; rv: 31 31 2E 30 29 20 6C 69 6B 65 20 47 65 63 6B 6F 11.0) li ke Gecko 0D 0A 48 6F 73 74 3A 20 31 30 2E 30 2E 30 2E 31 ..Host: 10.0.0.1 30 37 3A 34 34 33 07:443 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_cookie[36]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 73 65 73 73 69 6F 6E 3D 44 72 70 30 53 78 70 6B session= Drp0Sxpk 54 54 4A 32 62 4B 71 57 30 7A 7A 50 55 56 56 31 TTJ2bKqW 0zzPUVV1 67 32 59 3D g2Y= - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
... and the final rule, looking for /login/process.php.
alert tcp $HOME_NET any -> $EXTERNAL_NET 443 ( rem: "This rule is looking for Powershell Empire /login/process.php, hence the 'msg' option"; msg: "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected"; rem: "Looking for HTTP method GET. Using the Hex values instead of the string in the first 3 bytes of the payload"; http_method; content: "|47 45 54|", offset 0, depth 3; rem: "find the string /login/process.php in the path. "; http_uri: path; content: "/login/process.php", distance 0, within 18, nocase; rem: "Looking for HTTP Version 1.1"; http_version: request; content: "1.1"; rem: "Also look for a cookie that ends with '=', suggesting base64 encoded content"; http_header: field Cookie; content: "session", nocase; regex: "/^session.*=$/i"; classtype: malware-cnc; reference: url, http://www.securitynik.com; sid:4000004; rev: 10; )
Here is what the results look like, when snort is run against the pcap.
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A cmg --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 11/20-13:06:06.836488 [**] [1:4000004:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected" [**] [Classification: Known malware command and control traffic] [Prio rity: 1] {TCP} 10.0.0.110:1650 -> 10.0.0.107:443 http_inspect.http_method[3]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 47 45 54 GET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_version[8]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 48 54 54 50 2F 31 2E 31 HTTP/1.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_uri[18]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2F 6C 6F 67 69 6E 2F 70 72 6F 63 65 73 73 2E 70 /login/p rocess.p 68 70 hp - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_header[102]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 55 73 65 72 2D 41 67 65 6E 74 3A 20 4D 6F 7A 69 User-Age nt: Mozi 6C 6C 61 2F 35 2E 30 20 28 57 69 6E 64 6F 77 73 lla/5.0 (Windows 20 4E 54 20 36 2E 31 3B 20 57 4F 57 36 34 3B 20 NT 6.1; WOW64; 54 72 69 64 65 6E 74 2F 37 2E 30 3B 20 72 76 3A Trident/ 7.0; rv: 31 31 2E 30 29 20 6C 69 6B 65 20 47 65 63 6B 6F 11.0) li ke Gecko 0D 0A 48 6F 73 74 3A 20 31 30 2E 30 2E 30 2E 31 ..Host: 10.0.0.1 30 37 3A 34 34 33 07:443 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http_inspect.http_cookie[36]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 73 65 73 73 69 6F 6E 3D 72 2B 57 77 61 4F 42 61 session= r+WwaOBa 55 4C 50 37 6C 34 37 4B 72 64 68 55 30 41 42 4C ULP7l47K rdhU0ABL 36 49 30 3D 6I0= - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Ok. Then good stuff. With all of those in place we can now conclude, we are good to go for this detection. Taking a few different looks at the outputs.
First up JSON format.
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A alert_json --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 { "timestamp" : "11/20-13:03:59.632367", "pkt_num" : 9, "proto" : "TCP", "pkt_gen" : "stream_tcp", "pkt_len" : 164, "dir" : "C2S", "src_ap" : "10.0.0.110:1650", "dst_ap" : "10.0.0.107:443", "rule" : "1:4000001:10", "action" : "allow" } { "timestamp" : "11/20-13:04:02.701119", "pkt_num" : 18, "proto" : "TCP", "pkt_gen" : "stream_tcp", "pkt_len" : 462, "dir" : "C2S", "src_ap" : "10.0.0.110:1650", "dst_ap" : "10.0.0.107:443", "rule" : "1:4000002:10", "action" : "allow" } { "timestamp" : "11/20-13:05:06.697538", "pkt_num" : 51, "proto" : "TCP", "pkt_gen" : "stream_tcp", "pkt_len" : 148, "dir" : "C2S", "src_ap" : "10.0.0.110:1650", "dst_ap" : "10.0.0.107:443", "rule" : "1:4000003:10", "action" : "allow" } { "timestamp" : "11/20-13:06:06.836488", "pkt_num" : 58, "proto" : "TCP", "pkt_gen" : "stream_tcp", "pkt_len" : 148, "dir" : "C2S", "src_ap" : "10.0.0.110:1650", "dst_ap" : "10.0.0.107:443", "rule" : "1:4000004:10", "action" : "allow" } ...
Looking at alert fast
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A alert_full --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 [**] [1:4000001:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected" [**] 11/20-13:03:59.632367 [**] [1:4000002:10] "POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected" [**] 11/20-13:04:02.701119 [**] [1:4000003:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected" [**] 11/20-13:05:06.697538 [**] [1:4000004:10] "POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected" [**]
Looking at the talos alert_talos ...
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A alert_talos --talos --pcap-show -k none -d -R local.rules -c snort.lua -q | more Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 ##### empire-full-session.pcap ##### [1:4000001:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected (alerts: 3) [1:4000002:10] POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected (alerts: 3) [1:4000003:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected (alerts: 452) [1:4000004:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected (alerts: 377) #####
Looking at the above, I like the alert_talos as it gives me a summary of the alerts, rather than triggering all the alerts to the screen. Good job Talos!
Taking a final pass when snort is run without the -q option and with the -v option we see.
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A alert_talos --talos --pcap-show -k none -d -R local.rules -c snort.lua -v -------------------------------------------------- o")~ Snort++ 3.1.18.0 -------------------------------------------------- Loading snort.lua: Loading /usr/local/etc/snort/snort_defaults.lua: Finished /usr/local/etc/snort/snort_defaults.lua: Lua Allowlist Keywords for snort.lua: default_classifications, default_ftp_server, default_gtp, default_hi_port_scan, default_js_norm_built_in_ident, default_low_port_scan, default_med_port_scan, default_references, default_smtp, default_variables, default_wizard, ftp_command_specs, gtp_v0_info, gtp_v0_msg, gtp_v1_info, gtp_v1_msg, gtp_v2_info, gtp_v2_msg, http_methods, icmp_hi_sweep, icmp_low_sweep, icmp_med_sweep, ip_hi_decoy, ip_hi_dist, ip_hi_proto, ip_hi_sweep, ip_low_decoy, ip_low_dist, ip_low_proto, ip_low_sweep, ip_med_decoy, ip_med_dist, ip_med_proto, ip_med_sweep, netflow_versions, sip_requests, smtp_default_alt_max_command_lines, tcp_hi_decoy, tcp_hi_dist, tcp_hi_ports, tcp_hi_sweep, tcp_low_decoy, tcp_low_dist, tcp_low_ports, tcp_low_sweep, tcp_med_decoy, tcp_med_dist, tcp_med_ports, tcp_med_sweep, telnet_commands, udp_hi_decoy, udp_hi_dist, udp_hi_ports, udp_hi_sweep, udp_low_decoy, udp_low_dist, udp_low_ports, udp_low_sweep, udp_med_decoy, udp_med_dist, udp_med_ports, udp_med_sweep hosts so_proxy stream_tcp packets alerts host_tracker http_inspect binder wizard stream_ip search_engine profiler ips trace classifications active host_cache decode stream process output network daq Finished snort.lua: Loading rule args: Loading local.rules: Finished local.rules: Finished rule args: -------------------------------------------------- rule counts total rules loaded: 4 text rules: 4 option chains: 4 chain headers: 2 -------------------------------------------------- port rule counts tcp udp icmp ip dst 3 0 0 0 total 3 0 0 0 -------------------------------------------------- ips policies rule stats id loaded shared enabled file 0 4 0 4 snort.lua -------------------------------------------------- service rule counts to-srv to-cli http: 4 4 http2: 4 4 total: 8 8 -------------------------------------------------- fast pattern service groups to-srv to-cli key: 2 2 header: 2 2 -------------------------------------------------- search engine instances: 8 patterns: 16 pattern chars: 212 num states: 204 num match states: 16 memory scale: KB total memory: 14.2461 pattern memory: 0.824219 match list memory: 2.09375 transition memory: 10.3281 -------------------------------------------------- Flow Tracking -------------------------------------------------- stream: ip_frags_only: disabled max_flows: 476288 max_aux_ip: 16 pruning_timeout: 30 ip_cache: { idle_timeout = 180, cap_weight = 0 } tcp_cache: { idle_timeout = 3600, cap_weight = 11000 } udp_cache: { idle_timeout = 180, cap_weight = 0 } icmp_cache: { idle_timeout = 180, cap_weight = 0 } user_cache: { idle_timeout = 180, cap_weight = 0 } file_cache: { idle_timeout = 180, cap_weight = 32 } -------------------------------------------------- Network Policy : policy id 0 : snort.lua -------------------------------------------------- Inspection Policy : policy id 0 : snort.lua -------------------------------------------------- binder: bindings: { when = { proto = tcp, ports = 80 443 }, use = { type = http_inspect } } { when = { service = http }, use = { type = http_inspect } } { when = { }, use = { type = wizard } } -------------------------------------------------- http_inspect: request_depth: -1 (unlimited) response_depth: -1 (unlimited) unzip: enabled normalize_utf: enabled decompress_pdf: disabled decompress_swf: disabled decompress_zip: disabled decompress_vba: disabled script_detection: disabled normalize_javascript: disabled max_javascript_whitespaces: 200 js_normalization_depth: -1 js_norm_identifier_depth: 65536 js_norm_max_tmpl_nest: 32 js_norm_max_bracket_depth: 256 js_norm_max_scope_depth: 256 percent_u: disabled utf8: enabled utf8_bare_byte: disabled iis_unicode: disabled iis_unicode_code_page: 1252 iis_double_decode: enabled oversize_dir_length: 300 backslash_to_slash: enabled plus_to_space: enabled simplify_path: enabled xff_headers: x-forwarded-for true-client-ip request_body_app_detection: enabled -------------------------------------------------- so_proxy: -------------------------------------------------- stream_ip: max_frags: 8192 max_overlaps: 0 min_frag_length: 0 min_ttl: 1 policy: windows session_timeout: 60 -------------------------------------------------- stream_tcp: flush_factor: 0 max_pdu: 16384 max_window: 0 no_ack: disabled overlap_limit: 0 policy: windows queue_limit: { max_bytes = 4194304, max_segments = 3072 } reassemble_async: enabled require_3whs: 300 session_timeout: 180 small_segments: { count = 0, maximum_size = 0 } track_only: disabled -------------------------------------------------- wizard: -------------------------------------------------- pcap DAQ configured to read-file. -------------------------------------------------- host_cache memcap: 8388608 bytes Commencing packet processing ++ [0] empire-full-session.pcap Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 Instance 0 daq pool size: 256 Instance 0 daq batch size: 64 ##### empire-full-session.pcap ##### [1:4000001:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected (alerts: 3) [1:4000002:10] POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected (alerts: 3) [1:4000003:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected (alerts: 452) [1:4000004:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected (alerts: 377) ##### -- [0] empire-full-session.pcap -------------------------------------------------- Packet Statistics -------------------------------------------------- daq pcaps: 1 received: 17365 analyzed: 17365 allow: 17365 rx_bytes: 6929587 -------------------------------------------------- codec total: 17365 (100.000%) discards: 1610 ( 9.272%) eth: 17365 (100.000%) ipv4: 17365 (100.000%) tcp: 13785 ( 79.384%) udp: 1970 ( 11.345%) -------------------------------------------------- Module Statistics -------------------------------------------------- binder raw_packets: 3580 new_flows: 78 service_changes: 4 no_match: 4 inspects: 3658 -------------------------------------------------- detection analyzed: 17365 key_searches: 1314 header_searches: 2204 alerts: 835 total_alerts: 835 logged: 835 -------------------------------------------------- http_inspect flows: 25 scans: 4397 reassembles: 4399 inspections: 3956 requests: 1286 responses: 464 get_requests: 1253 post_requests: 33 request_bodies: 33 max_concurrent_sessions: 6 pipelined_flows: 10 pipelined_requests: 491 total_bytes: 898244 -------------------------------------------------- normalizer test_tcp_block: 3 -------------------------------------------------- search_engine max_queued: 2 total_flushed: 1318 total_inserts: 1318 total_unique: 1318 non_qualified_events: 483 qualified_events: 835 searched_bytes: 335622 -------------------------------------------------- stream flows: 78 total_prunes: 23 idle_prunes: 23 -------------------------------------------------- stream_tcp sessions: 78 max: 78 created: 78 released: 69 timeouts: 7 instantiated: 9 setups: 78 discards_skipped: 3 events: 12 syn_trackers: 69 segs_queued: 7414 segs_released: 7414 segs_used: 3224 rebuilt_packets: 4029 rebuilt_buffers: 20 rebuilt_bytes: 1064009 gaps: 47 client_cleanups: 29 server_cleanups: 10 syns: 69 syn_acks: 29 resets: 54 fins: 13 inspector_fallbacks: 5 partial_fallbacks: 22 max_segs: 816 max_bytes: 410007 -------------------------------------------------- wizard tcp_scans: 4 tcp_hits: 4 -------------------------------------------------- Summary Statistics -------------------------------------------------- timing runtime: 00:00:00 seconds: 0.073711 pkts/sec: 17365 Mbits/sec: 52 -------------------------------------------------- module profile (all, depth 255, sorted by total_time) # module layer checks time(us) avg/check %/caller %/total = ====== ===== ====== ======== ========= ======== ======= 1 other 1 17365 26070 1 36.73 36.73 2 stream_tcp 1 13785 16034 1 22.59 22.59 3 daq 1 17638 9925 0 13.98 13.98 4 http_inspect 1 16750 4542 0 6.40 6.40 5 mpse 1 53710 4029 0 5.68 5.68 6 rule_eval 1 18036 2855 0 4.02 4.02 7 decode 1 17365 2236 0 3.15 3.15 8 stream 1 15755 1919 0 2.70 2.70 9 eventq 1 25798 1613 0 2.27 2.27 10 paf 1 10990 1427 0 2.01 2.01 11 binder 1 3662 313 0 0.44 0.44 12 wizard 1 4 8 2 0.01 0.01 -- total -- 17365 70977 4 -- 100.00 -------------------------------------------------- rule profile (all, sorted by total_time) # gid sid rev checks matches alerts time (us) avg/check avg/match avg/non-match timeouts suspends = === === === ====== ======= ====== ========= ========= ========= ============= ======== ======== 1 14000003 10 472 452 452 4310 9 9 0 0 0 2 14000004 10 398 377 377 4139 10 10 0 0 0 3 14000002 10 436 3 3 375 0 38 0 0 0 4 14000001 10 12 3 3 308 25 86 5 0 0 o")~ Snort exiting
When we look above, we see that in one instance of we have 452 alerts and another of 377. The others with 3 we can live with. However, those 300+ and 400+ alerts can be a real headache for us as analysts, thus contributing to the so called alert fatigue. Let's reduce this noise ... and the fatigue.
Let's add the following event filter to the snort.lua file.
event_filter = { -- alerts once per 60 seconds then ignore any additional events during the 60 seconds. { gid = 1, sid = 4000001, type = 'both', track = 'by_src', count = 1, seconds = 60}, { gid = 1, sid = 4000002, type = 'both', track = 'by_src', count = 1, seconds = 60}, { gid = 1, sid = 4000003, type = 'both', track = 'by_src', count = 1, seconds = 60}, { gid = 1, sid = 4000004, type = 'both', track = 'by_src', count = 1, seconds = 60}, }
When Snort3 is run against the pcap, this time we see
┌──(rootđź’€securitynik)-[~/packets] └─# snort --pcap-list empire-full-session.pcap -A alert_talos --talos --pcap-show -k none -d -R local.rules -c snort.lua -q Reading network traffic from "empire-full-session.pcap" with snaplen = 1518 ##### empire-full-session.pcap ##### [1:4000001:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /news.php detected (alerts: 1) [1:4000002:10] POWERSHELL EMPIRE COMPROMISED HOST: POST request made for /news.php detected (alerts: 1) [1:4000003:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /admin/get.php detected (alerts: 1) [1:4000004:10] POWERSHELL EMPIRE COMPROMISED HOST: GET request made for /login/process.php detected (alerts: 1) ##### -------------------------------------------------- module profile (all, depth 255, sorted by total_time) # module layer checks time(us) avg/check %/caller %/total = ====== ===== ====== ======== ========= ======== ======= 1 other 1 17365 29039 1 39.42 39.42 2 stream_tcp 1 13785 15494 1 21.03 21.03 3 daq 1 17638 10463 0 14.20 14.20 4 http_inspect 1 16750 4428 0 6.01 6.01 5 mpse 1 53704 4115 0 5.59 5.59 6 rule_eval 1 18033 2923 0 3.97 3.97 7 stream 1 15755 2240 0 3.04 3.04 8 decode 1 17365 2203 0 2.99 2.99 9 paf 1 10989 1419 0 1.93 1.93 10 eventq 1 25792 994 0 1.35 1.35 11 binder 1 3662 336 0 0.46 0.46 12 wizard 1 4 7 1 0.01 0.01 -- total -- 17365 73666 4 -- 100.00 -------------------------------------------------- rule profile (all, sorted by total_time) # gid sid rev checks matches alerts time (us) avg/check avg/match avg/non-match timeouts suspends = === === === ====== ======= ====== ========= ========= ========= ============= ======== ======== 1 14000003 10 472 452 1 4786 10 10 0 0 0 2 14000004 10 398 377 1 3933 9 10 0 0 0 3 14000002 10 436 3 1 366 0 38 0 0 0 4 14000001 10 12 3 1 311 25 82 7 0 0
This is much better. From an Analyst perspective, we would prefer to not not have alert fatigue.
This is the end of this post, see you in the next post, where we use Zeek to detect this activity.
Other posts in this series:
Beginning Powershell Empire - The Attack in 10 steps
Powershell Empire Log Analysis
Powershell Empire Packet Analysis
Powershell Empire Detection with Snort
Powershell Empire - Detection with Zeek
References:
Great post! Would you mind sharing the PCAPs?
ReplyDelete