diff --git a/.css/capme.css b/.css/capme.css index 21374f7..f886298 100644 --- a/.css/capme.css +++ b/.css/capme.css @@ -185,11 +185,11 @@ span.capme_close:hover { } .txtext_src { - color: #ff0000; + color: #0000ff; } .txtext_dst { - color: #0000ff; + color: #ff0000; } .txtext_dbg { diff --git a/.inc/callback.php b/.inc/callback.php index 1261f61..88ac9fd 100644 --- a/.inc/callback.php +++ b/.inc/callback.php @@ -14,27 +14,113 @@ $spt = h2s($d[1]); $dip = h2s($d[2]); $dpt = h2s($d[3]); -$st = $d[4]; -$et = $d[5]; +$st_unix= $d[4]; +$et_unix= $d[5]; $usr = h2s($d[6]); $pwd = h2s($d[7]); $sidsrc = h2s($d[8]); +$xscript = h2s($d[9]); // Format timestamps -$st = date("Y-m-d H:i:s", $st); -$et = date("Y-m-d H:i:s", $et); +$st = date("Y-m-d H:i:s", $st_unix); +$et = date("Y-m-d H:i:s", $et_unix); + +// Fix Snorby timezone +if ($sidsrc == "event") { + + // load the user's timezone setting + include 'timezone.php'; + + // convert the start time from the user's timezone to UTC/GMT + $st = date_create($st, timezone_open($timezone)); + date_timezone_set($st, timezone_open('Etc/GMT')); + $st = date_format($st, 'Y-m-d H:i:s'); + + // convert the end time from the user's timezone to UTC/GMT + $et = date_create($et, timezone_open($timezone)); + date_timezone_set($et, timezone_open('Etc/GMT')); + $et = date_format($et, 'Y-m-d H:i:s'); +} // Defaults $err = 0; $fmtd = $debug = $errMsg = ''; -// Find appropriate sensor +/* +We need to determine 3 pieces of data: +sensor - sensor name (for Security Onion this is HOSTNAME-INTERFACE) +st - time of the event from the sensor's perspective (may be more accurate than what we were given), in Y-m-d H:i:s format +sid - sensor id +*/ + +$sensor = ""; +if ($sidsrc == "elsa") { + /* + If ELSA is enabled, then we need to: + - construct the ELSA query and submit it via cli.pl + - receive the response and parse out the sensor name (HOSTNAME-INTERFACE) and timestamp + - convert the timestamp to the proper format + NOTE: This requires that ELSA has access to Bro conn.log AND that the conn.log + has been extended to include the sensor name (HOSTNAME-INTERFACE). + */ + + $elsa_query = "class=bro_conn start:'$st_unix' end:'$et_unix' +$sip +$spt +$dip +$dpt limit:1 timeout:0"; + $elsa_command = "perl /opt/elsa/web/cli.pl -q '$elsa_query' "; + $elsa_response = shell_exec($elsa_command); + + // A successful query response looks like this: + // timestamp class host program msg fields + // 1372897204 BRO_CONN 127.0.0.1 bro_conn original_timestamp|Many|Pipe|Delimited|Fields|etc|sensorIsOffset22 + // Explode the output into separate lines and pull out the data line + $pieces = explode("\n", $elsa_response); + + // Sometimes the response contains a warning - this means that the + // expected query response data is not located on the second line. + // Iterate through until we find the header line - the next + // line is the data line we want. See line 35 of /opt/elsa/web/cli.pl + $data_line_n = 1; + + for ($n=0; $n<=count($pieces); $n++) { + if ($pieces[$n] === "timestamp\tclass\thost\tprogram\tmsg\tfields") { + $data_line_n = $n + 1; + break; + } + } + + $elsa_response_data = $pieces[$data_line_n]; + + // Explode the tab-delimited data line and pull out the pipe-delimited raw log + $pieces = explode("\t", $elsa_response_data); + $elsa_response_data_raw_log = $pieces[4]; + + // Explode the pipe-delimited raw log and pull out the original timestamp and sensor name + $pieces = explode("|", $elsa_response_data_raw_log); + $elsa_response_data_raw_log_timestamp = $pieces[0]; + $elsa_response_data_raw_log_sensor = $pieces[22]; + + // Convert timestamp to proper format + $st = date("Y-m-d H:i:s", $elsa_response_data_raw_log_timestamp); + + // Clean up $sensor + $sensor = rtrim($elsa_response_data_raw_log_sensor); + + // We now have 2 of the 3 pieces of data that we need. + // Next, we'll use $sensor to look up the $sid in Sguil's sensor table. +} + +/* +Query the Sguil database +If the user selected sancp or event, query those tables and get +the 3 pieces of data that we need. +*/ $queries = array( + "elsa" => "SELECT sid FROM sensor WHERE hostname='$sensor' AND agent_type='pcap' LIMIT 1", + "sancp" => "SELECT sancp.start_time, s2.sid, s2.hostname FROM sancp LEFT JOIN sensor ON sancp.sid = sensor.sid - LEFT JOIN sensor AS s2 ON sensor.net_name = s2.hostname + LEFT JOIN sensor AS s2 ON sensor.net_name = s2.net_name WHERE sancp.start_time >= '$st' AND sancp.end_time <= '$et' AND ((src_ip = INET_ATON('$sip') AND src_port = $spt AND dst_ip = INET_ATON('$dip') AND dst_port = $dpt) OR (src_ip = INET_ATON('$dip') AND src_port = $dpt AND dst_ip = INET_ATON('$sip') AND dst_port = $spt)) AND s2.agent_type = 'pcap' LIMIT 1", @@ -42,7 +128,7 @@ "event" => "SELECT event.timestamp AS start_time, s2.sid, s2.hostname FROM event LEFT JOIN sensor ON event.sid = sensor.sid - LEFT JOIN sensor AS s2 ON sensor.net_name = s2.hostname + LEFT JOIN sensor AS s2 ON sensor.net_name = s2.net_name WHERE timestamp BETWEEN '$st' AND '$et' AND ((src_ip = INET_ATON('$sip') AND src_port = $spt AND dst_ip = INET_ATON('$dip') AND dst_port = $dpt) OR (src_ip = INET_ATON('$dip') AND src_port = $dpt AND dst_ip = INET_ATON('$sip') AND dst_port = $spt)) AND s2.agent_type = 'pcap' LIMIT 1"); @@ -55,12 +141,19 @@ $debug = $queries[$sidsrc]; } else if (mysql_num_rows($response) == 0) { $err = 1; - $errMsg = "Failed to find a matching sid, please try again in a few seconds"; $debug = $queries[$sidsrc]; + $errMsg = "Failed to find a matching sid, please try again in a few seconds"; + $response = mysql_query("select * from sensor where agent_type='pcap' and active='Y';"); + if (mysql_num_rows($response) == 0) { + $errMsg = "Error: No pcap_agent found"; + } } else { $row = mysql_fetch_assoc($response); - $st = $row["start_time"]; - $sensor = $row["hostname"]; + // If using ELSA, we already set $st and $sensor above so don't overwrite that here + if ($sidsrc != "elsa") { + $st = $row["start_time"]; + $sensor = $row["hostname"]; + } $sid = $row["sid"]; } @@ -71,8 +164,12 @@ } else { - // CLIscript command - $cmd = "cliscript.tcl -sid $sid -sensor '$sensor' -timestamp '$st' -u '$usr' -pw '$pwd' -sip $sip -spt $spt -dip $dip -dpt $dpt"; + // We have all the data we need, so pass the parameters to the correct cliscript + $script = "cliscript.tcl"; + if ($xscript == "bro") { + $script = "cliscriptbro.tcl"; + } + $cmd = "$script -sid $sid -sensor '$sensor' -timestamp '$st' -u '$usr' -pw '$pwd' -sip $sip -spt $spt -dip $dip -dpt $dpt"; exec("../.scripts/$cmd",$raw); @@ -85,7 +182,7 @@ case "DEB": $debug .= preg_replace('/^DEBUG:.*$/', "$0", $line) . "
"; $line = ''; break; case "HDR": $line = preg_replace('/(^HDR:)(.*$)/', "$2", $line); break; case "DST": $line = preg_replace('/^DST:.*$/', "$0", $line); break; - case "SRC": $line = preg_replace('/^SRC:.*$/', "$0", $line); break; + case "SRC": $line = preg_replace('/^SRC:.*$/', "$0", $line); break; } if (strlen($line) > 0) { @@ -93,10 +190,73 @@ } } + // default to sending transcript + $mytx = $fmtd; + + /* + $debug EITHER looks like this: + + DEBUG: Using archived data: /nsm/server_data/securityonion/archive/2013-11-08/doug-virtual-machine-eth1/10.0.2.15:1066_192.168.56.50:80-6.raw + + OR it looks like this: + + DEBUG: Raw data request sent to doug-virtual-machine-eth1. + DEBUG: Making a list of local log files. + DEBUG: Looking in /nsm/sensor_data/doug-virtual-machine-eth1/dailylogs/2013-11-08. + DEBUG: Making a list of local log files in /nsm/sensor_data/doug-virtual-machine-eth1/dailylogs/2013-11-08. + DEBUG: Available log files: + DEBUG: 1383910121 + DEBUG: Creating unique data file: /usr/sbin/tcpdump -r /nsm/sensor_data/doug-virtual-machine-eth1/dailylogs/2013-11-08/snort.log.1383910121 -w /tmp/10.0.2.15:1066_192.168.56.50:80-6.raw (ip and host 10.0.2.15 and host 192.168.56.50 and port 1066 and port 80 and proto 6) or (vlan and host 10.0.2.15 and host 192.168.56.50 and port 1066 and port 80 and proto 6) + DEBUG: Receiving raw file from sensor. + */ + + // Find pcap + $archive = '/DEBUG: Using archived data.*/'; + $unique = '/DEBUG: Creating unique data file.*/'; + $found_pcap = 0; + if (preg_match($archive, $debug, $matches)) { + $found_pcap = 1; + $match = str_replace("
", "", $matches[0]); + $pieces = explode(" ", $match); + $full_filename = $pieces[4]; + $pieces = explode("/", $full_filename); + $filename = $pieces[7]; + } else if (preg_match($unique, $debug, $matches)) { + $found_pcap = 1; + $match = str_replace("
", "", $matches[0]); + $pieces = explode(" ", $match); + $sensor_filename = $pieces[7]; + $server_filename = $pieces[9]; + $pieces = explode("/", $sensor_filename); + $sensorname = $pieces[3]; + $dailylog = $pieces[5]; + $pieces = explode("/", $server_filename); + $filename = $pieces[2]; + $full_filename = "/nsm/server_data/securityonion/archive/$dailylog/$sensorname/$filename"; + } + // Add query to debug $debug .= "QUERY: " . $queries[$sidsrc] . ""; - $result = array("tx" => "$fmtd", + // if we found the pcap, create a symlink in /var/www/capme/pcap/ + // and then create a hyperlink to that symlink + if ($found_pcap == 1) { + $tmpstring = rand(); + $filename_random = str_replace(".raw", "", "$filename-$tmpstring"); + $filename_download = "$filename_random.pcap"; + $link = "/var/www/capme/pcap/$filename_download"; + symlink($full_filename, $link); + $debug .= "
$filename_download"; + $mytx = "$filename_download

$mytx"; + // if the user requested pcap, send the pcap instead of the transcript + if ($xscript == "pcap") { + $mytx = $filename_download; + } + } else { + $debug .= "
WARNING: Unable to find pcap."; + } + + $result = array("tx" => "$mytx", "dbg" => "$debug", "err" => "$errMsg"); } diff --git a/.inc/config.php.sample b/.inc/config.php.sample index 61feb0d..856ecfc 100644 --- a/.inc/config.php.sample +++ b/.inc/config.php.sample @@ -1,7 +1,7 @@ diff --git a/.inc/timezone.php.sample b/.inc/timezone.php.sample new file mode 100644 index 0000000..e8fcca5 --- /dev/null +++ b/.inc/timezone.php.sample @@ -0,0 +1,11 @@ + diff --git a/.js/capme.js b/.js/capme.js index 99c3605..aae00b8 100644 --- a/.js/capme.js +++ b/.js/capme.js @@ -36,7 +36,7 @@ $(document).ready(function(){ $(".capme_submit").click(function() { frmArgs = $('input[value!=""]').length; - if (frmArgs == 11) { + if (frmArgs == 15) { reqCap("usefrm"); } else { theMsg("Please complete all form fields"); @@ -51,6 +51,9 @@ $(document).ready(function(){ bOFF('.capme_submit'); theMsg("Sending request.."); + // Transcript + var xscript = s2h($('input:radio[name=xscript]:checked').val()); + // SID Source var sidsrc = s2h($('input:radio[name=sidsrc]:checked').val()); @@ -88,7 +91,7 @@ $(document).ready(function(){ // Continue if no errors if (err == 0) { - var urArgs = "d=" + sip + "-" + spt + "-" + dip + "-" + dpt + "-" + st + "-" + et + "-" + usr + "-" + pwd + "-" + sidsrc; + var urArgs = "d=" + sip + "-" + spt + "-" + dip + "-" + dpt + "-" + st + "-" + et + "-" + usr + "-" + pwd + "-" + sidsrc + "-" + xscript; $(function(){ $.get(".inc/callback.php?" + urArgs, function(data){cbtx(data)}); @@ -110,7 +113,9 @@ $(document).ready(function(){ txt += ""; txt += ""; txt += ""; - txt += txResult; + if (txResult.indexOf("OS Fingerprint:") >= 0) { + txt += txResult; + } txt += txDebug; txt += txError; txt += ""; @@ -119,6 +124,10 @@ $(document).ready(function(){ $(".capme_div").hide(); $(".capme_result").show(); $(".capme_msg").fadeOut('slow'); + if (txResult.indexOf("OS Fingerprint:") == -1) { + url = "/capme/pcap/" + txResult; + window.open(url, "_self"); + } } else { theMsg(txError); } diff --git a/.scripts/cliscript.tcl b/.scripts/cliscript.tcl index 2ecd8a5..aa39421 100755 --- a/.scripts/cliscript.tcl +++ b/.scripts/cliscript.tcl @@ -227,7 +227,7 @@ if { [catch {tls::import $socketID} tlsError] } { } # Give SSL a sec -after 1000 +# after 1000 # Send sguild a ping to confirm comms SendToSguild $socketID "PING" diff --git a/.scripts/cliscriptbro.tcl b/.scripts/cliscriptbro.tcl new file mode 100755 index 0000000..89b3469 --- /dev/null +++ b/.scripts/cliscriptbro.tcl @@ -0,0 +1,322 @@ +#!/usr/bin/tclsh + +# cliscript.tcl - Based on "quickscript.tcl" +# Portions Copyright (C) 2012 Paul Halliday + +# Copyright (C) 2002-2006 Robert (Bamm) Visscher +# +# This program is distributed under the terms of version 1.0 of the +# Q Public License. See LICENSE.QPL for further details. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +########################## GLOBALS ################################## + +set VERSION "SGUIL-0.8.0 OPENSSL ENABLED" +set SERVER 127.0.0.1 +set PORT 7734 + +# Comment out the following 2 lines if +# you wish to be prompted for a user/pass + +#set USERNAME "beta" +#set PASSWD "band" + +######################################################################### +# Get cmd line args +######################################################################### + +proc DisplayUsage { cmdName } { + + puts "Usage: $cmdName \[-s \] \[-p \] \[-u \]" + puts " \[-o \] \[-sensor \] \[-timestamp \]" + puts " \[-sid \] \[-sip \] \[-dip \]" + puts " \[-spt \] \[-dpt \]\n" + puts " -s : Hostname of sguild server." + puts " -p : Port of sguild server." + puts " -u : Username to connect as." + puts " -pw : Password to connect with." + puts " -o : PATH to tls libraries if needed." + puts " -sensor : The sensor name." + puts " -timestamp <\"timestamp\">: Event timestamp. e.g.: \"2012-08-18 16:28:00\"" + puts " -sid : The sensor ID." + puts " -sip : Source IP." + puts " -dip : Destination IP." + puts " -spt : Source port." + puts " -dpt : Destination port.\n" + exit 1 + +} + +set state flag + +foreach arg $argv { + + switch -- $state { + + flag { + switch -glob -- $arg { + -s { set state server } + -p { set state port } + -u { set state username } + -pw { set state password } + -o { set state openssl } + -sensor { set state sensorname } + -timestamp { set state timestamp } + -sid { set state sensorid } + -sip { set state sip } + -dip { set state dip } + -spt { set state spt } + -dpt { set state dpt } + default { DisplayUsage $argv0 } + } + } + + server { set SERVER $arg; set state flag } + port { set PORT $arg; set state flag } + username { set USERNAME $arg; set state flag } + password { set PASSWD $arg; set state flag } + openssl { set TLS_PATH $arg; set state flag } + sensorname { set SENSORNAME $arg; set state flag } + timestamp { set TIMESTAMP $arg; set state flag } + sensorid { set SENSORID $arg; set state flag } + sip { set SRCIP $arg; set state flag } + dip { set DSTIP $arg; set state flag } + spt { set SRCPORT $arg; set state flag } + dpt { set DSTPORT $arg; set state flag } + default { DisplayUsage $argv0 } + + } + +} + +# Check if we got all of our arguments + +if { [catch {set eventInfo "$SENSORNAME \"$TIMESTAMP\" $SENSORID $SRCIP $DSTIP $SRCPORT $DSTPORT"}] } { + DisplayUsage $argv0 +} + +# Now verify + +if { [regexp -expanded { + + ^.+\s + \"\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d\"\s + \d+\s + \d+\.\d+\.\d+\.\d+\s + \d+\.\d+\.\d+\.\d+\s + \d+\s + \d+$ } $eventInfo match] } { + +} else { + + DisplayUsage $argv0 + +} + +######################################################################### +# Package/Extension Requirements +######################################################################### + +# Check to see if a path to the tls libs was provided +if { [info exists TLS_PATH] } { + + if [catch {load $TLS_PATH} tlsError] { + + puts "ERROR: Unable to load tls libs ($TLS_PATH): $tlsError" + DisplayUsage $argv0 + + } + +} + +if { [catch {package require tls} tlsError] } { + + puts "ERROR: The tcl tls package does NOT appear to be installed on this sysem." + puts "Please see http://tls.sourceforge.net/ for more info." + exit 1 + +} + + +######################################################################### +# Procs +######################################################################### + +# A simple proc to send commands to sguild and catch errors +proc SendToSguild { socketID message } { + + if { [catch {puts $socketID $message} sendError] } { + + # Send failed. Close the socket and exit. + catch {close $socketID} closeError + + if { [info exists sendError] } { + + puts "ERROR: Caught exception while sending data: $sendError" + + } else { + + puts "ERROR: Caught unknown exception" + + } + + exit 1 + + } + +} + +######################################################################### +# Main +######################################################################### + +flush stdout + +# Try to connect to sguild +if [catch {socket $SERVER $PORT} socketID ] { + + # Exit on fail. + puts "ERROR: Connection failed" + exit 1 + +} + +# Successfully connected +fconfigure $socketID -buffering line + +# Check version compatibality +if [catch {gets $socketID} serverVersion] { + + # Caught an unknown error + puts "ERROR: $serverVersion" + catch {close $socketID} + exit 1 + +} + +if { $serverVersion == "Connection Refused." } { + + # Connection refused error + puts "ERROR: $serverVersion" + catch {close $socketID} + exit 1 + +} + +if { $serverVersion != $VERSION } { + + # Mismatched versions + catch {close $socketID} + puts "ERROR: Mismatched versions.\nSERVER= ($serverVersion)\nCLIENT= ($VERSION)" + exit 1 + +} + +# Send the server our version info +SendToSguild $socketID [list VersionInfo $VERSION] + +# SSL-ify the socket +if { [catch {tls::import $socketID} tlsError] } { + + puts "ERROR: $tlsError" + exit 1 + +} + +# Give SSL a sec +# after 1000 + +# Send sguild a ping to confirm comms +SendToSguild $socketID "PING" +# Get the PONG +set INIT [gets $socketID] + +# +# Auth starts here +# + +# Get username if not provided at cmd line +if { ![info exists USERNAME] } { + + puts -nonewline "Enter username: " + flush stdout + set USERNAME [gets stdin] + +} + +# Get users password +if { ![info exists PASSWD] } { + puts -nonewline "Enter password: " + flush stdout + exec stty -echo + set PASSWD [gets stdin] + exec stty echo + flush stdout + puts "" +} + +# Authenticate with sguild +SendToSguild $socketID [list ValidateUser $USERNAME $PASSWD] + +# Get the response. Success will return the users ID and failure will send INVALID. +if { [catch {gets $socketID} authMsg] } { + + puts "ERROR: $authMsg" + exit 1 + +} + +set authResults [lindex $authMsg 1] +if { $authResults == "INVALID" } { + + puts "ERROR: Authentication failed." + exit 1 + +} + +# Send info to Sguild +SendToSguild $socketID [list CliScriptBro $eventInfo] + +set SESSION_STATE DEBUG + +# Xscript data comes in the format XscriptMainMsg window message +# Tags are HDR, SRC, and DST. They are sent when state changes. + +while { 1 } { + + if { [eof $socketID] } { puts "ERROR: Lost connection to server."; exit 1 } + + if { [catch {gets $socketID} msg] } { + + puts "ERROR: $msg" + exit 1 + + } + + # Strip the command and faux winname from the msg + set data [lindex $msg 2] + + + switch -exact -- $data { + + HDR { set SESSION_STATE HDR } + SRC { set SESSION_STATE SRC } + DST { set SESSION_STATE DST } + DEBUG { set SESSION_STATE DEBUG } + DONE { break } + ERROR { set SESSION_STATE ERROR } + default { puts "${SESSION_STATE}: [lindex $msg 2]" } + + } + + # Exit if agent returns no data after debug + if { $SESSION_STATE == "DEBUG" && $data == "" } { + break + } + +} + +catch {close $socketID} diff --git a/index.php b/index.php index ce0eac4..11f7bcb 100644 --- a/index.php +++ b/index.php @@ -4,7 +4,7 @@ $s = 0; // Argument defaults -$sip = $spt = $dip = $dpt = $stime = $etime = $usr = $pwd = $sancp = $event = ''; +$sip = $spt = $dip = $dpt = $stime = $etime = $usr = $pwd = $sancp = $event = $elsa = $bro = $tcpflow = $pcap = ''; // Grab any arguments provided in URI if (isset($_REQUEST['sip'])) { $sip = $_REQUEST['sip']; $s++; } if (isset($_REQUEST['spt'])) { $spt = $_REQUEST['spt']; $s++; } @@ -14,11 +14,19 @@ if (isset($_REQUEST['etime'])) { $etime = $_REQUEST['etime']; $s++; } if (isset($_REQUEST['user'])) { $usr = $_REQUEST['user']; $s++; } if (isset($_REQUEST['password'])) { $pwd = $_REQUEST['password']; $s++; } +// If we see a filename parameter, we know the request came from Snorby +// and if so we can just query the event table since Snorby just has NIDS alerts +// If the referer contains ":3154", then it's most likely a Security Onion user +// pivoting from ELSA, so we should query using ELSA. +// If all else fails, query sancp. if (isset($_REQUEST['filename'])) { $event = " checked"; +} elseif ( isset($_SERVER['HTTP_REFERER']) && (strpos($_SERVER['HTTP_REFERER'],":3154") !== false)) { + $elsa = " checked"; } else { $sancp = " checked"; } +$tcpflow = " checked"; ?> >sancp >event +>elsa + + + + +Output: + +>tcpflow +>bro +>pcap diff --git a/pcap/index.php b/pcap/index.php new file mode 100644 index 0000000..fd72ca4 --- /dev/null +++ b/pcap/index.php @@ -0,0 +1,5 @@ +