diff --git a/snort3convert.py b/snort3convert.py index 6746fbc..04b8290 100644 --- a/snort3convert.py +++ b/snort3convert.py @@ -2,72 +2,138 @@ from unidecode import unidecode import argparse import time +import logging +logging.basicConfig(filename="snortconverterlog.txt", level=logging.DEBUG) -# Helper Function takes URL "reference" keyword and fixed fields then converts them to snort 3 -def convertreferencesnort3(urllist): - # Might need to re-work this to be truely universal, currently relies on surricata fields - tmp = [] - tmp2 = urllist[0].split(",") - tmp.append("http_uri") - tmp.append(";") - tmp.append("content:\"" + tmp2[1] + "\"") - tmp.append(urllist[1]) - return tmp +global sid_start_selector +global keyword_tracker +global converted_list +global row_tracker # Helper Function takes Field Agent Keyword and unknown number of associated fields then converts them to Snort 3 -def convertuseragentsnort3(snortlist): +def convert_threshold_snort3(threshold_list): # Count fields so you know how many to add to list - fieldcount = len(snortlist) + temp = [] + temp2 = threshold_list[0].split(":") + # Manually write modified threshold-->event_filter field + temp.append("event_filter:") + # Skip threshold field and add additional fields if they exist + temp.append(temp2[1]) + temp.append(";") + return temp + + +# Helper Function takes Field Agent Keyword and unknown number of associated fields then converts them to Snort 3 +def convert_dns_query_snort3(dns_query_list): + # First run will have only one Content field so we need to accommodate secondary and tertiary runs + # Count fields so you know how many to add to list + global converted_list + global row_tracker + temp = [] + temp3 = [] + + # Manually write modified byte_test field to test for DNS traffic instead of dns.query + temp.append("byte_test:1,!&,0xF8,2;") + # Add first content field to temp list + temp3.append(dns_query_list[2]) + # Iterate over rest of items + for x in range(3, len(dns_query_list)): + # If another content exists and we find it, convert current list, then start building a new one + if dns_query_list[x].find("content:") != -1: + temp2 = convert_content_snort3(temp3, True) + for item in temp2: + temp.append(item) + temp3 = [] + temp3.append(dns_query_list[x]) + # Else append additional option to current temp list + else: + temp3.append(dns_query_list[x]) + # If not more items convert current list + temp2 = convert_content_snort3(temp3, True) + for item in temp2: + temp.append(item) + # Update Alert field and replace http with udp + converted_list[row_tracker][0] = re.sub('dns', 'udp', converted_list[row_tracker][0]) + return temp + + +# Helper Function takes Field Agent Keyword and unknown number of associated fields then converts them to Snort 3 +def convert_user_agent_snort3(snort_list): + # Count fields so you know how many to add to list + field_count = len(snort_list) temp = [] # Manually write modified header field temp.append("http_header: field user-agent") temp.append(";") - temp.append(snortlist[0]) + temp.append(snort_list[0]) # Add additional fields if they exist - if fieldcount > 3: - for x in range(3, fieldcount): - temp.append(snortlist[x]) + if field_count > 3: + for x in range(3, field_count): + temp.append(snort_list[x]) return temp # Helper Function takes HTTP_Header Keyword and unknown number of associated fields then converts them to Snort 3 -def converthttpheadersnort3(headerlist): +def convert_http_header_snort3(header_list): # Count fields so you know how many to add to list - fieldcount = len(headerlist) + field_count = len(header_list) temp = [] # Switch HTTP_Header and CONTENT fields - temp.append(headerlist[2]) + temp.append(header_list[2]) temp.append(";") - temp.append(headerlist[0]) + temp.append(header_list[0]) # Add additional fields if they exist - if fieldcount > 3: - for x in range(3, fieldcount): - temp.append(headerlist[3]) + if field_count > 3: + for x in range(3, field_count): + temp.append(header_list[3]) return temp -# Helper Function takes Optional content Key/value pairs and converts them to Snort 3 format -def contentchangersnort3(contentlist): +# Helper Function takes Content and unknown Key/value pairs and converts them to Snort 3 format +def convert_content_snort3(contentlist, hexswitch): for index, item in enumerate(contentlist): + # Check Hex switch and if enabled convert the content field + if index == 0 and hexswitch: + b = contentlist[index].split(":") + if b[1].find("!") != -1: + contentlist[index] = b[0] + ":!" + convert_to_hex(b[1][1:]) + else: + contentlist[index] = b[0] + ":" + convert_to_hex(b[1]) + # Skip the Content Field and leave it in place if index >= 1: + # Search for alphanumerics (or NOT SYMBOLS) for content or key/value pairs if re.match(r"^[a-zA-Z0-9]", item[1:]): b = contentlist[index].split(":") if len(b) > 1: contentlist[index] = b[0] + " " + b[1] + contentlist[-1] = ";" return contentlist +# Helper function takes a Snort 3 content string and returns the hex version of the content string +def convert_to_hex (stringy): + # Initialize variables + answer = "\"" + temp5 = stringy[1:-1].split(".") + field_count = len(temp5) + # Iterate over each part of the domain and convert to hex + for x in range(0, field_count): + answer = answer + "|" + hex(len(temp5[x])) + "|" + temp5[x] + "" + answer = answer + "\"" + return answer + + # Helper Function takes SID and Generates new sequential ID's over 1000000 if they are BELOW, otherwise does nothing -def sidchangersnort3(sidlist): - global sid_start_selecter +def sid_changer_snort_3(sid_list): + global sid_start_selector - tmp = sidlist[0].split(":") - if int(tmp[1]) <= sid_start_selecter: - sidlist[0] = "sid:" + str(sid_start_selecter) - sid_start_selecter += 1 - return sidlist + tmp = sid_list[0].split(":") + if int(tmp[1]) <= sid_start_selector: + sid_list[0] = "sid:" + str(sid_start_selector) + sid_start_selector += 1 + return sid_list # Helper function selects delimiters based on selected output format @@ -85,40 +151,59 @@ def syntaxselector(state, output3): # Helper function selects which keywords generate the Rule Index -def indexselector(item2, ingester2): +def index_selector(item2, ingester2): # Search for keywords based in Ingest Selector to build index list + # Modified to correctly build index based on sticky buffers for dns.query + global keyword_tracker selector = False if ingester2 == "SURRICATA": for item in SurricataChunkKeywords: if item2.find(item) != -1: - selector = True + # Leading Keywords with possible content + if keyword_tracker == "dns.query" and item == "content:": + selector = False + else: + selector = True + keyword_tracker = item else: selector = False return selector # Helper function the lookup table for conversions and calls the conversion functions -def keywordselector(searchitem, lista, output3): +def keyword_selector(search_item, list_a, output3): # Based on Selector, convert to proper fields for output syntax if output3 == "SNORT3": - if re.search("\\bhttp_user_agent\\b", searchitem) != None: - lista = convertuseragentsnort3(lista) - elif re.search("\\breference\\b", searchitem) != None: - lista = convertreferencesnort3(lista) - elif re.search("\\bhttp_header\\b", searchitem) != None: - lista = converthttpheadersnort3(lista) - elif re.search("\\bsid\\b", searchitem) != None: - lista = sidchangersnort3(lista) - elif re.search("\\bcontent\\b", searchitem) != None: - lista = contentchangersnort3(lista) - return lista + # Check for leading keywords (fastest result with least waste) + # if re.search("\\bhttp_user_agent\\b", list_a[2]) != None and re.search("\\bcontent\\b", search_item) != None: + # list_a = convert_user_agent_snort3(list_a) + # elif re.search("\\bhttp_header\\b", list_a[2]) != None and re.search("\\bcontent\\b", search_item) != None: + # list_a = convert_http_header_snort3(list_a) + if re.search("\\bsid\\b", search_item) != None: + list_a = sid_changer_snort_3(list_a) + elif re.search("\\bthreshold\\b", search_item) != None: + list_a = convert_threshold_snort3(list_a) + elif re.search("\\bdns.query\\b", search_item) != None: + list_a = convert_dns_query_snort3(list_a) + elif re.search("\\bcontent\\b", search_item) != None: + # Check for trailing keywords: + for item in list_a: + if re.search("\\bhttp_user_agent\\b", item) != None: + list_a = convert_user_agent_snort3(list_a) + break + elif re.search("\\bhttp_header\\b", item) != None: + list_a = convert_http_header_snort3(list_a) + break + else: + list_a = convert_content_snort3(list_a, False) + return list_a # Function Creates Universal list for manipulation -def createintermediatelist(rulefile): - ruleslist = [] - with open(rulefile, 'r', encoding='utf-8') as file1: +def create_intermediate_list(rule_file): + rules_list = [] + with open(rule_file, 'r', encoding='utf-8') as file1: # Read non-empty lines from file lines = [line for line in file1.readlines() if line.strip()] for line in lines: @@ -132,92 +217,95 @@ def createintermediatelist(rulefile): # Remove Trailing bracket and semicolon line[-1] = line[-1][:-2] # Sanitize multiple errors - line = sanitizeingestlist(line) + line = sanitize_ingest_list(line) # compose rules into single list - ruleslist.append(line) - return ruleslist + rules_list.append(line) + return rules_list # This function perform standardization and Sanitization of the ingestlist -def sanitizeingestlist(lista): +def sanitize_ingest_list(list_a): # Fix Arrows - lista[0] = re.sub(r"(?<=\S)->", " ->", lista[0]) + list_a[0] = re.sub(r"(?<=\S)->", " ->", list_a[0]) # Fix Miss-formatted semicolons - for index, item in enumerate(lista): + for index, item in enumerate(list_a): if item.find(";") != -1: b = item.split(";") - lista[index]=b[0] - lista.insert(index + 1, b[1]) + list_a[index] = b[0] + list_a.insert(index + 1, b[1]) # Fix skipped white space - for index, item in enumerate(lista): - lista[index] = lista[index].strip() + for index, item in enumerate(list_a): + list_a[index] = list_a[index].strip() - return lista + return list_a # This function creates an index for each rule in the list given to it -def generateruleindex(rulelist2, ingester): - indexlist2 = [] - statecheck = False +def generate_rule_index(rule_list_2, ingester): + index_list_2 = [] # Search rule keywords and create index for "chunking" - for index3, item3 in enumerate(rulelist2): - indexlist2.append([]) + for index3, item3 in enumerate(rule_list_2): + index_list_2.append([]) for item2 in item3: - statecheck = indexselector(item2, ingester) - if statecheck: - indexlist2[index3].append(True) + state_check = index_selector(item2, ingester) + if state_check: + index_list_2[index3].append(True) else: - indexlist2[index3].append(False) - statecheck = False - return indexlist2 + index_list_2[index3].append(False) + return index_list_2 # This function enumerates the INGEST rules and converts them to OUTPUT syntax -def convertlist(indexlist3, ruleslist3, output2): - convertedlist = [] - templist = [] +def convert_list(index_list_3, rules_list_3, output2): + global converted_list + global row_tracker + + temp_list = [] # "For each rule" perform chunking and conversion - for index, item in enumerate(indexlist3): - convertedlist.append([]) + for index, item in enumerate(index_list_3): + row_tracker = index + converted_list.append([]) # First field of rule always behaves differently.... "alert....(" - templist.append(ruleslist3[index][0]) - templist.append(" (") - for item3 in templist: - convertedlist[index].append(item3) - templist = [] + temp_list.append(rules_list_3[index][0]) + temp_list.append(" (") + for item3 in temp_list: + converted_list[index].append(item3) + temp_list = [] # Add first non-alert field to the temporary list - templist.append(ruleslist3[index][1]) + temp_list.append(rules_list_3[index][1]) # Iterate over each of remaining items in the rule - for index4, item4 in enumerate(indexlist3[index][2:], 2): - # print(indexlist3[index][2:]) - # If indexlist is True then dump current chunk and convert list, start again - if indexlist3[index][index4]: - templist.append(syntaxselector(indexlist3[index][index4], output2)) - for index2, item2 in enumerate(templist): - templist = keywordselector(item2, templist, output2) - for item3 in templist: - convertedlist[index].append(item3) - templist = [] - templist.append(ruleslist3[index][index4]) + for index4, item4 in enumerate(index_list_3[index][2:], 2): + # If index_list_3 is True, append syntax then dump current chunk and convert list, start again + if index_list_3[index][index4]: + temp_list.append(syntaxselector(index_list_3[index][index4], output2)) + # TODO: verify replacement function works as intended + temp_list = keyword_selector(temp_list[0], temp_list, output2) + # for index2, item2 in enumerate(temp_list): + # temp_list = keyword_selector(item2, temp_list, output2) + # logging.debug(temp_list) + for item3 in temp_list: + converted_list[index].append(item3) + temp_list = [] + temp_list.append(rules_list_3[index][index4]) # Otherwise add to chunk else: - templist.append(syntaxselector(indexlist3[index][index4], output2)) - templist.append(ruleslist3[index][index4]) - templist.append(syntaxselector(True, output2)) - templist.append(")") - for item3 in templist: - convertedlist[index].append(item3) - templist = [] + temp_list.append(syntaxselector(index_list_3[index][index4], output2)) + temp_list.append(rules_list_3[index][index4]) + temp_list.append(syntaxselector(True, output2)) + temp_list.append(")") + for item3 in temp_list: + converted_list[index].append(item3) + temp_list = [] # return Full list of rules - return convertedlist + return converted_list # This function takes the final converted rules and writes them to a file -def writerulestofile(lista, outfilename): - with open(outfilename, 'w', encoding='utf-8') as file1: +def write_rules_to_file(lista, out_filename): + with open(out_filename, 'w', encoding='utf-8') as file1: # Spacing Selector for index, item in enumerate(lista): for index2, item2 in enumerate(lista[index]): @@ -233,26 +321,32 @@ def writerulestofile(lista, outfilename): def main(sid, ingest, output, infile, outfile): - global sid_start_selecter - sid_start_selecter = sid + global sid_start_selector + global keyword_tracker + global converted_list + global row_tracker + + row_tracker = 0 + sid_start_selector = sid + keyword_tracker = "NONE" + converted_list = [] # Create Rule List from Ingest Set - start_list = createintermediatelist(infile) + start_list = create_intermediate_list(infile) # Create Index for Key words (based in Ingest Selector) - index_list = generateruleindex(start_list, ingest) + index_list = generate_rule_index(start_list, ingest) # Generate the Base Output List - base_output_list = convertlist(index_list, start_list, output) - # User Emitter to generate final syntax rules - writerulestofile(base_output_list, outfile) + base_output_list = convert_list(index_list, start_list, output) + # Use Emitter to generate final syntax rules + write_rules_to_file(base_output_list, outfile) -SurricataChunkKeywords = ["pcre","isdataat","ssl", "alert", "msg:", "flow:", "content:", "reference:", "classtype:", - "metadata:", "sid", "rev"] +SurricataChunkKeywords = ["byte_test", "pcre", "isdataat", "ssl", "alert", "msg:", "flow:", "content:", "reference:", + "classtype:", "metadata:", "sid", "rev", "threshold", "dns.query"] if __name__ == '__main__': # TODO: Add McAfee, Forescout, Fortinet, Snort2 - # Initialize Argument Parser parser = argparse.ArgumentParser(description="Program Accepts Selected rule input and converts to selected output \ rule type.") diff --git a/testsnort3outputrules.txt b/testsnort3outputrules.txt index 8b0ce94..57086e2 100644 --- a/testsnort3outputrules.txt +++ b/testsnort3outputrules.txt @@ -1,18 +1,18 @@ -alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (msg:"Tuning JSON API DNS over HTTPS detected"; flow:established,to_server; http_header; content:"application/dns-json"; http_uri; content:"tools.ietf.org/html/draft-ietf-doh-dns-over-https-02"; classtype:policy-violation; sid:1000000; rev:1; metadata:created_at 2021_01_25, updated_at 2021_01_25;) -alert http any any -> $EXTERNAL_NET any (msg:"Tuning SolarWinds Orion User-Agent"; flow:to_server,established; http_header: field user-agent; content:"SolarWindsOrionImprovementClient/", nocase; http_uri; content:"foobar.baz.com"; classtype:misc-activity; sid:1000001; rev:1; metadata:affected_product SolarWinds, created_at 2021_01_11, signature_severity Major, tag User_Agent, updated_at 2021_01_11;) -alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning SSL 2.0 using preprocessor version and state client_hello"; ssl_version:sslv2; ssl_state:client_hello; http_uri; content:"foobar.com"; sid:1000002; rev:1;) -alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning SSL 2.0 using hex version and state client_hello"; content:"|16 00 02|", depth 3; content:"|01|", distance 2, within 1; http_uri; content:"foobar.com"; sid:1000003; rev:1;) -alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning SSL 3.0 using preprocessor version and state client_hello"; ssl_version:sslv3; ssl_state:client_hello; http_uri; content:"foobar.com"; sid:1000004; rev:1;) -alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning SSL 3.0 using hex version and state client_hello"; content:"|16 03 00|", depth 3; content:"|01|", distance 2, within 1; http_uri; content:"foobar.com"; sid:1000005; rev:1;) -alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning TLS 1.0 using preprocessor version and state client_hello"; ssl_version:tls1.0; ssl_state:client_hello; http_uri; content:"foobar.com"; sid:1000006; rev:1;) -alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning TLS 1.0 using hex version and state client_hello"; content:"|16 03 01|", depth 3; content:"|01|", distance 2, within 1; http_uri; content:"foobar.com"; sid:1000007; rev:1;) -alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning TLS 1.1 using preprocessor version and state client_hello"; ssl_version:tls1.1; ssl_state:client_hello; http_uri; content:"foobar.com"; sid:1000008; rev:1;) -alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning TLS 1.1 using hex version and state client_hello"; content:"|16 03 02|", depth 3; content:"|01|", distance 2, within 1; http_uri; content:"foobar.com"; sid:1000009; rev:1;) -alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning SSL 2.0 using preprocessor version and state server_hello"; ssl_version:sslv2; ssl_state: server_hello; http_uri; content:"foobar.com"; sid:1000010; rev:1;) -alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning SSL 2.0 using hex version and state server_hello"; content:"|16 00 02|", depth 3; content:"|02|", distance 2, within 1; http_uri; content:"foobar.com"; sid:1000011; rev:1;) -alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning SSL 3.0 using preprocessor version and state server_hello"; ssl_version:sslv3; ssl_state: server_hello; http_uri; content:"foobar.com"; sid:1000012; rev:1;) -alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning SSL 3.0 using hex version and state server_hello"; content:"|16 03 00|", depth 3; content:"|02|", distance 2, within 1; http_uri; content:"foobar.com"; sid:1000013; rev:1;) -alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning TLS 1.0 using preprocessor version and state server_hello"; ssl_version:tls1.0; ssl_state: server_hello; http_uri; content:"foobar.com"; sid:1000014; rev:1;) -alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning TLS 1.0 using hex version and state server_hello"; content:"|16 03 01|", depth 3; content:"|02|", distance 2, within 1; http_uri; content:"foobar.com"; sid:1000015; rev:1;) -alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning TLS 1.1 using preprocessor version and state server_hello"; ssl_version:tls1.1; ssl_state: server_hello; http_uri; content:"foobar.com"; sid:1000016; rev:1;) -alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning TLS 1.1 using hex version and state server_hello"; content:"|16 03 02|", depth 3; content:"|02|", distance 2, within 1; http_uri; content:"foobar.com"; sid:1000017; rev:1;) +alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (msg:"Tuning JSON API DNS over HTTPS detected"; flow:established,to_server; http_header; content:"application/dns-json"; reference:url,tools.ietf.org/html/draft-ietf-doh-dns-over-https-02; classtype:policy-violation; sid:1000000; rev:1; metadata:created_at 2021_01_25, updated_at 2021_01_25;) +alert http any any -> $EXTERNAL_NET any (msg:"Tuning SolarWinds Orion User-Agent"; flow:to_server,established; http_header: field user-agent; content:"SolarWindsOrionImprovementClient/", nocase; reference:url,foobar.baz.com; classtype:misc-activity; sid:1000001; rev:1; metadata:affected_product SolarWinds, created_at 2021_01_11, signature_severity Major, tag User_Agent, updated_at 2021_01_11;) +alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning SSL 2.0 using preprocessor version and state client_hello"; ssl_version:sslv2; ssl_state:client_hello; reference:url,foobar.com; sid:1000002; rev:1;) +alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning SSL 2.0 using hex version and state client_hello"; content:"|16 00 02|", depth 3; content:"|01|", distance 2, within 1; reference:url,foobar.com; sid:1000003; rev:1;) +alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning SSL 3.0 using preprocessor version and state client_hello"; ssl_version:sslv3; ssl_state:client_hello; reference:url,foobar.com; sid:1000004; rev:1;) +alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning SSL 3.0 using hex version and state client_hello"; content:"|16 03 00|", depth 3; content:"|01|", distance 2, within 1; reference:url,foobar.com; sid:1000005; rev:1;) +alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning TLS 1.0 using preprocessor version and state client_hello"; ssl_version:tls1.0; ssl_state:client_hello; reference:url,foobar.com; sid:1000006; rev:1;) +alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning TLS 1.0 using hex version and state client_hello"; content:"|16 03 01|", depth 3; content:"|01|", distance 2, within 1; reference:url,foobar.com; sid:1000007; rev:1;) +alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning TLS 1.1 using preprocessor version and state client_hello"; ssl_version:tls1.1; ssl_state:client_hello; reference:url,foobar.com; sid:1000008; rev:1;) +alert tcp $HOME_NET any -> $EXTERNAL_NET 443 (msg:"Tuning TLS 1.1 using hex version and state client_hello"; content:"|16 03 02|", depth 3; content:"|01|", distance 2, within 1; reference:url,foobar.com; sid:1000009; rev:1;) +alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning SSL 2.0 using preprocessor version and state server_hello"; ssl_version:sslv2; ssl_state: server_hello; reference:url,foobar.com; sid:1000010; rev:1;) +alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning SSL 2.0 using hex version and state server_hello"; content:"|16 00 02|", depth 3; content:"|02|", distance 2, within 1; reference:url,foobar.com; sid:1000011; rev:1;) +alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning SSL 3.0 using preprocessor version and state server_hello"; ssl_version:sslv3; ssl_state: server_hello; reference:url,foobar.com; sid:1000012; rev:1;) +alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning SSL 3.0 using hex version and state server_hello"; content:"|16 03 00|", depth 3; content:"|02|", distance 2, within 1; reference:url,foobar.com; sid:1000013; rev:1;) +alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning TLS 1.0 using preprocessor version and state server_hello"; ssl_version:tls1.0; ssl_state: server_hello; reference:url,foobar.com; sid:1000014; rev:1;) +alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning TLS 1.0 using hex version and state server_hello"; content:"|16 03 01|", depth 3; content:"|02|", distance 2, within 1; reference:url,foobar.com; sid:1000015; rev:1;) +alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning TLS 1.1 using preprocessor version and state server_hello"; ssl_version:tls1.1; ssl_state: server_hello; reference:url,foobar.com; sid:1000016; rev:1;) +alert tcp $HOME_NET 443 -> $EXTERNAL_NET any (msg:"Tuning TLS 1.1 using hex version and state server_hello"; content:"|16 03 02|", depth 3; content:"|02|", distance 2, within 1; reference:url,foobar.com; sid:1000017; rev:1;)