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.
References: