├── README.md ├── irc-2.0.bro ├── irc_sessions.bro ├── persistent_talkers.bro ├── rdp-block-scanners.bro ├── shellshock-detailed.bro ├── sip-scan.bro ├── sip-scans.bro ├── sip-shock.bro └── smtp-thresholds.bro /README.md: -------------------------------------------------------------------------------- 1 | # BroCon '15 2 | 3 | In this talk we demonstrate incident detection and analysis with Bro at Berkeley Lab. We will review several incidents over the last year and show how we use Bro to prevent reconnaissance, detect miscreant activity, and perform detailed network forensics. We will also review some of the scripts and capabilities of Bro we have implemented as a results of recent incidents. 4 | This talk is presented by Aashish Sharma & Vincent Stoffer. 5 | 6 | [Brocon'15](https://www.bro.org/brocon2015/brocon2015_abstracts.html#p0wnage-and-detection-withbro) 7 | 8 | [Slides](https://www.bro.org/brocon2015/slides/sharma_p0wnage.pdf) 9 | 10 | ### Scripts: 11 | 12 | * irc-2.0 13 | * irc_sessions 14 | * persistent_talkers 15 | * rdp-block-scanners 16 | * shellshock-detailed 17 | * sip-scan 18 | * sip-scans 19 | * sip-schock 20 | * smtp-thrsholds -------------------------------------------------------------------------------- /irc-2.0.bro: -------------------------------------------------------------------------------- 1 | # $Id: irc.bro 4758 2007-08-10 06:49:23Z vern $ 2 | 3 | module IRC; 4 | 5 | export { 6 | 7 | type irc_user: record { 8 | u_nick: string; # nick name 9 | u_real: string; # real name 10 | u_host: string; # client host 11 | u_channels: set[string]; # channels the user is member of 12 | u_is_operator: bool; # user is server operator 13 | u_conn: connection; # connection handle 14 | }; 15 | 16 | #type irc_channel: record { 17 | redef record irc_channel += { 18 | c_name: string &optional; # channel name 19 | c_users: set[string] &optional; # users in channel 20 | c_ops: set[string] &optional ; # channel operators 21 | c_type: string &optional; # channel type 22 | c_modes: string &optional; # channel modes 23 | c_topic: string &optional; # channel topic 24 | }; 25 | 26 | global expired_user: 27 | function(t: table[string] of irc_user, idx: string): interval; 28 | 29 | global expired_channel: 30 | function(t: table[string] of irc_channel, idx: string): interval; 31 | 32 | # Commands to ignore in irc_request/irc_message. 33 | const ignore_in_other_msgs = { "PING", "PONG", "ISON" } &redef; 34 | 35 | # Return codes to ignore in irc_response 36 | const ignore_in_other_responses: set[count] = { 37 | 303 # RPL_ISON 38 | } &redef; 39 | 40 | # Active users, indexed by nick. 41 | global active_users: table[string] of irc_user &read_expire = 6 hrs &redef ; #&expire_func = expired_user &redef; 42 | 43 | # Active channels, indexed by channel name. 44 | global active_channels: table[string] of irc_channel &read_expire = 6 hrs &redef; #&expire_func = expired_channel &redef; 45 | 46 | # Strings that generate a notice if found in session dialog. 47 | const hot_words = 48 | /.*etc\/shadow.*/ 49 | | /.*etc\/ldap.secret.*/ 50 | | /.*phatbot.*/ 51 | | /.*botnet.*/ 52 | &redef; 53 | 54 | redef enum Notice::Type += { 55 | IRC_HotWord, 56 | }; 57 | } 58 | 59 | # IRC ports. This could be widened to 6660-6669, say. 60 | #redef capture_filters += { ["irc-6666"] = "port 6666" }; 61 | #redef capture_filters += { ["irc-6667"] = "port 6667" }; 62 | 63 | # DPM configuration. 64 | global irc_ports = { 6666/tcp, 6667/tcp } &redef; 65 | #redef dpd_config += { [ANALYZER_IRC] = [$ports = irc_ports] }; 66 | 67 | #redef Weird::weird_action += { 68 | # ["irc_invalid_dcc_message_format"] = Weird::WEIRD_FILE, 69 | # ["irc_invalid_invite_message_format"] = Weird::WEIRD_FILE, 70 | # ["irc_invalid_kick_message_format"] = Weird::WEIRD_FILE, 71 | # ["irc_invalid_line"] = Weird::WEIRD_FILE, 72 | # ["irc_invalid_mode_message_format"] = Weird::WEIRD_FILE, 73 | # ["irc_invalid_names_line"] = Weird::WEIRD_FILE, 74 | # ["irc_invalid_njoin_line"] = Weird::WEIRD_FILE, 75 | # ["irc_invalid_notice_message_format"] = Weird::WEIRD_FILE, 76 | # ["irc_invalid_oper_message_format"] = Weird::WEIRD_FILE, 77 | # ["irc_invalid_privmsg_message_format"] = Weird::WEIRD_FILE, 78 | # ["irc_invalid_reply_number"] = Weird::WEIRD_FILE, 79 | # ["irc_invalid_squery_message_format"] = Weird::WEIRD_FILE, 80 | # ["irc_invalid_who_line"] = Weird::WEIRD_FILE, 81 | # ["irc_invalid_who_message_format"] = Weird::WEIRD_FILE, 82 | # ["irc_invalid_whois_channel_line"] = Weird::WEIRD_FILE, 83 | # ["irc_invalid_whois_message_format"] = Weird::WEIRD_FILE, 84 | # ["irc_invalid_whois_operator_line"] = Weird::WEIRD_FILE, 85 | # ["irc_invalid_whois_user_line"] = Weird::WEIRD_FILE, 86 | # ["irc_line_size_exceeded"] = Weird::WEIRD_FILE, 87 | # ["irc_line_too_short"] = Weird::WEIRD_FILE, 88 | # ["irc_partial_request"] = Weird::WEIRD_FILE, 89 | # ["irc_too_many_invalid"] = Weird::WEIRD_FILE, 90 | #}; 91 | 92 | # # IRC servers to identify server-to-server connections. 93 | # redef irc_servers = { 94 | # # German IRCnet servers 95 | # irc.leo.org, 96 | # irc.fu-berlin.de, 97 | # irc.uni-erlangen.de, 98 | # irc.belwue.de, 99 | # irc.freenet.de, 100 | # irc.tu-ilmenau.de, 101 | # irc.rz.uni-karlsruhe.de, 102 | # }; 103 | 104 | global conn_list: table[conn_id] of count; 105 | global conn_ID = 0; 106 | global check_connection: function(c: connection); 107 | 108 | function irc_check_hot(c: connection, s: string, context: string) 109 | { 110 | if ( s == hot_words ) 111 | NOTICE([$note=IRC_HotWord, $conn=c, 112 | $msg=fmt("IRC hot word in: %s", context)]); 113 | } 114 | 115 | function check_connection(c: connection) 116 | { 117 | if ( c$id !in conn_list ) 118 | { 119 | ++conn_ID; 120 | #append_addl(c, fmt("#%d", conn_ID)); 121 | conn_list[c$id] = conn_ID; 122 | 123 | log_activity(c, fmt("new connection %s", id_string(c$id))); 124 | } 125 | 126 | local conn = get_conn(c); 127 | } 128 | 129 | function check_message(c: connection, source: string, target: string, msg: string, msg_type: string) 130 | { 131 | check_connection(c); 132 | c$irc_detailed$irc_event = fmt ("%s", msg_type); 133 | irc_check_hot(c, msg, msg); 134 | log_activity(c, fmt("%s%s to '%s': %s", msg_type, source != "" ? fmt(" from '%s'", source) : "", target, msg)); 135 | } 136 | 137 | function expired_user(t: table[string] of irc_user, idx: string): interval 138 | { 139 | for ( my_c in active_users[idx]$u_channels ) 140 | { 141 | suspend_state_updates(); 142 | delete active_channels[my_c]$c_users[idx]; 143 | delete active_channels[my_c]$c_ops[idx]; 144 | resume_state_updates(); 145 | } 146 | 147 | return 0 secs; 148 | } 149 | 150 | function expired_channel(t:table[string] of irc_channel, idx: string): interval 151 | { 152 | for ( my_u in active_channels[idx]$c_users ) 153 | if ( my_u in active_users ) 154 | delete active_users[my_u]$u_channels[idx]; 155 | # Else is there a possible state leak? How could it not 156 | # be in active_users? Yet sometimes it isn't, which 157 | # is why we needed to add the above test. 158 | 159 | return 0 secs; 160 | } 161 | 162 | #function log_activity(c: connection, msg: string) 163 | # { 164 | # #print log_file, fmt("%.6f #%s %s", network_time(), conn_list[c$id], msg); 165 | # print fmt("%.6f #%s %s", network_time(), conn_list[c$id], msg); 166 | # } 167 | 168 | event irc_request(c: connection, is_orig: bool, prefix: string, command: string, arguments: string) 169 | { 170 | check_connection(c); 171 | c$irc_detailed$irc_event = fmt ("irc_request"); 172 | 173 | local context = fmt("%s %s", command, arguments); 174 | irc_check_hot(c, command, context); 175 | irc_check_hot(c, arguments, context); 176 | 177 | if ( command !in ignore_in_other_msgs ) 178 | log_activity(c, fmt("other request%s%s: %s", 179 | prefix == "" ? "" : " ", 180 | prefix, context)); 181 | } 182 | 183 | event irc_reply(c: connection, is_orig: bool, prefix: string, code: count, params: string) 184 | { 185 | check_connection(c); 186 | c$irc_detailed$irc_event = fmt ("irc_reply"); 187 | 188 | local context = fmt("%s %s", code, params); 189 | irc_check_hot(c, params, context); 190 | 191 | if ( code !in ignore_in_other_responses ) 192 | log_activity(c, fmt("other response from %s: %s", 193 | prefix, context)); 194 | } 195 | 196 | event irc_message(c: connection, is_orig: bool, prefix: string, command: string, message: string) 197 | { 198 | check_connection(c); 199 | c$irc_detailed$irc_event = fmt ("irc_message"); 200 | 201 | # Sanity checks whether this is indeed IRC. 202 | # 203 | # If we happen to parse an HTTP connection, the server "commands" will 204 | # end with ":". 205 | if ( command == /.*:$/ ) 206 | { 207 | local aid = current_analyzer(); 208 | event protocol_violation(c, Analyzer::ANALYZER_IRC, aid, "broken server response"); 209 | return; 210 | } 211 | 212 | local context = fmt("%s %s", command, message); 213 | irc_check_hot(c, command, context); 214 | irc_check_hot(c, message, context); 215 | 216 | if ( command !in ignore_in_other_msgs ) 217 | log_activity(c, fmt("other server PRIVMSG %s: %s", prefix, context)); 218 | } 219 | 220 | event irc_user_message(c: connection, is_orig: bool, user: string, host: string, server: string, real_name: string) 221 | { 222 | check_connection(c); 223 | c$irc_detailed$irc_event = fmt ("irc_user_message"); 224 | 225 | log_activity(c, fmt("new user, user='%s', host='%s', server='%s', real = '%s'", user, host, server, real_name)); 226 | 227 | if ( user in active_users ) 228 | active_users[user]$u_conn = c; 229 | else 230 | { 231 | local u: irc_user; 232 | u$u_nick = user; 233 | u$u_real = real_name; 234 | u$u_conn = c; 235 | u$u_host = ""; 236 | u$u_is_operator = F; 237 | active_users[user] = u; 238 | } 239 | } 240 | 241 | event irc_quit_message(c: connection, is_orig: bool, nick: string, message: string) 242 | { 243 | check_connection(c); 244 | 245 | c$irc_detailed$irc_event = fmt ("irc_quit_message"); 246 | 247 | log_activity(c, fmt("user '%s' leaving%s", nick, 248 | message == "" ? "" : fmt(", \"%s\"", message))); 249 | 250 | # Remove from lists. 251 | if ( nick in active_users ) 252 | { 253 | delete active_users[nick]; 254 | for ( my_channel in active_channels ) 255 | delete active_channels[my_channel]$c_users[nick]; 256 | } 257 | } 258 | 259 | event irc_privmsg_message(c: connection, is_orig: bool, source: string, target: string, message: string) 260 | { 261 | check_message(c, source, target, message, "message"); 262 | } 263 | 264 | event irc_notice_message(c: connection, is_orig: bool, source: string, target: string, message: string) 265 | { 266 | check_message(c, source, target, message, "notice"); 267 | } 268 | 269 | event irc_squery_message(c: connection, is_orig: bool, source: string, target: string, message: string) 270 | { 271 | check_message(c, source, target, message, "squery"); 272 | } 273 | 274 | event irc_join_message(c: connection, is_orig: bool, info_list: irc_join_list) 275 | { 276 | check_connection(c); 277 | c$irc_detailed$irc_event = fmt ("irc_join_message"); 278 | 279 | for ( l in info_list ) 280 | { 281 | log_activity(c, fmt("user '%s' joined '%s'%s", l$nick, l$channel, l$password != "" ? 282 | fmt("with password '%s'", l$password) : "")); 283 | 284 | if ( l$nick == "" ) 285 | next; 286 | 287 | if ( l$nick in active_users ) 288 | add (active_users[l$nick]$u_channels)[l$channel]; 289 | else 290 | { 291 | local user: irc_user; 292 | user$u_nick = l$nick; 293 | user$u_real = ""; 294 | user$u_conn = c; 295 | user$u_host = ""; 296 | user$u_is_operator = F; 297 | add user$u_channels[l$channel]; 298 | active_users[l$nick] = user; 299 | } 300 | 301 | # Add channel to lists. 302 | if ( l$channel in active_channels ) 303 | add (active_channels[l$channel]$c_users)[l$nick]; 304 | else 305 | { 306 | local my_c: irc_channel; 307 | my_c$c_name = l$channel; 308 | add my_c$c_users[l$nick]; 309 | 310 | my_c$c_type = my_c$c_modes = ""; 311 | 312 | active_channels[l$channel] = my_c; 313 | } 314 | } 315 | } 316 | 317 | event irc_part_message(c: connection, is_orig: bool, nick: string, chans: string_set, message: string) 318 | { 319 | check_connection(c); 320 | c$irc_detailed$irc_event = fmt ("irc_part_message"); 321 | 322 | local channel_str = ""; 323 | for ( ch in chans ) 324 | channel_str = channel_str == "" ? 325 | ch : fmt("%s, %s", channel_str, ch); 326 | 327 | log_activity(c, fmt("%s channel '%s'%s", 328 | nick == "" ? "leaving" : 329 | fmt("user '%s' leaving", nick), 330 | channel_str, 331 | message == "" ? 332 | "" : fmt("with message '%s'", message))); 333 | 334 | # Remove user from channel. 335 | if ( nick == "" ) 336 | return; 337 | 338 | for ( ch in active_channels ) 339 | { 340 | delete (active_channels[ch]$c_users)[nick]; 341 | delete (active_channels[ch]$c_ops)[nick]; 342 | if ( nick in active_users ) 343 | delete (active_users[nick]$u_channels)[ch]; 344 | } 345 | } 346 | 347 | event irc_nick_message(c: connection, is_orig: bool, who: string, newnick: string) 348 | { 349 | check_connection(c); 350 | c$irc_detailed$irc_event = fmt ("irc_nick_message"); 351 | 352 | log_activity(c, fmt("%s nick name to '%s'", 353 | who == "" ? "changing" : fmt("user '%s' changing", who), newnick)); 354 | } 355 | 356 | event irc_invalid_nick(c: connection, is_orig: bool) 357 | { 358 | check_connection(c); 359 | c$irc_detailed$irc_event = fmt ("irc_invalid_nick"); 360 | log_activity(c, "changing nick name failed"); 361 | 362 | } 363 | 364 | event irc_network_info(c: connection, is_orig: bool, users: count, services: count, servers: count) 365 | { 366 | check_connection(c); 367 | c$irc_detailed$irc_event = fmt ("irc_network_info"); 368 | 369 | log_activity(c, fmt("network includes %d users, %d services, %d servers", 370 | users, services, servers)); 371 | } 372 | 373 | event irc_server_info(c: connection, is_orig: bool, users: count, services: count, servers: count) 374 | { 375 | check_connection(c); 376 | c$irc_detailed$irc_event = fmt ("irc_server_info"); 377 | log_activity(c, fmt("server includes %d users, %d services, %d peers", users, services, servers)); 378 | } 379 | 380 | event irc_channel_info(c: connection, is_orig: bool, chans: count) 381 | { 382 | check_connection(c); 383 | c$irc_detailed$irc_event = fmt ("irc_channel_info"); 384 | log_activity(c, fmt("network includes %d channels", chans)); 385 | } 386 | 387 | event irc_who_line(c: connection, is_orig: bool, target_nick: string, channel: string, user: string, host: string, server: string, nick: string, params: string, hops: count, real_name: string) 388 | { 389 | check_connection(c); 390 | c$irc_detailed$irc_event = fmt ("irc_who_line"); 391 | 392 | log_activity(c, fmt("channel '%s' includes '%s' on %s connected to %s with nick '%s', real name '%s', params %s", 393 | channel, user, host, server, 394 | nick, real_name, params)); 395 | 396 | if ( nick == "" || channel == "" ) 397 | return; 398 | 399 | print fmt ("NICK is %s", nick); 400 | 401 | if ( nick in active_users ) 402 | active_users[nick]$u_conn = c; 403 | else 404 | { 405 | local myuser: irc_user; 406 | myuser$u_nick = nick; 407 | myuser$u_real = real_name; 408 | myuser$u_conn = c; 409 | myuser$u_host = host; 410 | myuser$u_is_operator = F; 411 | add myuser$u_channels[channel]; 412 | 413 | active_users[nick] = myuser; 414 | 415 | if ( channel in active_channels ) 416 | ; 417 | ###add (active_channels[channel]$c_users)[nick]; 418 | else 419 | { 420 | local my_c: irc_channel; 421 | my_c$c_name = channel; 422 | ###add my_c$c_users[nick] ; 423 | my_c$c_type = ""; 424 | my_c$c_modes = ""; 425 | 426 | active_channels[channel] = my_c; 427 | } 428 | } 429 | } 430 | 431 | event irc_who_message(c: connection, is_orig: bool, mask: string, oper: bool) 432 | { 433 | check_connection(c); 434 | c$irc_detailed$irc_event = fmt ("irc_who_message"); 435 | 436 | log_activity(c, fmt("WHO with mask %s%s", mask, 437 | oper ? ", only operators" : "")); 438 | } 439 | 440 | event irc_whois_message(c: connection, is_orig: bool, server: string, users: string) 441 | { 442 | check_connection(c); 443 | c$irc_detailed$irc_event = fmt ("irc_whois_message"); 444 | 445 | log_activity(c, fmt("WHOIS%s for user(s) %s", 446 | server == "" ? 447 | server : fmt(" to server %s", server), 448 | users)); 449 | } 450 | 451 | event irc_whois_user_line(c: connection, is_orig: bool, nick: string, user: string, host: string, real_name: string) 452 | { 453 | check_connection(c); 454 | c$irc_detailed$irc_event = fmt ("irc_whois_user_line"); 455 | 456 | log_activity(c, fmt("user '%s' with nick '%s' on host %s has real name '%s'", 457 | user, nick, host, real_name)); 458 | 459 | if ( nick in active_users ) 460 | { 461 | active_users[nick]$u_real = real_name; 462 | active_users[nick]$u_host = host; 463 | } 464 | else 465 | { 466 | local u: irc_user; 467 | u$u_nick = nick; 468 | u$u_real = real_name; 469 | u$u_conn = c; 470 | u$u_host = host; 471 | u$u_is_operator = F; 472 | 473 | active_users[nick] = u; 474 | } 475 | } 476 | 477 | event irc_whois_operator_line(c: connection, is_orig: bool, nick: string) 478 | { 479 | check_connection(c); 480 | c$irc_detailed$irc_event = fmt ("irc_whois_operator"); 481 | log_activity(c, fmt("user '%s' is an IRC operator", nick)); 482 | 483 | if ( nick in active_users ) 484 | active_users[nick]$u_is_operator = T; 485 | else 486 | { 487 | local u: irc_user; 488 | u$u_nick = nick; 489 | u$u_real = ""; 490 | u$u_conn = c; 491 | u$u_host = ""; 492 | u$u_is_operator = T; 493 | 494 | active_users[nick] = u; 495 | } 496 | } 497 | 498 | event irc_whois_channel_line(c: connection, is_orig: bool, nick: string, chans: string_set) 499 | { 500 | check_connection(c); 501 | c$irc_detailed$irc_event = fmt ("irc_whois_channel"); 502 | 503 | local message = fmt("user '%s' is on channels:", nick); 504 | for ( channel in chans ) 505 | message = fmt("%s %s", message, channel); 506 | 507 | log_activity(c, message); 508 | 509 | if ( nick in active_users ) 510 | { 511 | for ( ch in chans ) 512 | add active_users[nick]$u_channels[ch]; 513 | } 514 | else 515 | { 516 | local u: irc_user; 517 | u$u_nick = nick; 518 | u$u_real = ""; 519 | u$u_conn = c; 520 | u$u_host = ""; 521 | u$u_is_operator = F; 522 | u$u_channels = chans; 523 | 524 | active_users[nick] = u; 525 | } 526 | 527 | for ( ch in chans ) 528 | { 529 | if ( ch in active_channels ) 530 | add (active_channels[ch]$c_users)[nick]; 531 | else 532 | { 533 | local my_c: irc_channel; 534 | my_c$c_name = ch; 535 | add my_c$c_users[nick]; 536 | my_c$c_type = ""; 537 | my_c$c_modes = ""; 538 | 539 | active_channels[ch] = my_c; 540 | } 541 | } 542 | } 543 | 544 | event irc_oper_message(c: connection, is_orig: bool, user: string, password: string) 545 | { 546 | check_connection(c); 547 | c$irc_detailed$irc_event = fmt ("irc_oper_message"); 548 | log_activity(c, fmt("user requests operator status with name '%s', password '%s'", 549 | user, password)); 550 | } 551 | 552 | event irc_oper_response(c: connection, is_orig: bool, got_oper: bool) 553 | { 554 | check_connection(c); 555 | c$irc_detailed$irc_event = fmt ("irc_oper_response"); 556 | log_activity(c, fmt("user %s operator status", 557 | got_oper ? "received" : "did not receive")); 558 | } 559 | 560 | event irc_kick_message(c: connection, is_orig: bool, prefix: string, chans: string, users: string, comment: string) 561 | { 562 | check_connection(c); 563 | c$irc_detailed$irc_event = fmt ("irc_kick_message"); 564 | log_activity(c, fmt("user '%s' requested to kick '%s' from channel(s) %s with comment %s", 565 | prefix, users, chans, comment)); 566 | } 567 | 568 | event irc_error_message(c: connection, is_orig: bool, prefix: string, message: string) 569 | { 570 | check_connection(c); 571 | c$irc_detailed$irc_event = fmt ("irc_error_message"); 572 | log_activity(c, fmt("error message%s: %s", 573 | prefix == "" ? "" : fmt("from '%s'", prefix), 574 | message)); 575 | } 576 | 577 | event irc_invite_message(c: connection, is_orig: bool, prefix: string, nickname: string, channel: string) 578 | { 579 | check_connection(c); 580 | c$irc_detailed$irc_event = fmt ("irc_invite_message"); 581 | log_activity(c, fmt("'%s' invited to channel %s%s", 582 | nickname, channel, 583 | prefix == "" ? "" : fmt(" by %s", prefix))); 584 | } 585 | 586 | event irc_mode_message(c: connection, is_orig: bool, prefix: string, params: string) 587 | { 588 | check_connection(c); 589 | c$irc_detailed$irc_event = fmt ("irc_mode_message"); 590 | log_activity(c, fmt("mode command%s: %s", 591 | prefix == "" ? "" : fmt(" from '%s'", prefix), 592 | params)); 593 | } 594 | 595 | event irc_squit_message(c: connection, is_orig: bool, prefix: string, server: string, message: string) 596 | { 597 | check_connection(c); 598 | c$irc_detailed$irc_event = fmt ("irc_squit_message"); 599 | 600 | log_activity(c, fmt("server disconnect attempt%s for %s with comment %s", 601 | prefix == "" ? "" : fmt(" from '%s'", prefix), 602 | server, message)); 603 | } 604 | 605 | event irc_names_info(c: connection, is_orig: bool, c_type: string, channel: string, users: string_set) 606 | { 607 | check_connection(c); 608 | c$irc_detailed$irc_event = fmt ("irc_names_info"); 609 | 610 | local chan_type = 611 | c_type == "@" ? "secret" : 612 | (c_type == "*" ? "private" : "public"); 613 | 614 | local message = fmt("channel '%s' (%s) contains users:", 615 | channel, chan_type); 616 | 617 | for ( user in users ) 618 | message = fmt("%s %s", message, user); 619 | 620 | log_activity(c, message); 621 | 622 | if ( channel in active_channels ) 623 | { 624 | for ( u in users ) 625 | add (active_channels[channel]$c_users)[u]; 626 | } 627 | else 628 | { 629 | local my_c: irc_channel; 630 | my_c$c_name = channel; 631 | my_c$c_users = users; 632 | my_c$c_type = ""; 633 | my_c$c_modes = ""; 634 | 635 | active_channels[channel] = my_c; 636 | } 637 | 638 | for ( nick in users ) 639 | { 640 | if ( nick in active_users ) 641 | add (active_users[nick]$u_channels)[channel]; 642 | else 643 | { 644 | local usr: irc_user; 645 | usr$u_nick = nick; 646 | usr$u_real = ""; 647 | usr$u_conn = c; 648 | usr$u_host = ""; 649 | usr$u_is_operator = F; 650 | add usr$u_channels[channel]; 651 | 652 | active_users[nick] = usr; 653 | } 654 | } 655 | } 656 | 657 | event irc_dcc_message(c: connection, is_orig: bool, prefix: string, target: string, dcc_type: string, argument: string, address: addr, dest_port: count, size: count) 658 | { 659 | check_connection(c); 660 | c$irc_detailed$irc_event = fmt ("irc_dcc_message"); 661 | 662 | log_activity(c, fmt("DCC %s invitation for '%s' to host %s on port %s%s", 663 | dcc_type, target, address, dest_port, 664 | dcc_type == "SEND" ? 665 | fmt(" (%s: %s bytes)", argument, size) : 666 | "")); 667 | } 668 | 669 | event irc_channel_topic(c: connection, is_orig: bool, channel: string, topic: string) 670 | { 671 | check_connection(c); 672 | c$irc_detailed$irc_event = fmt ("irc_channel_topic"); 673 | log_activity(c, fmt("topic for %s is '%s'", channel, topic)); 674 | } 675 | 676 | event irc_password_message(c: connection, is_orig: bool, password: string) 677 | { 678 | check_connection(c); 679 | c$irc_detailed$irc_event = fmt ("irc_password_message"); 680 | log_activity(c, fmt("password %s", password)); 681 | } 682 | 683 | event connection_state_remove(c: connection) 684 | { 685 | delete conn_list[c$id]; 686 | } 687 | -------------------------------------------------------------------------------- /irc_sessions.bro: -------------------------------------------------------------------------------- 1 | # $Id: irc.bro 4758 2007-08-10 06:49:23Z vern $ 2 | 3 | module IRC; 4 | 5 | export 6 | { 7 | 8 | #global detailed_log = open_log_file("irc.detailed") &redef; 9 | global bot_log = open_log_file("irc-bots") &redef; 10 | 11 | redef enum Log::ID += { Sessions_LOG }; 12 | redef enum Notice::Type += { 13 | IrcBotServerFound, 14 | IrcBotClientFound, 15 | }; 16 | 17 | global summary_interval = 1 min &redef; 18 | global detailed_logging = T &redef; 19 | global content_dir = "irc-bots" &redef; 20 | global bot_nicks = 21 | /^\[([^\]]+\|)+[0-9]{2,}]/ # [DEU|XP|L|00] 22 | | /^\[[^ ]+\]([^ ]+\|)+([0-9a-zA-Z-]+)/ # [0]CHN|3436036 [DEU][1]3G-QE 23 | | /^DCOM[0-9]+$/ # DCOM7845 24 | | /^\{[A-Z]+\}-[0-9]+/ # {XP}-5021040 25 | | /^\[[0-9]+-[A-Z0-9]+\][a-z]+/ # [0058-X2]wpbnlgwf 26 | | /^\[[a-zA-Z0-9]\]-[a-zA-Z0-9]+$/ # [SD]-743056826 27 | | /^[a-z]+[A-Z]+-[0-9]{5,}$/ 28 | | /^[A-Z]{3}-[0-9]{4}/ # ITD-1119 29 | ; 30 | 31 | global bot_cmds = 32 | /(^| *)[.?#!][^ ]{0,5}(scan|ndcass|download|cvar\.|execute|update|dcom|asc|scanall) / 33 | | /(^| +\]\[ +)\* (ipscan|wormride)/ 34 | | /(^| *)asn1/ 35 | ; 36 | 37 | global skip_msgs = 38 | /.*AUTH .*/ 39 | | /.*\*\*\* Your host is .*/ 40 | | /.*\*\*\* If you are having problems connecting .*/ 41 | ; 42 | 43 | type irc_channel: record { 44 | name: string &optional ; 45 | passwords: set[string] &optional; 46 | topic: string &default=""; 47 | topic_history: vector of string &optional; 48 | } &redef; 49 | 50 | type bot_client: record { 51 | host: addr; 52 | p: port; 53 | nick: string &default=""; 54 | user: string &default=""; 55 | realname: string &default=""; 56 | channels: table[string] of irc_channel; 57 | servers: set[addr] &optional; 58 | first_seen: time; 59 | last_seen: time; 60 | }; 61 | 62 | type bot_server: record { 63 | host: addr; 64 | p: set[port]; 65 | clients: table[addr] of bot_client; 66 | global_users: string &default=""; 67 | passwords: set[string]; 68 | channels: table[string] of irc_channel; 69 | first_seen: time; 70 | last_seen: time; 71 | }; 72 | 73 | type bot_conn: record { 74 | client: bot_client; 75 | server: bot_server; 76 | conn: connection; 77 | fd: file; 78 | ircx: bool &default=F; 79 | }; 80 | 81 | # We keep three sets of clients/servers: 82 | # (1) tables containing all IRC clients/servers 83 | # (2) sets containing potential bot hosts 84 | # (3) sets containing confirmend bot hosts 85 | # 86 | # Hosts are confirmed when a connection is established between 87 | # potential bot hosts. 88 | # 89 | # FIXME: (1) should really be moved into the general IRC script. 90 | 91 | global expire_server: 92 | function(t: table[addr] of bot_server, idx: addr): interval; 93 | 94 | global expire_client: 95 | function(t: table[addr] of bot_client, idx: addr): interval; 96 | 97 | global servers: table[addr] of bot_server &write_expire=24 hrs; #&expire_func=expire_server ;#&persistent; 98 | global clients: table[addr] of bot_client &write_expire=24 hrs; #&expire_func=expire_client ;#&persistent; 99 | 100 | global potential_bot_clients: set[addr] ;#&persistent; 101 | global potential_bot_servers: set[addr] ;#&persistent; 102 | global confirmed_bot_clients: set[addr] ;#&persistent; 103 | global confirmed_bot_servers: set[addr] ;#&persistent; 104 | 105 | # All IRC connections. 106 | global conns: table[conn_id] of bot_conn ;#&persistent; 107 | 108 | # Connections between confirmed hosts. 109 | global bot_conns: set[conn_id] ;#&persistent; 110 | 111 | # Helper functions for readable output. 112 | global strset_to_str: function(s: set[string]) : string; 113 | global portset_to_str: function(s: set[port]) : string; 114 | global addrset_to_str: function(s: set[addr]) : string; 115 | 116 | type ircInfo:record 117 | { 118 | ts: time &log; 119 | ## Unique ID for the connection. 120 | uid: string &log; 121 | ## Connection details. 122 | id: conn_id &log; 123 | irc_event: string &log &optional; 124 | message: string &log &optional; 125 | 126 | #irc_conn: string &log &optional; 127 | #host: string &log &optional; 128 | #server: string &log &optional; 129 | #channel: string &log &optional ; 130 | #nick: string &log &optional; 131 | #code: count &log &optional; 132 | #command: string &log &optional; 133 | #arguments: string &log &optional; 134 | #real_name: string &log &optional; 135 | #who: string &log &optional ; 136 | #params: string &log &optional ; 137 | #source: string &log &optional; 138 | #target: string &log &optional; 139 | #user: string &log &optional; 140 | #who: string &log &optional; 141 | 142 | }; 143 | 144 | } 145 | 146 | global urls: set[string] &read_expire = 7 days ;#&persistent; 147 | 148 | redef record connection += { 149 | irc_detailed: ircInfo &optional; 150 | }; 151 | 152 | function detailed_new_session(c: connection): ircInfo 153 | { 154 | 155 | local info: ircInfo; 156 | info$ts = network_time(); 157 | info$uid = c$uid; 158 | info$id = c$id; 159 | return info; 160 | 161 | } 162 | 163 | function detailed_set_session(c: connection) 164 | { 165 | 166 | #print fmt ("\nIn detailed_set_session : bot_conn: %s\n", i_conn); 167 | 168 | local info: ircInfo; 169 | 170 | if ( ! c?$irc_detailed) 171 | c$irc_detailed = detailed_new_session(c); 172 | 173 | c$irc_detailed$ts=network_time(); 174 | 175 | #c$irc_detailed$irc_conn = i_conn; 176 | #c$irc_detailed$message = fmt ("%s", i_conn); 177 | 178 | #print fmt ("irc_detailed for %s: %s", c$id$orig_h, c$irc_detailed); 179 | 180 | } 181 | 182 | function log_activity(c: connection, msg: string) 183 | { 184 | 185 | if ( ! c?$irc_detailed) 186 | c$irc_detailed = detailed_new_session(c); 187 | 188 | if (msg != "") 189 | c$irc_detailed$message= msg; 190 | 191 | Log::write(IRC::Sessions_LOG, c$irc_detailed); 192 | 193 | } 194 | 195 | function do_log_force(c: connection, msg: string) 196 | { 197 | 198 | # print fmt ("inside do_log_force: %s", msg); 199 | 200 | if (msg != "") 201 | c$irc_detailed$message=msg ; 202 | 203 | #print fmt ("c$irc_detailed: %s", c$irc_detailed); 204 | 205 | Log::write(IRC::Sessions_LOG, c$irc_detailed); 206 | 207 | } 208 | 209 | function do_log(c: connection, msg: string) 210 | { 211 | 212 | if ( c$id !in bot_conns ) 213 | return; 214 | 215 | do_log_force(c, msg); 216 | 217 | } 218 | 219 | function log_msg(c: connection, cmd: string, prefix: string, msg: string) 220 | { 221 | if ( skip_msgs in msg ) 222 | return; 223 | 224 | do_log(c, fmt("MSG command=%s prefix=%s msg=\"%s\"", cmd, prefix, msg)); 225 | } 226 | 227 | function strset_to_str(s: set[string]) : string 228 | { 229 | if ( |s| == 0 ) 230 | return ""; 231 | 232 | local r = ""; 233 | for ( i in s ) 234 | { 235 | if ( r != "" ) 236 | r = cat(r, ","); 237 | r = cat(r, fmt("\"%s\"", i)); 238 | } 239 | 240 | return r; 241 | } 242 | 243 | function portset_to_str(s: set[port]) : string 244 | { 245 | if ( |s| == 0 ) 246 | return ""; 247 | 248 | local r = ""; 249 | for ( i in s ) 250 | { 251 | if ( r != "" ) 252 | r = cat(r, ","); 253 | r = cat(r, fmt("%d", i)); 254 | } 255 | 256 | return r; 257 | } 258 | 259 | function addrset_to_str(s: set[addr]) : string 260 | { 261 | if ( |s| == 0 ) 262 | return ""; 263 | 264 | local r = ""; 265 | for ( i in s ) 266 | { 267 | if ( r != "" ) 268 | r = cat(r, ","); 269 | r = cat(r, fmt("%s", i)); 270 | } 271 | 272 | return r; 273 | } 274 | 275 | function fmt_time(t: time) : string 276 | { 277 | return strftime("%y-%m-%d-%H-%M-%S", t); 278 | } 279 | 280 | function update_timestamps(c: connection) : bot_conn 281 | { 282 | local conn = conns[c$id]; 283 | 284 | conn$client$last_seen = network_time(); 285 | conn$server$last_seen = network_time(); 286 | 287 | # To prevent the set of entries from premature expiration, 288 | # we need to make a write access (can't use read_expire as we 289 | # iterate over the entries on a regular basis). 290 | clients[c$id$orig_h] = conn$client; 291 | servers[c$id$resp_h] = conn$server; 292 | 293 | return conn; 294 | } 295 | 296 | function add_server(c: connection) : bot_server 297 | { 298 | local s_h = c$id$resp_h; 299 | 300 | if ( s_h in servers ) 301 | return servers[s_h]; 302 | 303 | local empty_table1: table[addr] of bot_client; 304 | local empty_table2: table[string] of irc_channel; 305 | local empty_set: set[string]; 306 | local empty_set2: set[port]; 307 | 308 | local server = [$host=s_h, $p=empty_set2, $clients=empty_table1, 309 | $channels=empty_table2, $passwords=empty_set, 310 | $first_seen=network_time(), $last_seen=network_time()]; 311 | servers[s_h] = server; 312 | 313 | return server; 314 | } 315 | 316 | function add_client(c: connection) : bot_client 317 | { 318 | local c_h = c$id$orig_h; 319 | 320 | if ( c_h in clients ) 321 | return clients[c_h]; 322 | 323 | local empty_table: table[string] of irc_channel; 324 | local empty_set: set[addr]; 325 | local client = [$host=c_h, $p=c$id$resp_p, $servers=empty_set, 326 | $channels=empty_table, $first_seen=network_time(), 327 | $last_seen=network_time()]; 328 | clients[c_h] = client; 329 | 330 | return client; 331 | } 332 | 333 | function check_bot_conn(c: connection) 334 | { 335 | if ( c$id in bot_conns ) 336 | return; 337 | 338 | local client = c$id$orig_h; 339 | local server = c$id$resp_h; 340 | 341 | if ( client !in potential_bot_clients || server !in potential_bot_servers ) 342 | return; 343 | 344 | # New confirmed bot_conn. 345 | 346 | add bot_conns[c$id]; 347 | 348 | if ( server !in confirmed_bot_servers ) 349 | { 350 | NOTICE([$note=IrcBotServerFound, $src=server, $p=c$id$resp_p, $conn=c, 351 | $msg=fmt("ircbot server found: %s:%d", server, $p=c$id$resp_p)]); 352 | add confirmed_bot_servers[server]; 353 | } 354 | 355 | if ( client !in confirmed_bot_clients ) 356 | { 357 | NOTICE([$note=IrcBotClientFound, $src=client, $p=c$id$orig_p, $conn=c, 358 | $msg=fmt("ircbot client found: %s:%d", client, $p=c$id$orig_p)]); 359 | add confirmed_bot_clients[client]; 360 | } 361 | } 362 | 363 | function get_conn(c: connection) : bot_conn 364 | { 365 | 366 | local conn: bot_conn; 367 | 368 | #print fmt ("In get_conn : %s",c ); 369 | 370 | if ( c$id in conns ) 371 | { 372 | check_bot_conn(c); 373 | return update_timestamps(c); 374 | } 375 | 376 | local c_h = c$id$orig_h; 377 | local s_h = c$id$resp_h; 378 | 379 | local client : bot_client; 380 | local server : bot_server; 381 | 382 | if ( c_h in clients ) 383 | client = clients[c_h]; 384 | else 385 | client = add_client(c); 386 | 387 | if ( s_h in servers ) 388 | server = servers[s_h]; 389 | else 390 | server = add_server(c); 391 | 392 | server$clients[c_h] = client; 393 | add server$p[c$id$resp_p]; 394 | add client$servers[s_h]; 395 | 396 | conn$server = server; 397 | conn$client = client; 398 | conn$conn = c; 399 | conns[c$id] = conn; 400 | update_timestamps(c); 401 | 402 | detailed_set_session(c); 403 | 404 | return conn; 405 | } 406 | 407 | function expire_server(t: table[addr] of bot_server, idx: addr): interval 408 | { 409 | local server = t[idx]; 410 | for ( c in server$clients ) 411 | { 412 | local client = server$clients[c]; 413 | delete client$servers[idx]; 414 | } 415 | 416 | delete potential_bot_servers[idx]; 417 | delete confirmed_bot_servers[idx]; 418 | return 0secs; 419 | } 420 | 421 | function expire_client(t: table[addr] of bot_client, idx: addr): interval 422 | { 423 | local client = t[idx]; 424 | for ( s in client$servers ) 425 | if ( s in servers ) 426 | delete servers[s]$clients[idx]; 427 | delete potential_bot_clients[idx]; 428 | delete confirmed_bot_clients[idx]; 429 | return 0secs; 430 | } 431 | 432 | function remove_connection(c: connection) 433 | { 434 | local conn = conns[c$id]; 435 | delete conns[c$id]; 436 | delete bot_conns[c$id]; 437 | } 438 | 439 | function get_channel(conn: bot_conn, channel: string) : irc_channel 440 | { 441 | if ( channel in conn$server$channels ) 442 | return conn$server$channels[channel]; 443 | else 444 | { 445 | local empty_set: set[string]; 446 | local empty_vec: vector of string; 447 | local ch = [$name=channel, $passwords=empty_set, $topic_history=empty_vec]; 448 | conn$server$channels[ch$name] = ch; 449 | return ch; 450 | } 451 | } 452 | 453 | event bro_init() &priority=5 454 | { 455 | 456 | if ( summary_interval != 0 secs ) 457 | #schedule summary_interval { print_bot_state() }; 458 | 459 | Log::create_stream(IRC::Sessions_LOG, [$columns=ircInfo]); 460 | 461 | } 462 | 463 | event bro_init() 464 | { 465 | #set_buf(detailed_log, F); 466 | set_buf(bot_log, F); 467 | } 468 | 469 | event irc_user_message(c: connection , is_orig: bool , user: string , host: string , server: string , real_name: string ) 470 | { 471 | 472 | local conn = get_conn(c); 473 | 474 | c$irc_detailed$irc_event = fmt ("irc_user_message"); 475 | conn$client$user = user; 476 | conn$client$realname = real_name; 477 | 478 | do_log(c, fmt("USER user=%s host=%s server=%s real_name=%s", user, host, server, real_name)); 479 | 480 | } 481 | 482 | event irc_join_message(c: connection, is_orig:bool, info_list: irc_join_list) 483 | { 484 | local conn = get_conn(c); 485 | 486 | for ( i in info_list ) 487 | { 488 | local ch = get_channel(conn, i$channel); 489 | if ( i$password != "" ) 490 | add ch$passwords[i$password]; 491 | 492 | conn$client$channels[ch$name] = ch; 493 | 494 | do_log(c, fmt("irc_join_message: JOIN channel=%s password=%s", i$channel, i$password)); 495 | } 496 | } 497 | 498 | event irc_channel_topic(c: connection, is_orig: bool, channel: string, topic: string) 499 | { 500 | if ( bot_cmds in topic ) 501 | { 502 | do_log_force(c, fmt("irc_channel_topic: Matching TOPIC %s", topic)); 503 | add potential_bot_servers[c$id$resp_h]; 504 | } 505 | 506 | local conn = get_conn(c); 507 | 508 | local ch = get_channel(conn, channel); 509 | ch$topic_history[|ch$topic_history| + 1] = ch$topic; 510 | ch$topic = topic; 511 | 512 | if ( c$id in bot_conns ) 513 | { 514 | do_log(c, fmt("irc_channel_topic: TOPIC channel=%s topic=\"%s\"", channel, topic)); 515 | 516 | local s = split(topic, / /); 517 | for ( i in s ) 518 | { 519 | local w = s[i]; 520 | if ( w == /[a-zA-Z]+:\/\/.*/ ) 521 | { 522 | add urls[w]; 523 | do_log(c, fmt("irc_channel_topic: URL channel=%s url=\"%s\"", 524 | channel, w)); 525 | } 526 | } 527 | } 528 | } 529 | 530 | event irc_nick_message(c: connection, is_orig: bool, who: string, newnick: string) 531 | { 532 | if ( bot_nicks in newnick ) 533 | { 534 | do_log_force(c, fmt("irc_nick_message: Matching NICK %s", newnick)); 535 | add potential_bot_clients[c$id$orig_h]; 536 | } 537 | 538 | local conn = get_conn(c); 539 | conn$client$nick = newnick; 540 | 541 | do_log(c, fmt("irc_nick_message: NICK who=%s nick=%s", who, newnick)); 542 | } 543 | 544 | event irc_password_message(c: connection, is_orig: bool, password: string) 545 | { 546 | local conn = get_conn(c); 547 | add conn$server$passwords[password]; 548 | 549 | do_log(c, fmt("irc_password_message: PASS password=%s", password)); 550 | } 551 | 552 | event irc_privmsg_message(c: connection, is_orig: bool, source: string, target: string, message: string) 553 | { 554 | log_msg(c, "privmsg", source, fmt("irc_privmsg_message: ->%s %s", target, message)); 555 | } 556 | 557 | event irc_notice_message(c: connection, is_orig: bool, source: string, target: string, message: string) 558 | { 559 | log_msg(c, "notice", source, fmt("irc_notice_message: ->%s %s", target, message)); 560 | } 561 | 562 | event irc_global_users(c: connection, is_orig: bool, prefix: string, msg: string) 563 | { 564 | local conn = get_conn(c); 565 | 566 | # Better would be to parse the message to extract the counts. 567 | conn$server$global_users = msg; 568 | 569 | log_msg(c, "globalusers", prefix, msg); 570 | } 571 | 572 | event http_request(c: connection, method: string, original_URI: string, unescaped_URI: string, version: string) 573 | { 574 | if ( original_URI in urls ) 575 | do_log_force(c, fmt("Request for URL %s", original_URI)); 576 | } 577 | 578 | event print_bot_state() 579 | { 580 | local bot_summary_log = open_log_file("irc-bots.summary"); 581 | disable_print_hook(bot_summary_log); 582 | 583 | print bot_summary_log, "---------------------------"; 584 | print bot_summary_log, strftime("%y-%m-%d-%H-%M-%S", network_time()); 585 | print bot_summary_log, "---------------------------"; 586 | print bot_summary_log; 587 | print bot_summary_log, "Known servers"; 588 | 589 | for ( h in confirmed_bot_servers ) 590 | { 591 | local s = servers[h]; 592 | 593 | print bot_summary_log, 594 | fmt(" %s %s - clients: %d ports %s password(s) %s last-seen %s first-seen %s global-users %s", 595 | (Site::is_local_addr(s$host) ? "L" : "R"), 596 | s$host, |s$clients|, portset_to_str(s$p), 597 | strset_to_str(s$passwords), 598 | fmt_time(s$last_seen), fmt_time(s$first_seen), 599 | s$global_users); 600 | 601 | for ( name in s$channels ) 602 | { 603 | local ch = s$channels[name]; 604 | print bot_summary_log, 605 | fmt(" channel %s: topic \"%s\", password(s) %s", 606 | ch$name, ch$topic, 607 | strset_to_str(ch$passwords)); 608 | } 609 | } 610 | 611 | print bot_summary_log, "\nKnown clients"; 612 | 613 | for ( h in confirmed_bot_clients ) 614 | { 615 | local c = clients[h]; 616 | print bot_summary_log, 617 | fmt(" %s %s - server(s) %s user %s nick %s realname %s last-seen %s first-seen %s", 618 | (Site::is_local_addr(h) ? "L" : "R"), h, 619 | addrset_to_str(c$servers), 620 | c$user, c$nick, c$realname, 621 | fmt_time(c$last_seen), fmt_time(c$first_seen)); 622 | } 623 | 624 | close(bot_summary_log); 625 | 626 | #if ( summary_interval != 0 secs ) 627 | # schedule summary_interval { print_bot_state() }; 628 | } 629 | 630 | event connection_state_remove(c: connection) 631 | { 632 | if ( c$id !in conns ) 633 | return; 634 | 635 | remove_connection(c); 636 | } 637 | 638 | #event irc_reply(c: connection , is_orig: bool , prefix: string , code: count , params: string ) 639 | # { 640 | # 641 | # local conn = get_conn(c); 642 | # c$irc_detailed$irc_event = fmt ("irc_reply"); 643 | # 644 | # local msg = fmt ("irc_reply: code: %s, params %s", code, params); 645 | # do_log_force (c, msg); 646 | # 647 | # } 648 | 649 | #event irc_client(c: connection, prefix: string, data: string) 650 | # { 651 | # if ( detailed_logging ) 652 | # print detailed_log, fmt("%.6f %s > (%s) %s", network_time(), id_string(c$id), prefix, data); 653 | # 654 | # local conn = get_conn(c); 655 | # 656 | # if ( data == /^ *[iI][rR][cC][xX] *$/ ) 657 | # conn$ircx = T; 658 | # } 659 | 660 | #event irc_server(c: connection, prefix: string, data: string) 661 | # { 662 | # if ( detailed_logging ) 663 | # print detailed_log, fmt("%.6f %s < (%s) %s", network_time(), id_string(c$id), prefix, data); 664 | # 665 | # local conn = get_conn(c); 666 | # } 667 | -------------------------------------------------------------------------------- /persistent_talkers.bro: -------------------------------------------------------------------------------- 1 | 2 | module Persistent ; 3 | 4 | @load base/protocols/conn 5 | @load base/utils/site.bro 6 | @load policy/misc/profiling.bro 7 | 8 | export 9 | { 10 | 11 | global debug = 0 ; 12 | global per_debug = open_log_file("per-debug"); 13 | global table_log = open_log_file("per-table"); 14 | 15 | redef enum Notice::Type += { 16 | ProlongConversation, 17 | ShortDelete, 18 | MediumDelete, 19 | LongDelete, 20 | HastyChitChat, 21 | ProlongChatter, 22 | Table_size, 23 | Table_Delete, 24 | Table_Keep, 25 | New_conn, 26 | scanner, 27 | DurationSpike, 28 | }; 29 | 30 | type Track_Conn_Record: record { 31 | src: addr &optional ; 32 | dst: addr &optional ; 33 | first_seen_time: time &optional ; 34 | last_seen: time &optional ; 35 | inactive_for: interval &optional &default=0 sec; 36 | conn_count: count &default=0; 37 | mean_time_between_conn: interval &default=0 sec; 38 | history: string &optional ; 39 | conn_state: string &optional ; 40 | per_conn_duration: interval ; 41 | 42 | }; 43 | 44 | type a_set: record { 45 | _src: addr; 46 | _dst: addr; 47 | }; 48 | 49 | global idx_set: a_set ; 50 | global table_size_count = 0 ; 51 | global remove_stale_conn : function (t: table[addr,addr] of Track_Conn_Record, idx: any): interval; 52 | global long_connections: table[addr, addr] of Track_Conn_Record &persistent &create_expire= 0 sec &expire_func=remove_stale_conn ; 53 | global mean_time_between_conn: interval ; 54 | global chatty_ports: set[port] = { 53/tcp, 53/udp, 389/tcp }; 55 | global SIGMA = 10 ; 56 | const LONG_DEL_TIME = 10 hrs ; 57 | const MEDIUM_DEL_TIME = 30 secs ; 58 | 59 | redef enum Log::ID += {Conn_LOG}; 60 | 61 | type status : enum { NEW, ONGOING, INACTIVE, EXPIRED, TERMINATED, EXTENDED_INCACHE }; 62 | type Info: record { 63 | ts: time &log ; 64 | src: addr &log &optional ; 65 | dst: addr &log &optional ; 66 | note: Notice::Type &log ; 67 | conn_count: count &log; 68 | conn_status: status &log &default=NEW; 69 | inactive_for: interval &log &optional &default=0 sec; 70 | first_seen_time: string &log; 71 | last_seen: string &log ; 72 | mean_time_between_conn: string &log ; 73 | duration: string &log ; 74 | }; 75 | 76 | global log_table: table[addr,addr] of count &write_expire = 1 hrs &default=0 ; 77 | global bf_scanner: opaque of bloomfilter ; 78 | global conn_history : opaque of bloomfilter ; 79 | 80 | } 81 | 82 | function duration_to_hour_mins_secs(dur: interval): string 83 | { 84 | 85 | if (dur < 0 sec) 86 | return fmt("%dh%dm%ds",0, 0, 0); 87 | local dur_count = double_to_count(interval_to_double(dur)); 88 | local hour = dur_count / 3600 ; 89 | local _mins = dur_count - ((dur_count / 3600) * 3600); 90 | return fmt("%dh%dm%ds",hour, _mins/60, _mins%60); 91 | 92 | } 93 | 94 | function log_persistent(conn_rec:Track_Conn_Record, duration: string, note: Notice::Type, conn_status: status) 95 | { 96 | 97 | local src = conn_rec$src ; 98 | local dst = conn_rec$dst ; 99 | local info: Info; 100 | 101 | info$ts = conn_rec$last_seen; 102 | info$src = src ; 103 | info$dst = dst ; 104 | info$note = note; 105 | info$inactive_for = conn_rec$inactive_for; 106 | info$conn_status = conn_status; 107 | info$first_seen_time = strftime("%y-%m-%d_%H.%M.%S",conn_rec$first_seen_time); 108 | info$last_seen = strftime("%y-%m-%d_%H.%M.%S",conn_rec$last_seen) ; 109 | info$duration = duration ; 110 | info$conn_count = conn_rec$conn_count ; 111 | info$mean_time_between_conn = duration_to_hour_mins_secs(conn_rec$mean_time_between_conn) ; 112 | 113 | if ([src, dst] !in log_table) 114 | { 115 | if (info$conn_count > 3) 116 | Log::write(Persistent::Conn_LOG, info); 117 | log_table[src, dst] = 1; 118 | } 119 | else if (note == ShortDelete || note == LongDelete || note == MediumDelete) 120 | { 121 | Log::write(Persistent::Conn_LOG, info); 122 | } 123 | 124 | } 125 | 126 | function log_expire(conn_rec:Track_Conn_Record, duration: string, note: Notice::Type) 127 | { 128 | } 129 | 130 | function is_failed_conn(c: connection): bool 131 | { 132 | if ( (c$orig$state == TCP_SYN_SENT && c$resp$state == TCP_RESET) 133 | || (((c$orig$state == TCP_RESET && c$resp$state == TCP_SYN_ACK_SENT) || (c$orig$state == TCP_RESET && c$resp$state == TCP_ESTABLISHED && "S" in c$history)) && /[Dd]/ !in c$history) ) 134 | return T; 135 | return F; 136 | } 137 | 138 | function is_reverse_failed_conn(c: connection): bool 139 | { 140 | if ( (c$resp$state == TCP_SYN_SENT && c$orig$state == TCP_RESET) 141 | || (((c$resp$state == TCP_RESET && c$orig$state == TCP_SYN_ACK_SENT) || (c$resp$state == TCP_RESET && c$orig$state == TCP_ESTABLISHED && "s" in c$history)) && /[Dd]/ !in c$history) ) 142 | return T; 143 | return F; 144 | } 145 | 146 | function remove_stale_conn(t: table[addr,addr] of Track_Conn_Record, idx: any): interval 147 | { 148 | 149 | local src: addr; 150 | local dst: addr ; 151 | local delta: interval ; 152 | local dt: double; 153 | local mtbc: double ; 154 | 155 | [src, dst] = idx ; 156 | local nt = network_time(); 157 | 158 | delta = nt - long_connections[src, dst]$last_seen ; 159 | 160 | dt = interval_to_double(delta); 161 | mtbc = interval_to_double((long_connections[src, dst]$mean_time_between_conn)) ; 162 | 163 | local time_diff = duration_to_hour_mins_secs(long_connections[src, dst]$last_seen - long_connections[src, dst]$first_seen_time); 164 | 165 | local bloom_idx = fmt ("%s%s", src, dst); 166 | 167 | local rec: Info ; 168 | 169 | long_connections[src, dst]$inactive_for = network_time () - long_connections[src, dst]$last_seen; 170 | 171 | if ((long_connections[src, dst]$mean_time_between_conn <= 0 secs) && (/S0|OTH/ in long_connections[src, dst]$conn_state) && (bloomfilter_lookup(bf_scanner, bloom_idx) == 0)) 172 | { 173 | bloomfilter_add(bf_scanner, bloom_idx); 174 | log_persistent(long_connections[src, dst], time_diff, scanner, INACTIVE); 175 | return 0 sec ; 176 | } 177 | 178 | if ((/S0|OTH/ in long_connections[src, dst]$conn_state) && delta > MEDIUM_DEL_TIME && long_connections[src, dst]$conn_count == 1 && bloomfilter_lookup(bf_scanner, bloom_idx) <= 3) 179 | { 180 | log_persistent(long_connections[src, dst], time_diff, scanner, INACTIVE); 181 | bloomfilter_add(bf_scanner, bloom_idx); 182 | return 0 secs ; 183 | } 184 | 185 | SIGMA = long_connections[src, dst]$conn_count ; 186 | 187 | if ((dt > (SIGMA * mtbc)) && long_connections[src, dst]$conn_count > 1) 188 | { 189 | log_persistent(long_connections[src, dst], time_diff, ShortDelete, INACTIVE); 190 | bloomfilter_add(bf_scanner, bloom_idx); 191 | if (debug ==1) 192 | print per_debug, fmt ("Deleting : %s", long_connections[src,dst]); 193 | return 0 secs ; 194 | } 195 | 196 | if ( delta > LONG_DEL_TIME) 197 | { 198 | long_connections[src, dst]$inactive_for = network_time () - long_connections[src, dst]$last_seen; 199 | log_persistent(long_connections[src, dst], time_diff, LongDelete, EXPIRED); 200 | if (debug ==1) 201 | print per_debug, fmt ("Deleting : %s", long_connections[src,dst]); 202 | return 0 secs ; 203 | } 204 | 205 | 206 | if (long_connections[src, dst]$mean_time_between_conn == 0 secs && delta > 10 mins) 207 | { 208 | local ret = 30 mins ; 209 | long_connections[src, dst]$inactive_for = network_time () - long_connections[src, dst]$last_seen; 210 | log_persistent(long_connections[src, dst], time_diff, Table_Keep, EXTENDED_INCACHE); 211 | return ret ; 212 | } 213 | else if ( long_connections[src, dst]$mean_time_between_conn > 1 min && long_connections[src, dst]$conn_count > 4) 214 | { 215 | ret = double_to_interval(SIGMA * mtbc) ; 216 | long_connections[src, dst]$inactive_for = network_time () - long_connections[src, dst]$last_seen; 217 | log_persistent(long_connections[src, dst], time_diff, Table_Keep, EXTENDED_INCACHE); 218 | return ret ; 219 | } 220 | else if (long_connections[src, dst]$mean_time_between_conn > 2 hrs) 221 | { 222 | long_connections[src, dst]$inactive_for = network_time () - long_connections[src, dst]$last_seen; 223 | log_persistent(long_connections[src, dst], time_diff, Table_Keep, EXTENDED_INCACHE); 224 | return LONG_DEL_TIME; 225 | } 226 | 227 | ret = 1 min ; 228 | return ret ; 229 | 230 | } 231 | 232 | function debug_log(m: string) 233 | { 234 | return ; 235 | #print per_debug, fmt ("%s, %s", network_time(), m); 236 | #print fmt ("%s", m); 237 | } 238 | 239 | event bro_init() 240 | { 241 | table_size_count=0 ; 242 | #schedule 1 min { print_table_size()} ; 243 | bf_scanner = bloomfilter_counting_init(3, 2000000, 1000, "Persistent"); 244 | conn_history = bloomfilter_basic_init(0.0000001, 4000000, "Persistent"); 245 | Log::create_stream(Persistent::Conn_LOG, [$columns=Info]); 246 | } 247 | 248 | event new_connection(c: connection) &priority=-10 249 | { 250 | } 251 | 252 | event conn_state_expire(c: connection) 253 | { 254 | } 255 | 256 | event connection_state_remove(c: connection) &priority=-10 257 | { 258 | local src = c$id$orig_h; 259 | local dst = c$id$resp_h ; 260 | local msg = "" ; 261 | local time_diff: string; 262 | local bloom_idx=fmt ("%s-%s", src,dst); 263 | if (c$id$resp_p in chatty_ports) 264 | return ; 265 | if (( src in Site::local_nets && dst in Site::local_nets) || src in Site::neighbor_nets || dst in Site::neighbor_nets) 266 | return ; 267 | if (is_reverse_failed_conn(c) || is_failed_conn(c) || ( /[Dd]/ !in c$history)) 268 | { 269 | #print per_debug, fmt ("%s failed-conn: %s, %s, %s", c$conn$ts, c$conn$uid, src, dst ); 270 | return ; 271 | } 272 | 273 | if ([src, dst] !in long_connections) 274 | { 275 | local conn_rec: Track_Conn_Record ; 276 | conn_rec$src = src ; 277 | conn_rec$dst = dst ; 278 | conn_rec$conn_count = 0 ; 279 | conn_rec$first_seen_time = c$start_time ; 280 | conn_rec$last_seen = c$start_time ; 281 | conn_rec$inactive_for=0 sec; 282 | conn_rec$history = c$history ; 283 | conn_rec$conn_state = c$conn$conn_state; 284 | conn_rec$per_conn_duration = c$duration ; 285 | long_connections[src, dst]=conn_rec; 286 | } 287 | 288 | long_connections[src, dst]$conn_count += 1 ; 289 | long_connections[src, dst]$last_seen = c$conn$ts; 290 | time_diff = duration_to_hour_mins_secs(long_connections[src, dst]$last_seen - long_connections[src, dst]$first_seen_time) ; 291 | long_connections[src, dst]$inactive_for = network_time () - long_connections[src, dst]$last_seen ; 292 | long_connections[src, dst]$mean_time_between_conn = (long_connections[src, dst]$last_seen - long_connections[src, dst]$first_seen_time) / long_connections[src, dst]$conn_count ; 293 | 294 | local mtbc = long_connections[src, dst]$mean_time_between_conn ; 295 | local last_seen = long_connections[src, dst]$last_seen ; 296 | local first_seen = long_connections[src, dst]$first_seen_time ; 297 | local conn_count = long_connections[src, dst]$conn_count ; 298 | if (c$duration > 1 msec) 299 | { 300 | 301 | local a = interval_to_double(long_connections[src, dst]$per_conn_duration) ; 302 | 303 | if ((interval_to_double(c$duration) > 10 * interval_to_double(long_connections[src, dst]$per_conn_duration/conn_count)) && conn_count > 10 &&mtbc > 0 sec) 304 | { 305 | msg = fmt ("long connections : %s, %s, duration: %s (%s) ", src, dst, time_diff, long_connections[src, dst]); 306 | #debug_log (fmt ("%s", c)); 307 | #if (debug == 1) 308 | #print per_debug, fmt ("SPIKE :: %s, %s, %s, %s, %s", c$duration, long_connections[src, dst]$per_conn_duration, conn_count, a/conn_count, double_to_interval(a/conn_count)); 309 | NOTICE([$note=DurationSpike, $conn=c, $msg=msg, $identifier=cat(src, dst)]); 310 | } 311 | 312 | long_connections[src, dst]$per_conn_duration += c$duration; 313 | 314 | #long_connections[src, dst]$per_conn_duration += double_to_interval(interval_to_double(long_connections[src, dst]$per_conn_duration+c$duration)/conn_count); 315 | 316 | #if (debug == 1) 317 | # print per_debug, fmt (":: %s, %s, %s, %s, %s", c$duration, long_connections[src, dst]$per_conn_duration, conn_count, a/conn_count, double_to_interval(a/conn_count)); 318 | 319 | } 320 | 321 | #if ( (long_connections[src, dst]$last_seen - long_connections[src, dst]$first_seen_time) > 5 mins) 322 | # { 323 | # log_persistent(long_connections[src, dst], time_diff, ProlongConversation, ONGOING); 324 | # local msg = fmt ("long connections : %s, %s, duration: %s (%s) ", src, dst, time_diff, long_connections[src, dst]); 325 | # NOTICE([$note=ProlongConversation, $conn=c, $msg=msg, $identifier=cat(src, dst)]); 326 | # } 327 | 328 | if (mtbc < 1 sec && mtbc > 0.00001 sec && conn_count > 50) 329 | { 330 | log_persistent(long_connections[src, dst], time_diff, HastyChitChat, ONGOING); 331 | msg = fmt ("HastyChitChat: %s %s, duration: %s (%s) ", src, dst, time_diff, long_connections[src, dst]); 332 | NOTICE([$note=HastyChitChat, $conn=c, $msg=msg, $identifier=cat(src, dst)]); 333 | } 334 | 335 | if (mtbc < 1 sec && mtbc > 0.00001 sec && conn_count > 2500) 336 | { 337 | log_persistent(long_connections[src, dst], time_diff, ProlongChatter, ONGOING); 338 | msg = fmt ("Prolonged_ChitChat: %s %s, duration: %s (%s) ", src, dst, time_diff, long_connections[src, dst]); 339 | NOTICE([$note=ProlongChatter, $conn=c, $msg=msg, $identifier=cat(src, dst)]); 340 | } 341 | 342 | log_persistent(long_connections[src, dst], time_diff, ProlongConversation, ONGOING); 343 | #debug_log (fmt ("%s, %s, %s", c$conn$ts, c$id, c$duration)); 344 | 345 | } 346 | 347 | event connection_state_remove(c: connection) &priority=-10 348 | { 349 | local src = c$id$orig_h; 350 | local dst = c$id$resp_h ; 351 | local msg = "" ; 352 | local time_diff: string; 353 | local bloom_idx=fmt ("%s-%s", src,dst); 354 | if (c$id$resp_p in chatty_ports) 355 | return ; 356 | if (( src in Site::local_nets && dst in Site::local_nets) || src in Site::neighbor_nets || dst in Site::neighbor_nets) 357 | return ; 358 | if (is_reverse_failed_conn(c) || is_failed_conn(c) || ( /[Dd]/ !in c$history)) 359 | { 360 | #print per_debug, fmt ("%s failed-conn: %s, %s, %s", c$conn$ts, c$conn$uid, src, dst ); 361 | return ; 362 | } 363 | 364 | if ([src, dst] !in long_connections) 365 | { 366 | local conn_rec: Track_Conn_Record ; 367 | conn_rec$src = src ; 368 | conn_rec$dst = dst ; 369 | conn_rec$conn_count = 0 ; 370 | conn_rec$first_seen_time = c$start_time ; 371 | conn_rec$last_seen = c$start_time ; 372 | conn_rec$inactive_for=0 sec; 373 | conn_rec$history = c$history ; 374 | conn_rec$conn_state = c$conn$conn_state; 375 | conn_rec$per_conn_duration = c$duration ; 376 | long_connections[src, dst]=conn_rec; 377 | } 378 | 379 | long_connections[src, dst]$conn_count += 1 ; 380 | long_connections[src, dst]$last_seen = c$conn$ts; 381 | time_diff = duration_to_hour_mins_secs(long_connections[src, dst]$last_seen - long_connections[src, dst]$first_seen_time) ; 382 | long_connections[src, dst]$inactive_for = network_time () - long_connections[src, dst]$last_seen ; 383 | long_connections[src, dst]$mean_time_between_conn = (long_connections[src, dst]$last_seen - long_connections[src, dst]$first_seen_time) / long_connections[src, dst]$conn_count ; 384 | 385 | local mtbc = long_connections[src, dst]$mean_time_between_conn ; 386 | local last_seen = long_connections[src, dst]$last_seen ; 387 | local first_seen = long_connections[src, dst]$first_seen_time ; 388 | local conn_count = long_connections[src, dst]$conn_count ; 389 | if (c$duration > 1 msec) 390 | { 391 | 392 | local a = interval_to_double(long_connections[src, dst]$per_conn_duration) ; 393 | 394 | if ((interval_to_double(c$duration) > 10 * interval_to_double(long_connections[src, dst]$per_conn_duration/conn_count)) && conn_count > 10 &&mtbc > 0 sec) 395 | { 396 | msg = fmt ("long connections : %s, %s, duration: %s (%s) ", src, dst, time_diff, long_connections[src, dst]); 397 | #debug_log (fmt ("%s", c)); 398 | #if (debug == 1) 399 | #print per_debug, fmt ("SPIKE :: %s, %s, %s, %s, %s", c$duration, long_connections[src, dst]$per_conn_duration, conn_count, a/conn_count, double_to_interval(a/conn_count)); 400 | NOTICE([$note=DurationSpike, $conn=c, $msg=msg, $identifier=cat(src, dst)]); 401 | } 402 | 403 | long_connections[src, dst]$per_conn_duration += c$duration; 404 | 405 | #long_connections[src, dst]$per_conn_duration += double_to_interval(interval_to_double(long_connections[src, dst]$per_conn_duration+c$duration)/conn_count); 406 | 407 | #if (debug == 1) 408 | # print per_debug, fmt (":: %s, %s, %s, %s, %s", c$duration, long_connections[src, dst]$per_conn_duration, conn_count, a/conn_count, double_to_interval(a/conn_count)); 409 | 410 | } 411 | 412 | #if ( (long_connections[src, dst]$last_seen - long_connections[src, dst]$first_seen_time) > 5 mins) 413 | # { 414 | # log_persistent(long_connections[src, dst], time_diff, ProlongConversation, ONGOING); 415 | # local msg = fmt ("long connections : %s, %s, duration: %s (%s) ", src, dst, time_diff, long_connections[src, dst]); 416 | # NOTICE([$note=ProlongConversation, $conn=c, $msg=msg, $identifier=cat(src, dst)]); 417 | # } 418 | 419 | if (mtbc < 1 sec && mtbc > 0.00001 sec && conn_count > 50) 420 | { 421 | log_persistent(long_connections[src, dst], time_diff, HastyChitChat, ONGOING); 422 | msg = fmt ("HastyChitChat: %s %s, duration: %s (%s) ", src, dst, time_diff, long_connections[src, dst]); 423 | NOTICE([$note=HastyChitChat, $conn=c, $msg=msg, $identifier=cat(src, dst)]); 424 | } 425 | 426 | if (mtbc < 1 sec && mtbc > 0.00001 sec && conn_count > 2500) 427 | { 428 | log_persistent(long_connections[src, dst], time_diff, ProlongChatter, ONGOING); 429 | msg = fmt ("Prolonged_ChitChat: %s %s, duration: %s (%s) ", src, dst, time_diff, long_connections[src, dst]); 430 | NOTICE([$note=ProlongChatter, $conn=c, $msg=msg, $identifier=cat(src, dst)]); 431 | } 432 | 433 | log_persistent(long_connections[src, dst], time_diff, ProlongConversation, ONGOING); 434 | #debug_log (fmt ("%s, %s, %s", c$conn$ts, c$id, c$duration)); 435 | 436 | } 437 | 438 | event bro_done() 439 | { 440 | #print fmt ("Size of table is now %s", |long_connections|); 441 | } 442 | -------------------------------------------------------------------------------- /rdp-block-scanners.bro: -------------------------------------------------------------------------------- 1 | module RDP; 2 | 3 | export 4 | { 5 | 6 | redef enum Notice::Type += { 7 | BruteforceScan, 8 | ScanSummary, 9 | } ; 10 | 11 | global rdp_bruteforcer: table [addr, string] of count &create_expire=1 day &default=0 &redef ; 12 | global rdp_scanners_account = /a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z/ &redef ; 13 | redef rdp_scanners_account += /A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z/ ; 14 | redef rdp_scanners_account += /NCRACK_USER/ ; 15 | 16 | } 17 | 18 | event rdp_connect_request(c: connection, cookie: string) &priority=5 19 | { 20 | 21 | local orig=c$id$orig_h ; 22 | local resp=c$id$resp_h ; 23 | 24 | if (cookie == rdp_scanners_account) 25 | { 26 | if ( [orig,cookie] !in rdp_bruteforcer) 27 | { 28 | rdp_bruteforcer[orig,cookie] = 1 ; 29 | 30 | NOTICE([$note=RDP::BruteforceScan, $src=c$id$orig_h, 31 | $msg=fmt("%s bruteforced %s on RDP (%s) using Account: \"%s\" ", c$id$orig_h, c$id$resp_h, c$id$resp_p, cookie)]); 32 | } 33 | else 34 | rdp_bruteforcer[orig,cookie] += 1 ; 35 | } 36 | 37 | } 38 | 39 | 40 | event bro_done() 41 | { 42 | 43 | for ([scan_addr, cookie] in rdp_bruteforcer) 44 | { 45 | NOTICE([$note=RDP::ScanSummary, $src=scan_addr, 46 | $msg=fmt("%s bruteforced RDP using Account: \"%s\" %s times ", 47 | scan_addr, cookie, |rdp_bruteforcer[scan_addr, cookie]|)]); 48 | } 49 | 50 | } 51 | 52 | -------------------------------------------------------------------------------- /shellshock-detailed.bro: -------------------------------------------------------------------------------- 1 | 2 | module Shellshock; 3 | 4 | @load policy/frameworks/intel/seen 5 | @load policy/frameworks/intel/do_notice 6 | 7 | export { 8 | redef enum Notice::Type += { 9 | ## Indicates that a host may have attempted a bash cgi header attack 10 | Attempt, 11 | Hostile_Domain, 12 | Hostile_URI, 13 | Compromise, 14 | }; 15 | 16 | const url_regex = /^([a-zA-Z\-]{3,5})(:\/\/[^\/?#"'\r\n><]*)([^?#"'\r\n><]*)([^[:blank:]\r\n"'><]*|\??[^"'\r\n><]*)/ &redef; 17 | 18 | type shellshock_MO: record { 19 | victim: addr &optional; 20 | scanner: set[addr] &optional; 21 | web_host: string &optional ; 22 | mal_ips: set[addr] &optional; 23 | c_and_c: set[addr] &optional; 24 | culprit_conn: set[conn_id] &optional; 25 | } ; 26 | 27 | global shellshock_attack: table[string] of shellshock_MO; 28 | } 29 | 30 | function find_all_urls(s: string): string_set 31 | { 32 | return find_all(s, url_regex); 33 | } 34 | 35 | function extract_host(name: string): string 36 | { 37 | local split_on_slash = split(name, /\//); 38 | local num_slash = |split_on_slash|; 39 | return split_on_slash[3]; 40 | } 41 | 42 | event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count) &priority=10 43 | { 44 | 45 | if (query in shellshock_attack) 46 | { 47 | NOTICE([$note=Shellshock::Hostile_Domain, 48 | $conn=c, 49 | $msg=fmt("ShellShock Hostile domain seen %s=%s [%s]", c$id$orig_h, c$id$resp_h, query), 50 | $identifier=c$uid]); 51 | 52 | add shellshock_attack[query]$mal_ips[c$id$resp_h]; 53 | } 54 | 55 | } 56 | 57 | event dns_A_reply(c: connection, msg: dns_msg, ans: dns_answer, a: addr) &priority=5 58 | { 59 | print fmt ("DNS_A_REPLY: ans: %s, address: %s", msg, a); 60 | } 61 | 62 | event http_request(c: connection, method: string, original_URI: string, unescaped_URI: string, version: string) &priority=5 63 | { 64 | } 65 | 66 | event http_header(c: connection, is_orig: bool, name: string, value: string) &priority=3 67 | { 68 | if (is_orig) 69 | { 70 | if (/\x28\x29\x20\x7b\x20/ in value) 71 | { 72 | 73 | NOTICE([$note=Shellshock::Attempt, 74 | $conn=c, 75 | $msg=fmt("CVE-2014-6271: %s - %s submitting %s=%s",c$id$orig_h, c$id$resp_h, name, value), 76 | $identifier=c$uid]); 77 | 78 | if (/http/ !in value) 79 | return ; 80 | 81 | local links = find_all_urls(value); 82 | for (a in links) 83 | { 84 | local cmd = fmt ("%s", a); 85 | local uri = split(cmd,/ /); 86 | for (b in uri) 87 | { 88 | if (/http/ in uri[b]) 89 | { 90 | local domain = extract_host(uri[b]); 91 | if (domain !in shellshock_attack) 92 | { 93 | local rec: shellshock_MO; 94 | 95 | shellshock_attack[domain] = rec; 96 | shellshock_attack[domain]$web_host = domain; 97 | 98 | shellshock_attack[domain]$culprit_conn= set(); 99 | add shellshock_attack[domain]$culprit_conn[c$id]; 100 | 101 | shellshock_attack[domain]$mal_ips= set(); 102 | add shellshock_attack[domain]$mal_ips[c$id$orig_h]; 103 | shellshock_attack[domain]$victim= c$id$resp_h; 104 | } 105 | 106 | local m_item: Intel::Item = [$indicator=domain, $indicator_type = Intel::DOMAIN, $meta = [$source = "Shellshock_Script",$do_notice = T] ]; 107 | Intel::insert(m_item); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | event http_message_done(c: connection, is_orig: bool, stat: http_message_stat) 116 | { 117 | 118 | if (c$http$host in shellshock_attack ) 119 | { 120 | local vuln_url = HTTP::build_url_http(c$http); 121 | local domain = c$http$host ; 122 | 123 | NOTICE([$note=Shellshock::Hostile_URI, 124 | $conn=c, 125 | $msg=fmt("ShellShock Hostile domain seen %s=%s [%s]",c$id$orig_h, c$id$resp_h, c$http$host), 126 | $identifier=c$uid, 127 | $suppress_for=1 min]); 128 | 129 | #print fmt ("mal_ips: %s", shellshock_attack[domain]$mal_ips); 130 | 131 | if (c$id$orig_h == shellshock_attack[domain]$victim) 132 | { 133 | NOTICE([$note=Shellshock::Compromise , 134 | $conn=c, 135 | $msg=fmt("ShellShock compromise: %s=%s [%s]",c$id$orig_h, c$id$resp_h, vuln_url ), 136 | $identifier=c$uid]); 137 | } 138 | } 139 | } 140 | 141 | event bro_done() 142 | { 143 | print fmt ("%s", shellshock_attack); 144 | } 145 | -------------------------------------------------------------------------------- /sip-scan.bro: -------------------------------------------------------------------------------- 1 | @load base/utils/files 2 | 3 | module Conn; 4 | 5 | export 6 | { 7 | 8 | global sip_scanner_counter: table[addr] of set[addr] &read_expire = 1 days &redef; 9 | global identified_sip_scanner: set [addr]; 10 | 11 | const sip_threshold: vector of count = { 12 | 100, 500, 1000, 2000, 10000, 20000, 50000, 100000, 13 | } &redef; 14 | 15 | global sip_ip_idx: table[addr] of count &default=0 &read_expire = 1 day &redef; 16 | 17 | } 18 | 19 | function check_ip_threshold(v: vector of count, idx: table[addr] of count, orig: addr, n: count):bool 20 | { 21 | 22 | #print fmt ("orig: %s and IDX_orig: %s and n is: %s and v[idx[orig]] is: %s", orig, idx[orig], n, v[idx[orig]]); 23 | if ( idx[orig] < |v| && n >= v[idx[orig]] ) 24 | { 25 | ++idx[orig]; 26 | 27 | return (T); 28 | } 29 | else 30 | return (F); 31 | 32 | } 33 | 34 | function check_sip_scan( c: connection) 35 | { 36 | 37 | local src = c$id$orig_h; 38 | local dst = c$id$resp_h; 39 | local src_p = c$id$orig_p; 40 | local dst_p = c$id$resp_p ; 41 | 42 | #if (src in identified_sip_scanner) 43 | # return; 44 | 45 | #if ( dst_p == 5060/udp) 46 | if (!Site::is_local_addr(src) && dst_p == 5060/udp) 47 | { 48 | 49 | if (src !in sip_scanner_counter) 50 | sip_scanner_counter[src] = set(); 51 | 52 | if ( dst !in sip_scanner_counter[src] ) 53 | add sip_scanner_counter[src][dst]; 54 | 55 | local n = |sip_scanner_counter[src]| ; 56 | local svc = 5060/udp ; 57 | local check_thresh = check_ip_threshold(sip_threshold, sip_ip_idx, src, n); 58 | 59 | if (check_thresh) 60 | { 61 | NOTICE([$note=Scan::AddressScan, $src=src, $p=svc, $n=n, $msg=fmt("%s has scanned %d hosts (%s)", src, n, svc)]); 62 | add identified_sip_scanner[src] ; 63 | } 64 | } 65 | 66 | } 67 | 68 | event connection_established(c: connection) &priority=-5 69 | { 70 | check_sip_scan(c); 71 | } 72 | 73 | event connection_established(c: connection) 74 | { 75 | check_sip_scan(c); 76 | } 77 | 78 | event connection_attempt(c: connection) 79 | { 80 | check_sip_scan(c); 81 | } 82 | 83 | event connection_half_finished(c: connection) 84 | { 85 | check_sip_scan(c); 86 | } 87 | 88 | event connection_pending(c: connection) 89 | { 90 | check_sip_scan(c); 91 | } 92 | 93 | event connection_reset(c: connection) 94 | { 95 | check_sip_scan(c); 96 | } 97 | 98 | event connection_rejected(c: connection) 99 | { 100 | check_sip_scan(c); 101 | } 102 | 103 | event connection_state_remove(c: connection) 104 | { 105 | check_sip_scan(c); 106 | } 107 | -------------------------------------------------------------------------------- /sip-scans.bro: -------------------------------------------------------------------------------- 1 | module SIP; 2 | 3 | export 4 | { 5 | 6 | redef enum Notice::Type += { 7 | SIP_403_Forbidden, 8 | SipviciousScan, 9 | }; 10 | 11 | const BLOCK_THRESHOLD=5 ; 12 | 13 | global sip_scanners: table[addr] of count &redef &read_expire=1 hrs &default=0 ; 14 | 15 | global malicious_sip: pattern = /sipvicious|[Ss][Ii][Pp][Vv][Ii][Cc][Ii][Oo][Uu][Ss] 16 | |friendly-scanner|nm@nm|nm2@nm2|[Nn][Mm][0-9]@[Nn][Mm][0-9]/ &redef ; 17 | 18 | global sip_block_codes: set[count] = { 403, 401 } ; 19 | global sip_block_reason: pattern = /Forbidden|Unauthorized/ ; 20 | 21 | } 22 | 23 | event sip_request (c: connection , method: string , original_URI: string , version: string ) 24 | { 25 | print fmt ("sip_request: method: %s, URI: %s, version: %s", method, original_URI, version) ; 26 | } 27 | 28 | event sip_reply (c: connection , version: string , code: count , reason: string ) 29 | { 30 | local src=c$id$orig_h ; 31 | local dst=c$id$resp_h ; 32 | if (code == 403 && sip_block_reason in reason) 33 | { 34 | if (src !in sip_scanners) 35 | sip_scanners[src] = 0 ; 36 | 37 | sip_scanners[src] += 1; 38 | 39 | if (sip_scanners[src] > BLOCK_THRESHOLD ) 40 | { 41 | NOTICE([$note=SIP::SIP_403_Forbidden, 42 | $conn=c, 43 | $suppress_for=6hrs, 44 | $msg=fmt("SIP bruteforce: 403 Forbidden"), 45 | $identifier=cat(c$id$orig_h)]); 46 | } 47 | } 48 | } 49 | 50 | event sip_header (c: connection , is_orig: bool , name: string , value: string ) 51 | { 52 | print fmt ("sip_header: name :%s, value: %s", name, value); 53 | 54 | if (( name == "FROM" || name == "TO" || name == "USER-AGENT") && malicious_sip in value)) 55 | { 56 | NOTICE([$note=SIP::SipviciousScan, 57 | $conn=c, 58 | $suppress_for=6hrs, 59 | $msg=fmt("Sipvicious Scan seen"), 60 | $identifier=cat(c$id$orig_h)]); 61 | } 62 | } 63 | 64 | event sip_all_headers (c: connection , is_orig: bool , hlist: mime_header_list ) 65 | { 66 | print fmt ("sip_all_headers: %s", mime_header_list ); 67 | } 68 | 69 | event sip_begin_entity (c: connection , is_orig: bool ) 70 | { 71 | } 72 | 73 | event sip_end_entity (c: connection , is_orig: bool ) 74 | { 75 | } 76 | 77 | #if (code in sip_block_codes && sip_block_reason in reason) 78 | -------------------------------------------------------------------------------- /sip-shock.bro: -------------------------------------------------------------------------------- 1 | 2 | redef enum Notice::Type += { 3 | ## Indicates that a host may have attempted a bash cgi header attack 4 | SIP_Shock_Attack, 5 | }; 6 | 7 | event sip_header(c: connection, is_request: bool, name: string, value: string) &priority=5 8 | { 9 | if ( /\x28\x29\x20\x7b\x20/ in value) 10 | { 11 | NOTICE([$note=SIP_Shock_Attack, 12 | $conn=c, 13 | $msg=fmt("%s %s submitting \"%s\"=\"%s\"",c$id$orig_h, c$id$resp_h, name, value), 14 | $identifier=c$uid]); 15 | } 16 | #print fmt ("name: %s value: %s", name, value); 17 | } 18 | -------------------------------------------------------------------------------- /smtp-thresholds.bro: -------------------------------------------------------------------------------- 1 | module SMTP; 2 | 3 | @load smtp-decode-encoded-word-subjects.bro 4 | 5 | export 6 | { 7 | 8 | global smtp_debug=open_log_file("smtp-debug"); 9 | global email_domain = /XXXX/ &redef ; 10 | 11 | redef enum Notice::Type += { 12 | HighNumberRecepients , 13 | HighVolumeSender, 14 | HighVolumeSubject, 15 | TargetedSubject, 16 | MailThreshold, 17 | MailFlood, 18 | BulkSender, 19 | }; 20 | 21 | type smtp_thresholds: record { 22 | start_time: time ; 23 | end_time: time; 24 | mailfrom: string ; 25 | from: set[string]; 26 | to: set[string] ; 27 | rcptto: set[string] ; 28 | subject: set[string] ; 29 | reply_to: set[string] ; 30 | bcc: set[string] ; 31 | has_url: set[string] ; 32 | has_attach: set[string] ; 33 | mail_for: set[string] ; 34 | }; 35 | 36 | global smtp_activity: table[string] of smtp_thresholds &create_expire=10 hrs &persistent; 37 | 38 | type smtp_subjects: record { 39 | sender: set[string]; 40 | recipients: set[string]; 41 | }; 42 | 43 | global smtp_subject_activity: table[string] of smtp_subjects &create_expire=10 hrs &persistent ; 44 | 45 | ## code for threshold determination 46 | 47 | const smtp_threshold: vector of count = { 48 | 200, 300, 500, 750, 1000, 2000, 5000, 7500, 10000, 20000, 50000, 1000000, 49 | } &redef; 50 | 51 | global smtp_to_threshold_idx: table[string] of count &default=0 &write_expire=1 day &redef; 52 | 53 | const smtp_subject_threshold: vector of count = { 54 | 50, 100, 200, 300, 500, 750, 1000, 2000, 5000, 7500, 10000, 20000, 50000, 1000000, 55 | } &redef; 56 | 57 | global smtp_subject_threshold_idx: table[string] of count &default=0 &write_expire=1 day &redef; 58 | 59 | ## prepare to digest data from feeds 60 | type BulkSenderIdx: record { 61 | mailfrom: string; 62 | }; 63 | 64 | type BulkSenderVal: record { 65 | mailfrom: string; 66 | #comment: string &optional &default="null"; 67 | }; 68 | 69 | global ok_bulk_sender: table[string] of BulkSenderVal = table() &synchronized &redef; 70 | global ok_bulk_sender_ip_feed="/usr/local/BRO-feeds/smtp-thresholds::ok_bulk_sender" &redef ; 71 | global ignore_smtp_subjects: pattern = /phenixbb/ &redef ; 72 | global SMTP::m_w_email_add: event (rec: SMTP::Info); 73 | global SMTP::w_m_new_email: event (rec: SMTP::Info); 74 | global populate_smtp_activity: function(rec: SMTP::Info); 75 | global site_email: pattern = /@lbl\.gov|@nersc\.gov|@es\.net/ &redef ; 76 | 77 | const SUBJECT_THRESHOLD = 20 ; 78 | 79 | } #end of export 80 | 81 | @if ( Cluster::is_enabled() ) 82 | @load base/frameworks/cluster 83 | redef Cluster::manager2worker_events += /SMTP::m_w_email_add/; 84 | redef Cluster::worker2manager_events += /SMTP::w_m_new_email/; 85 | @endif 86 | 87 | function check_smtp_threshold(v: vector of count, idx: table[string] of count, orig: string, n: count):bool 88 | { 89 | if ( idx[orig] < |v| && n >= v[idx[orig]] ) 90 | { 91 | ++idx[orig]; 92 | return (T); 93 | } 94 | else 95 | return (F); 96 | } 97 | 98 | function duration_to_hour_mins_secs(dur: interval): string 99 | { 100 | 101 | if (dur < 0 sec) 102 | return fmt("%dh%dm%ds",0, 0, 0); 103 | 104 | local dur_count = double_to_count(interval_to_double(dur)); 105 | local hour = dur_count / 3600 ; 106 | local _mins = dur_count - ((dur_count / 3600 ) * 3600 ); 107 | return fmt("%dh%dm%ds",hour, _mins/60, _mins%60); 108 | 109 | } 110 | 111 | function clean_sender(sender: string): string 112 | { 113 | 114 | local pat = />|<| |\"|\'/; 115 | local to_n = split(sender,/|<| |\"|\'/; 185 | local c = lookup_connection(rec$id); 186 | local mailfrom = "" ; 187 | 188 | # get the from address of the mailfrom/phisher/spammer 189 | 190 | if (rec?$from) 191 | { 192 | mailfrom=rec$from ; 193 | } 194 | else if ( rec?$mailfrom ) 195 | { 196 | mailfrom=strip(gsub(rec$mailfrom, pat, "")); 197 | 198 | if ( mailfrom == ""||mailfrom == "-" || mailfrom =="," ) 199 | { 200 | mailfrom=rec$from; 201 | } 202 | } 203 | else 204 | return ; 205 | 206 | local clean_mf = clean_sender(mailfrom); 207 | 208 | if ( clean_mf in ok_bulk_sender) 209 | { 210 | return ; 211 | } 212 | 213 | if (rec?$subject) 214 | { 215 | for (a in ok_bulk_sender ) 216 | { 217 | if (to_lower(a) in to_lower(rec$subject) ) 218 | { 219 | return; 220 | } 221 | } 222 | 223 | local subject = fmt ("%s", rec$subject); 224 | } 225 | 226 | if (mailfrom !in smtp_activity) 227 | { 228 | local activity_rec: smtp_thresholds ; 229 | smtp_activity[mailfrom]=activity_rec ; 230 | smtp_activity[mailfrom]$mailfrom=fmt("%s", mailfrom); 231 | smtp_activity[mailfrom]$start_time=rec$ts ; 232 | smtp_activity[mailfrom]$to=set(); 233 | smtp_activity[mailfrom]$reply_to=set(); 234 | smtp_activity[mailfrom]$bcc=set(); 235 | smtp_activity[mailfrom]$has_url=set(); 236 | smtp_activity[mailfrom]$has_attach=set(); 237 | smtp_activity[mailfrom]$mail_for=set(); 238 | } 239 | 240 | smtp_activity[mailfrom]$end_time=rec$ts ; 241 | 242 | if (subject !in smtp_subject_activity) 243 | { 244 | local subject_rec: smtp_subjects; 245 | smtp_subject_activity[subject]=subject_rec ; 246 | smtp_subject_activity[subject]$sender=set(); 247 | smtp_subject_activity[subject]$recipients=set(); 248 | } 249 | 250 | add smtp_subject_activity[subject]$sender[mailfrom]; 251 | 252 | local check_thresh = F; 253 | local check_subject_thresh = F; 254 | 255 | if (rec?$rcptto) 256 | { 257 | for (rcptto in rec$rcptto) 258 | { 259 | rcptto = strip(gsub(rcptto, pat, "")); 260 | rcptto = to_lower(strip(gsub(rcptto, email_domain, ""))); 261 | rcptto = strip(gsub(rcptto, email_domain, "")); 262 | 263 | if ( rcptto !in smtp_activity[mailfrom]$rcptto ) 264 | { 265 | add smtp_activity[mailfrom]$rcptto[rcptto] ; 266 | } 267 | 268 | if ( rcptto !in smtp_subject_activity[subject]$recipients) 269 | { 270 | add smtp_subject_activity[subject]$recipients[rcptto]; 271 | } 272 | } 273 | } 274 | 275 | if (rec?$to) 276 | { 277 | for (to in rec$to) 278 | { 279 | local to_split = split(to,/,/); 280 | 281 | for (every_to in to_split) 282 | { 283 | 284 | local to_n = split(to_split[every_to],/ SUBJECT_THRESHOLD && site_email !in mailfrom ) 376 | { 377 | msg+= fmt ("number of LBL receipients: %s", site_receipient) ; 378 | NOTICE([$note=TargetedSubject, $msg=msg]); 379 | } 380 | else 381 | { 382 | msg+= fmt ("number of LBL receipients: %s", site_receipient) ; 383 | NOTICE([$note=HighVolumeSubject, $msg=msg]); 384 | } 385 | } 386 | 387 | @if (! Cluster::is_enabled() || ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER )) 388 | 389 | local n = 0 ; 390 | n = |smtp_activity[mailfrom]$rcptto| ; 391 | check_thresh = check_smtp_threshold(smtp_threshold, smtp_to_threshold_idx, mailfrom, n); 392 | 393 | local s = 0 ; 394 | s = |smtp_subject_activity[subject]$recipients|; 395 | check_subject_thresh = check_smtp_threshold(smtp_subject_threshold, smtp_subject_threshold_idx, subject, s); 396 | 397 | if (check_thresh) 398 | { 399 | 400 | site_receipient=0 ; 401 | site_receipient = get_site_recipients_count(subject); 402 | 403 | msg = generate_threshold_notice(mailfrom); 404 | msg+= fmt ("number of LBL receipients: %s", site_receipient) ; 405 | 406 | duration = smtp_activity[mailfrom]$end_time - smtp_activity[mailfrom]$start_time ; 407 | 408 | if (|smtp_activity[mailfrom]$subject| < SUBJECT_THRESHOLD && duration < 5 hrs) 409 | { 410 | if (site_email in mailfrom ) 411 | { 412 | NOTICE([$note=HighVolumeSender, $msg=msg]); 413 | } 414 | else 415 | { 416 | NOTICE([$note=HighNumberRecepients, $msg=msg]); 417 | } 418 | } 419 | else 420 | { 421 | if (site_email in mailfrom ) 422 | { 423 | NOTICE([$note=BulkSender, $msg=msg]); 424 | } 425 | else 426 | { 427 | NOTICE([$note=MailFlood, $msg=msg]); 428 | } 429 | } 430 | 431 | } 432 | 433 | @endif 434 | 435 | @if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) 436 | 437 | msg =fmt ("inside populae_smtp_activity : %s, %s", rec$uid, rec$subject); 438 | event reporter_info(current_time(), msg, peer_description); 439 | event SMTP::w_m_new_email (rec); 440 | 441 | @endif 442 | 443 | } 444 | 445 | function check_from_mailfrom(rec: SMTP::Info) 446 | { 447 | 448 | local mf = rec$mailfrom; 449 | local f = rec$from; 450 | local mailfrom= clean_sender(mf); 451 | local from= clean_sender(f); 452 | 453 | } 454 | 455 | event bro_init() 456 | { 457 | Input::add_table([$source=ok_bulk_sender_ip_feed, $name="bulk_sender", $idx=BulkSenderIdx, $val=BulkSenderVal, $destination=ok_bulk_sender, 458 | $mode=Input::REREAD, 459 | $pred(typ: Input::Event, left: BulkSenderIdx, right: BulkSenderVal) = 460 | { 461 | right$mailfrom= clean_sender(right$mailfrom); left$mailfrom= clean_sender(left$mailfrom); return T; 462 | } 463 | ]); 464 | 465 | schedule 1 min { force_update_input_logs() }; 466 | } 467 | 468 | event SMTP::log_smtp (rec: SMTP::Info) &priority=-5 469 | { 470 | if ( connection_exists(rec$id) ) 471 | { 472 | populate_smtp_activity(rec); 473 | check_from_mailfrom(rec); 474 | } 475 | } # end of policy 476 | 477 | @if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER ) 478 | event SMTP::m_w_email_add(rec: SMTP::Info) 479 | { 480 | 481 | local msg =fmt ("m_w_email_add: %s, %s", rec$uid, rec$subject); 482 | event reporter_info(current_time(), msg, peer_description); 483 | #populate_smtp_activity(rec); 484 | } 485 | @endif 486 | 487 | @if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) 488 | event SMTP::w_m_new_email (rec: SMTP::Info) 489 | { 490 | local msg =fmt ("w_m_email_new: %s, %s", rec$uid, rec$subject); 491 | event reporter_info(current_time(), msg, peer_description); 492 | 493 | ## event SMTP::m_w_email_add(rec); 494 | populate_smtp_activity(rec); 495 | } 496 | @endif 497 | 498 | event force_update_input_logs() 499 | { 500 | Input::force_update("bulk_sender"); 501 | schedule 1 min { force_update_input_logs() }; 502 | } 503 | 504 | event bro_done() 505 | { 506 | } 507 | --------------------------------------------------------------------------------