├── LICENSE ├── README.md ├── __load__.bro ├── attachments.bro ├── levenshtein.bro ├── log-smtp-urls.bro └── main.bro /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of bro-phishing nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bro Phishing Detection Module 2 | ============================= 3 | 4 | Phishing detection in Bro. 5 | 6 | Installation 7 | ----------------------- 8 | 9 | ```bash 10 | cd /share/bro/site/ 11 | git clone git://github.com/hosom/bro-phishing.git Phishing 12 | echo "@load Phishing" >> local.bro 13 | ``` 14 | 15 | attachments.bro 16 | ----------------------- 17 | A simple phishing detection for mass phishing campaigns like Dridex. Detects the same email attachment being sent to many recipients. 18 | 19 | **max_attachment_recipients** controls the threshold that this script will alert on. 20 | 21 | **exploit_types** are the file types to monitor. We can't monitor for just any filetype, otherwise certificates and signature files will result in an alert. 22 | 23 | **attachment_policy** is a hook that allows for complex tuning of this script. 24 | 25 | For example, if you wanted to ignore all email from the source *marketing@foo.com*, you would add the following to a script and load it after loading the **attachments.bro** script. 26 | 27 | ```bro 28 | hook Phishing::attachment_policy(f: fa_file) &priority=10 29 | { 30 | # Because this hook utilizes a file, rather than a connection object... the exception code can be 31 | # longer than I would prefer. 32 | local ignore = F; 33 | 34 | for ( cid in f$conns) 35 | { 36 | local c = f$conns[cid]; 37 | if ( c?$smtp && c$smtp?$mailfrom && c$smtp$mailfrom == "" ) 38 | { 39 | ignore = T; 40 | # This break controls the flow of the inner loop, not the hook. 41 | break; 42 | } 43 | } 44 | 45 | if (ignore) 46 | # This break controls the flow of the hook, based on the status posted to ignore 47 | break; 48 | } 49 | ``` 50 | 51 | levenshtein.bro 52 | ----------------------- 53 | Detection of emails from domains close to domains within **Site::local_zones**. 54 | 55 | **max_distance** is the maximum levenshtein distance that will cause an alert in the notice.log. 56 | 57 | To monitor a domain, simply add it to the **Site::local_zones**. 58 | 59 | Example hook for policy 60 | ----------------------- 61 | ```bro 62 | hook policy(rec: SMTP::Info) 63 | { 64 | if ( Site::is_local_addr(rec$id$orig_h) ) 65 | break; 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /__load__.bro: -------------------------------------------------------------------------------- 1 | @load ./main 2 | @load ./levenshtein 3 | @load ./attachments 4 | @load ./log-smtp-urls 5 | -------------------------------------------------------------------------------- /attachments.bro: -------------------------------------------------------------------------------- 1 | ##! Sumstats script to detect when many individuals receive the 2 | ##! same document type file. 3 | 4 | module Phishing; 5 | 6 | @load base/frameworks/notice 7 | @load base/frameworks/sumstats 8 | 9 | export { 10 | redef enum Notice::Type += { 11 | ## Indicates that a suspicious email document was seen 12 | Suspicious_Email_Document 13 | }; 14 | 15 | ## Time period to run analysis on emails 16 | global analysis_interval: interval = 15min &redef; 17 | ## Maximum acceptable document attachment attachment_recipients 18 | global max_attachment_recipients: double = 5.0 &redef; 19 | ## The file mime_types to keep track of 20 | global exploit_types: set[string] = { 21 | "application/java-archive", 22 | "application/x-java-applet", 23 | "application/x-java-jnlp-file", 24 | "application/msword", 25 | "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 26 | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 27 | "application/vnd.openxmlformats-officedocument.presentationml.presentation", 28 | "application/pdf", 29 | "application/x-dosexec", 30 | "application/zip" 31 | }; 32 | ## Provides the ability to whitelist emails that should not be monitored for attachments / phishing 33 | global Phishing::attachment_policy: hook(f: fa_file); 34 | } 35 | 36 | event bro_init() 37 | { 38 | local r1: SumStats::Reducer = [$stream="phishing.attachment_recipients", 39 | $apply=set(SumStats::SUM)]; 40 | SumStats::create([$name="phishing.email_docs", 41 | $epoch=analysis_interval, 42 | $reducers=set(r1), 43 | $threshold=max_attachment_recipients, 44 | $threshold_val(key: SumStats::Key, result: SumStats::Result) = 45 | { 46 | return result["phishing.attachment_recipients"]$sum; 47 | }, 48 | $threshold_crossed(key: SumStats::Key, result: SumStats::Result) = 49 | { 50 | local message = fmt("SHA1 %s seen with more than %g recipients", key$str, result["phishing.attachment_recipients"]$sum); 51 | local subtext = "Indicates mass mail of document files."; 52 | local i = Notice::Info($ts=network_time(), 53 | $note=Suspicious_Email_Document, 54 | $identifier=key$str, 55 | $msg=message, 56 | $sub=subtext); 57 | NOTICE(i); 58 | }]); 59 | } 60 | 61 | event file_sniff(f: fa_file, meta: fa_metadata) 62 | { 63 | if ( f?$source && f$source == "SMTP" ) 64 | Files::add_analyzer(f, Files::ANALYZER_SHA1); 65 | } 66 | 67 | event file_state_remove(f: fa_file) 68 | { 69 | 70 | if ( ! f?$source || f$source != "SMTP") 71 | return; 72 | 73 | if ( f$info?$mime_type && f$info$mime_type in exploit_types && hook Phishing::attachment_policy(f) ) 74 | { 75 | SumStats::observe("phishing.attachment_recipients", 76 | SumStats::Key($str=f$info$sha1), 77 | SumStats::Observation($num=1)); 78 | } 79 | } -------------------------------------------------------------------------------- /levenshtein.bro: -------------------------------------------------------------------------------- 1 | ##! Phishing detection utilizing levenshtein algorithm to find 2 | ##! senders using domains too close to locally used domain names 3 | 4 | module Phishing; 5 | 6 | @load base/frameworks/notice 7 | 8 | export { 9 | redef enum Notice::Type += { 10 | ## Raised when an SMTP mailfrom is too close to a domain defined within 11 | ## the :bro:id:`Site::local_zones` variable. 12 | SMTP_Mail_From_too_Close, 13 | ## Raised when an SMTP from is too close to a domain defined within 14 | ## the :bro:id:`Site::local_zones` variable. 15 | SMTP_From_too_Close, 16 | ## Raised when the SMTP reply_to is too close to a domain defined 17 | ## within the :bro:id`Site::local_zones` variable. 18 | SMTP_Reply_To_too_Close 19 | }; 20 | 21 | ## Used to define the maximum difference in names that will raise a Notice. 22 | global max_distance: int = 4 &redef; 23 | } 24 | 25 | function parse_domain(s: string): string 26 | { 27 | local a = split_string(s, /@/); 28 | if ( |a| > 1 ) 29 | { 30 | local b = split_string(a[1], />/); 31 | return b[0]; 32 | } 33 | return ""; 34 | } 35 | 36 | event SMTP::log_smtp(rec: SMTP::Info) 37 | { 38 | if ( ! hook Phishing::policy(rec) ) 39 | return; 40 | local domain = ""; 41 | local msg: string; 42 | for ( zone in Site::local_zones ) 43 | { 44 | if ( rec?$mailfrom ) 45 | { 46 | domain = parse_domain(rec$mailfrom); 47 | if ( levenshtein_distance(domain, zone) < max_distance ) 48 | { 49 | msg = fmt("local zone %s was too close to observed mailfrom %s", 50 | zone, rec$mailfrom); 51 | NOTICE([$note=SMTP_Mail_From_too_Close, 52 | $id=rec$id, 53 | $uid=rec$uid, 54 | $msg=msg]); 55 | } 56 | } 57 | if ( rec?$from ) 58 | { 59 | domain = parse_domain(rec$from); 60 | if ( levenshtein_distance(domain, zone) < max_distance ) 61 | { 62 | msg = fmt("local zone %s was too close to observed from %s", 63 | zone, rec$from); 64 | NOTICE([$note=SMTP_From_too_Close, 65 | $id=rec$id, 66 | $uid=rec$uid, 67 | $msg=msg]); 68 | } 69 | } 70 | if ( rec?$reply_to ) 71 | { 72 | domain = parse_domain(rec$reply_to); 73 | if ( levenshtein_distance(domain, zone) < max_distance ) 74 | { 75 | msg = fmt("local zone %s was too close to observed reply_to %s", 76 | zone, rec$reply_to); 77 | NOTICE([$note=SMTP_Reply_To_too_Close, 78 | $id=rec$id, 79 | $uid=rec$uid, 80 | $msg=msg]); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /log-smtp-urls.bro: -------------------------------------------------------------------------------- 1 | ##! Create a log containing all links seen in emails 2 | 3 | module Phishing; 4 | 5 | @load base/protocols/smtp 6 | @load base/utils/urls 7 | 8 | export { 9 | ## Create log stream 10 | redef enum Log::ID += { Links_LOG }; 11 | 12 | type Info: record { 13 | ## Timestamp pulled from the SMTP record 14 | ts: time &log; 15 | ## Connection UID to tie the log to the conn log 16 | uid: string &log; 17 | ## SMTP MAILFROM header 18 | from: string &log &optional; 19 | ## SMTP RCPTTO header 20 | to: set[string] &log &optional; 21 | ## The host portion of the URL found 22 | host: string &log; 23 | ## The path of the URL found 24 | path: string &log; 25 | }; 26 | 27 | ## Event fired when a link is found in an email 28 | global link_found: event(host: string, path: string); 29 | } 30 | 31 | event bro_init() 32 | { 33 | Log::create_stream(Phishing::Links_LOG, [$columns=Info]); 34 | } 35 | 36 | event mime_all_data(c: connection, length: count, data: string) 37 | { 38 | if ( ! c?$smtp ) 39 | return; 40 | 41 | # Get all of the URLs from the mime data 42 | local urls = find_all_urls_without_scheme(data); 43 | # Loop through each of the links, logging them 44 | for ( url in urls ) 45 | { 46 | 47 | # Basic parsing of URL to make the log more useful 48 | local uri = split_string1(url, /\//); 49 | local host = uri[0]; 50 | local path = ""; 51 | if ( |uri| > 1 ) 52 | { 53 | path = "/" + uri[1]; 54 | } 55 | 56 | # Fire an event for additional use of this information 57 | event Phishing::link_found(host, path); 58 | 59 | local i: Info; 60 | 61 | i$ts = c$smtp$ts; 62 | i$uid = c$smtp$uid; 63 | if ( c$smtp?$mailfrom ) 64 | i$from = c$smtp$mailfrom; 65 | if ( c$smtp?$rcptto ) 66 | i$to = c$smtp$rcptto; 67 | i$host = host; 68 | i$path = path; 69 | 70 | # Log the link to the links log 71 | Log::write(Links_LOG, i); 72 | } 73 | } -------------------------------------------------------------------------------- /main.bro: -------------------------------------------------------------------------------- 1 | ##! Phishing module for bro 2 | 3 | module Phishing; 4 | 5 | export { 6 | ## Used to determine whether this script will analyze SMTP connections 7 | global policy: hook(rec: SMTP::Info); 8 | } --------------------------------------------------------------------------------