1<?php
2/*******************************************************************************
3** Basic Analysis and Security Engine (BASE)
4** Copyright (C) 2004 BASE Project Team
5** Copyright (C) 2000 Carnegie Mellon University
6**
7** (see the file 'base_main.php' for license details)
8**
9** Project Leads: Kevin Johnson <kjohnson@secureideas.net>
10**                Sean Muller <samwise_diver@users.sourceforge.net>
11** Built upon work by Roman Danyliw <rdd@cert.org>, <roman@danyliw.com>
12**
13** Purpose: Binary download of payload and pcap format packet download
14**
15** Input GET/POST variables
16**   - sid
17**   - cid
18**   - download: 1 - binary download of just the payload. Nothing else.
19**               2 - download pcap format based packet (for FLoP extended db)
20**               3 - download pcap format based packet (for non-FLoP)
21********************************************************************************
22** Authors:
23********************************************************************************
24** Kevin Johnson <kjohnson@secureideas.net>
25**
26********************************************************************************
27*/
28
29include ("base_conf.php");
30include ("$BASE_path/includes/base_constants.inc.php");
31include ("$BASE_path/includes/base_include.inc.php");
32
33// Check role out and redirect if needed -- Kevin
34$roleneeded = 10000;
35$BUser = new BaseUser();
36if (($BUser->hasRole($roleneeded) == 0) && ($Use_Auth_System == 1))
37{
38  base_header("Location: ". $BASE_urlpath . "/index.php");
39  exit();
40}
41
42$cid = ImportHTTPVar("cid", VAR_DIGIT);
43$sid = ImportHTTPVar("sid", VAR_DIGIT);
44$download = ImportHTTPVar("download", VAR_DIGIT);
45
46
47/**********************************************************/
48/* 1 = binary download of just the payload. Nothing else. */
49if ($download == 1)
50{
51	/* Connect to the Alert database */
52	$db = NewBASEDBConnection($DBlib_path, $DBtype);
53	$db->baseDBConnect($db_connect_method,
54	$alert_dbname, $alert_host, $alert_port, $alert_user, $alert_password);
55
56	/* Get the Payload from the database: */
57	$sql2 = "SELECT data_payload FROM data WHERE sid='".$sid."' AND cid='".$cid."'";
58	$result2 = $db->baseExecute($sql2);
59	$myrow2 = $result2->baseFetchRow();
60	$result2->baseFreeRows();
61
62	/* get encoding information for payload */
63	/* 0 == hex, 1 == base64, 2 == ascii;	*/
64	$sql3 = 'SELECT encoding FROM sensor WHERE sid='.$sid;
65	$result3 = $db->baseExecute($sql3);
66	$myrow3 = $result3->baseFetchRow();
67	$result3->baseFreeRows();
68
69	if ( $myrow2[0] ){
70		/****** database contains hexadecimal *******************/
71		if ($myrow3[0] == 0){
72			header ('HTTP/1.0 200');
73			header ("Content-type: application/download");
74			header ("Content-Disposition: attachment; filename=payload_".$sid."-".$cid.".bin");
75			header ("Content-Transfer-Encoding: binary");
76			ob_start();
77			$payload = str_replace("\n", "", $myrow2[0]);
78			$len = strlen($payload);
79			$half = ($len / 2);
80			header ("Content-Length: $half");
81			$counter = 0;
82			for ($i = 0; $i < ( $len + 32 ); $i += 2){
83				$counter++;
84				if ($counter > ($len / 2)){
85					break;
86				}
87				$byte_hex_representation = ($payload[$i].$payload[$i+1]);
88				echo chr(hexdec($byte_hex_representation));
89			}
90			ob_end_flush();
91			// nothing should come AFTER ob_end_flush().
92
93		/********database contains base64 *******************/
94		} elseif ($myrow3[0] == 1){
95			header ('HTTP/1.0 200');
96			header ("Content-type: application/octet-stream");
97			header ("Content-Disposition: attachment; filename=payload".$sid."-".$cid.".bin");
98			header ("Content-Transfer-Encoding: binary");
99			ob_start();
100			$pre_payload = str_replace("\n", "", $myrow2[0]);
101			$payload = base64_decode($pre_payload);
102			$len = strlen($payload);
103			header ("Content-Length: $len");
104			$counter = 0;
105			for ($i = 0; $i < ($len + 16); $i++){
106				$counter++;
107				if ($counter > $len) {
108					break;
109				}
110				$byte = $payload[$i];
111				print $byte;
112			}
113			ob_end_flush();
114			// nothing should come AFTER ob_end_flush().
115
116		/********** database contains ASCII ***************/
117		} elseif ($myrow3[0] == 2){
118			header ('HTTP/1.0 200');
119			header ('Content-Type: text/html');
120			print "<h1> File not found:</h1>";
121
122			print "<br>Output of binary data with storage method ASCII<br>";
123			print "is NOT supported, because this method looses data<br>";
124			print "So you can not definitely rebuild the binary,<br>";
125			print "as one ASCII character may represent different<br>";
126			print "binary values. Think of the dot, for example.<br>";
127
128			print "<br><br><hr><i>Generated by base_payload.php</i><br>";
129		} else {
130			header ('HTTP/1.0 200');
131			header ('Content-Type: text/html');
132			print "<h1> File not found:</h1>";
133			print "<br>Encoding type not implemented in base_payload.php.";
134			print "<br><br><hr><i>Generated by base_payload.php</i><br>";
135		}
136	} else {
137		header ('HTTP/1.0 200');
138		header ('Content-Type: text/html');
139		print "<h1> File not found:</h1>";
140		print "<br>No payload data found, that could be downloaded or stored.";
141		print "<br><br><hr><i>Generated by base_payload.php</i><br>";
142	}
143
144}
145/**************************************************************************/
146/* pcap download, for both flop-extended databases and non-flop databases */
147else if ($download == 2 || $download == 3)
148{
149	/*
150	 * If we have FLoP extended database schema then we can rebuild alert
151	 * in pcap format which can be used to analyze it via tcpdump or
152	 * ethereal to use their protocol analyzing features.
153	 */
154
155	/* Connect to the Alert database. */
156	$db = NewBASEDBConnection($DBlib_path, $DBtype);
157	$db->baseDBConnect($db_connect_method,
158	$alert_dbname, $alert_host, $alert_port, $alert_user, $alert_password);
159
160
161	/* Sanity check */
162	if ($download == 2)
163	{
164		/* Check do we have pcap_header and data_header columns in data table. */
165		if (!in_array("pcap_header", $db->DB->MetaColumnNames('data')) ||
166		    !in_array("data_header", $db->DB->MetaColumnNames('data')))
167		{
168			header ('HTTP/1.0 200');
169			header ('Content-Type: text/html');
170			print "<h1> File not found:</h1>";
171			print "<br>Make sure you have FLoP extended database.";
172			print "<br><br><hr><i>Generated by base_payload.php</i><br>";
173			exit;
174		}
175	}
176
177
178
179	/*********** Get needed data from database. **************/
180
181	/* For FLoP-extended databases: */
182	if ($download == 2)
183	{
184		$sql2 = "SELECT pcap_header, data_header, data_payload FROM data ";
185		$sql2.= "WHERE sid='".$sid."' AND cid='".$cid."'";
186	}
187	/* For non-flop databases: */
188	else
189	{
190		$sql2 = "SELECT data_payload FROM data ";
191		$sql2.= "WHERE sid='".$sid."' AND cid='".$cid."'";
192	}
193	$result2 = $db->baseExecute($sql2);
194	$myrow2 = $result2->baseFetchRow();
195	$result2->baseFreeRows();
196
197	/* Get encoding information for current sensor. */
198	$sql3 = 'SELECT encoding FROM sensor WHERE sid='.$sid;
199	$result3 = $db->baseExecute($sql3);
200	$myrow3 = $result3->baseFetchRow();
201	$result3->baseFreeRows();
202
203
204
205	/* For flop-extended databases: IP header information is already present
206	   in myrow2.
207
208	   For non-flop databases we are NOT done, yet.  So, try and get the missing
209	   information: */
210	if ($download == 3)
211	{
212		$ip_sql = "SELECT ip_ver, ip_hlen, ip_tos, ip_len, ip_id, ip_off, ip_flags,";
213		$ip_sql.= "ip_ttl, ip_proto, ip_csum, ip_src, ip_dst FROM iphdr ";
214		$ip_sql.= "WHERE sid='".$sid."' AND cid='".$cid."'";
215		$ip_res = $db->baseExecute($ip_sql);
216		$ip = $ip_res->baseFetchRow();
217		$ip_res->baseFreeRows();
218
219		if ($ip[8] == 1)
220		{
221			$l4_sql = "SELECT icmp_type, icmp_code, icmp_csum, icmp_id, icmp_seq ";
222			$l4_sql.= "FROM icmphdr WHERE sid='".$sid."' AND cid='".$cid."'";
223		}
224		elseif ($ip[8] == 6)
225		{
226			$l4_sql = "SELECT tcp_sport, tcp_dport, tcp_seq, tcp_ack, tcp_off,";
227			$l4_sql.= "tcp_res, tcp_flags, tcp_win, tcp_csum, tcp_urp from tcphdr ";
228			$l4_sql.= "WHERE sid='".$sid."' AND cid='".$cid."'";
229		}
230		elseif ($ip[8] == 17)
231		{
232			$l4_sql = "SELECT udp_sport, udp_dport, udp_len, udp_csum FROM udphdr ";
233			$l4_sql.= "WHERE sid='".$sid."' AND cid='".$cid."'";
234		}
235
236		$l4_res = $db->baseExecute($l4_sql);
237		$l4 = $l4_res->baseFetchRow();
238		$l4_res->baseFreeRows();
239
240	} // if ($download == 3)
241
242
243	/***********************************************************************/
244	/* Now, when extracting the information from the database: Pay attention
245	 * to the encoding:
246   *
247	/* 0 == hex, 1 == base64, 2 == ascii; cf. snort-2.8.5.1/src/plugbase.h */
248
249	/****** hexadecimal encoding *********/
250	if ($myrow3[0] == 0)
251	{
252		/* FLoP-extended databases */
253		if ($download == 2)
254		{
255			$pcap_header  = $myrow2[0];
256			$data_header  = $myrow2[1];
257			$data_payload = $myrow2[2];
258		}
259		/* Non-flop databases */
260		else
261		{
262			$data_payload = $myrow2[0];
263		}
264	}
265	/******** base64 encoding ********/
266	elseif ($myrow3[0] == 1)
267	{
268		/* FLoP-extended databases */
269		if ($download == 2)
270		{
271			$pcap_header  = bin2hex(base64_decode($myrow2[0]));
272			$data_header  = bin2hex(base64_decode($myrow2[1]));
273			$data_payload = bin2hex(base64_decode($myrow2[2]));
274		}
275		/* Non-flop databases */
276		else
277		{
278			$data_payload = bin2hex(base64_decode($myrow2[0]));
279		}
280	}
281	else
282	{
283		/******* database contains neither hex nor base64 encoding. *********/
284		header ('HTTP/1.0 200');
285		header ('Content-Type: text/html');
286		print "<h1> File not found:</h1>";
287		print "<br>Only HEX and BASE64 encoding types are supported, nothing else.";
288		print "<br><br><hr><i>Generated by base_payload.php</i><br>";
289		exit;
290	}
291
292
293
294
295	/*
296	 * From here on: pcap header, data_header and data_payload all contain data
297	 * in hex encoding, even if original encoding type was base64.
298	 */
299
300	/* Sanity checks for FLoP-extended databases*/
301	if ($download == 2)
302	{
303		 # /usr/include/pcap.h:
304		 # struct pcap_pkthdr {
305  	 #    struct timeval ts;  /* time stamp */
306  	 #    bpf_u_int32 caplen; /* length of portion present */
307  	 #    bpf_u_int32 len;    /* length this packet (off wire) */
308		 # };
309		 #
310		 # And a struct timeval has either a 32-bit or 64-bit tv_sec.
311		if (strlen($pcap_header) > 48)
312		{
313			header ('HTTP/1.0 200');
314			header ('Content-Type: text/html');
315			print "<h1> File not found:</h1>";
316			print "<br>Error in pcap_header, answer is too large: ".strlen($pcap_header)."!";
317			print "<br><br><hr><i>Generated by base_payload.php</i><br>";
318			exit;
319		}
320		else if (strlen($pcap_header) == 0)
321		{
322			header ('HTTP/1.0 200');
323			header ('Content-Type: text/html');
324			print "<h1> File not found:</h1>";
325			print "<br>No pcap header, we can't rebuild the network packet.";
326			print "<br><br><hr><i>Generated by base_payload.php</i><br>";
327			exit;
328		}
329	} // if ($download == 2)
330
331
332
333
334
335	/* For non-flop databases, the data_header has not been created, yet.
336	 * Do it now: */
337	if ($download == 3)
338	{
339		# tack an ethernet header on there
340		$data_header = "DEADCAFEBABE1122334455660800";
341
342		# later on, all of this gets interpreted as hex, so simply
343		# pull the values from the db, convert them to hex, 0-pad them
344		# as necessary, and tack them together.
345		$data_header.= sprintf("%02s", $ip[0] . $ip[1]); // ver&ihl
346		$data_header.= sprintf("%02s", dechex($ip[2])); // tos
347		$data_header.= sprintf("%04s", dechex($ip[3])); // len
348		$data_header.= sprintf("%04s", dechex($ip[4])); // id
349		$data_header.= sprintf("%02s", dechex($ip[5])); // flags
350		$data_header.= sprintf("%02s", dechex($ip[6])); // offset
351		$data_header.= sprintf("%02s", dechex($ip[7])); // ttl
352		$data_header.= sprintf("%02s", dechex($ip[8])); // proto
353		$data_header.= sprintf("%04s", dechex($ip[9])); // csum.
354
355		# http://us2.php.net/manual/en/function.dechex.php#71795
356		# source IP
357		$chars = ($ip[10] <= 0x0fffffff) ? 1 : 0;
358		$data_header.= sprintf("%02s", substr(dechex((float) $ip[10]),0,2-$chars));
359
360		for ($i = 1; $i < 4; $i++)
361			$data_header.= sprintf("%02s", substr(dechex((float) $ip[10]), $i*2-$chars, 2));
362
363		# dest IP
364		$chars = ($ip[11] <= 0x0fffffff) ? 1 : 0;
365		$data_header.= sprintf("%02s", substr(dechex((float) $ip[11]),0,2-$chars));
366
367		for ($i = 1; $i < 4; $i++)
368			$data_header.= sprintf("%02s", substr(dechex((float) $ip[11]), $i*2-$chars, 2));
369
370		if ($ip[8] == 1)
371		{
372			$data_header.= sprintf("%02s", dechex((float) $l4[0])); // type
373			$data_header.= sprintf("%02s", dechex((float) $l4[1])); // code
374			$data_header.= sprintf("%04s", dechex((float) $l4[2])); // sum
375			// only echo req/rep, timestamp, info req/rep have id/seq
376			if ($l4[0] == 0 || $l4[0] == 8 || ($l4[0] >= 13 && $l4[0] <= 16))
377			{
378				$data_header.= sprintf("%04s", dechex((float) $l4[3])); // id
379				$data_header.= sprintf("%04s", dechex((float) $l4[4])); // seq
380			}
381		}
382		elseif ($ip[8] == 6)
383		{
384			$data_header.= sprintf("%04s", dechex((float) $l4[0])); // source port
385			$data_header.= sprintf("%04s", dechex((float) $l4[1])); // dest port
386			$data_header.= sprintf("%08s", dechex((float) $l4[2])); // seq #
387			$data_header.= sprintf("%08s", dechex((float) $l4[3])); // ack #
388			$data_header.= sprintf("%01s", dechex((float) $l4[4])); // offset
389			$data_header.= sprintf("%03s", dechex((float) $l4[6])); // flags
390			$data_header.= sprintf("%04s", dechex((float) $l4[7])); // window
391			$data_header.= sprintf("%04s", dechex((float) $l4[8])); // checksum
392			$data_header.= sprintf("%04s", dechex((float) $l4[9])); // urg ptr
393
394			# walk opts...
395			$tcp_opt_sql = "SELECT optid, opt_code, opt_len, opt_data FROM opt ";
396			$tcp_opt_sql.= "WHERE sid='".$sid."' AND cid='".$cid."' AND opt_proto=6 ORDER BY optid ASC";
397			$tcp_opt_res = $db->baseExecute($tcp_opt_sql);
398			$tcp_opt_data = "";
399
400
401			while ($tcp_opt = $tcp_opt_res->baseFetchRow())
402			{
403				$tcp_opt_data .= sprintf("%02s", dechex((float) $tcp_opt[1]));
404
405				// if opt_len == 0, its an "opt kind", and thus has no length or data
406				if ($tcp_opt[2] != 0)
407				{
408					$tcp_opt_data .= sprintf("%02s", dechex((float) $tcp_opt[2] + 2));
409					$tcp_opt_data .= $tcp_opt[3];
410				}
411			} // while ($tcp_opt = $tcp_opt_res->baseFetchRow())
412
413			$tcp_opt_res->baseFreeRows();
414			$data_header.= $tcp_opt_data;
415
416		}
417		elseif ($ip[8] == 17)
418		{
419			$data_header.= sprintf("%04s", dechex((float) $l4[0])); // source port
420			$data_header.= sprintf("%04s", dechex((float) $l4[1])); // dest port
421			$data_header.= sprintf("%04s", dechex((float) $l4[2])); // len
422			$data_header.= sprintf("%04s", dechex((float) $l4[3])); // sum
423		}
424
425	} // if ($download == 3)
426
427
428
429
430	/*****************************************************************/
431	/* Now, begin to create the file the user wants to download: */
432	header ('HTTP/1.0 200');
433	header ("Content-type: application/octet-stream");
434	header ("Content-Disposition: attachment; filename=base_packet_".$sid."-".$cid.".pcap");
435	header ("Content-Transfer-Encoding: binary");
436
437
438
439	/*
440	 * Calculating snaplen which is length of payload plus header,
441	 * for HEX we have to divide by two -> two HEX characters
442	 * represent one binary byte.
443	 */
444
445	$snaplen = (strlen($data_header) + strlen($data_payload)) / 2;
446	header ("Content-length: ". 40 + $snaplen);
447
448
449
450	/* Create pcap file header. */
451	$hdr['magic'] =         pack('L', 0xa1b2c3d4);  /* unsigned long  (always 32 bit, machine byte order) */
452	$hdr['version_major'] = pack('S', 2);           /* unsigned short (always 16 bit, machine byte order) */
453	$hdr['version_minor'] = pack('S', 4);           /* unsigned short (always 16 bit, machine byte order) */
454	$hdr['thiszone'] =      pack('I', 0);           /* signed   long  (always 32 bit, machine byte order) */
455	$hdr['sigfigs'] =       pack('L', 0);           /* unsigned long  (always 32 bit, machine byte order) */
456	$hdr['snaplen'] =       pack('L', $snaplen);    /* unsigned long  (always 32 bit, machine byte order) */
457	$hdr['linktype'] =      pack('L', 1);           /* unsigned long  (always 32 bit, machine byte order) */
458
459
460	/* Create pcap packet header. Converting hex to decimal and then to network byte order (big endian). */
461	/* For FLoP-extended databases: */
462	if ($download == 2)
463	{
464		/* tv_sec in a struct timeval is either 32 bits or 64 bits long.
465		 * But, as it seems, in $pcap_header it is ALWAYS 32 bits = 4 bytes long.
466		 * Which means that, in the way as snort stores it, 8 bytes are consumed.
467		 * So, offset is 0, and length is ALWAYS 8.
468		 * I have not checked whether this is FLoP's or snort's fault.
469		 */
470		list(, $phdr['timeval_sec']) =  unpack('L', pack('N', hexdec(substr($pcap_header, 0, 8))));
471
472		if (strlen($pcap_header) > 32)
473		{
474			/* A 64-bit tv_sec in a struct timeval are 8 bytes.  In hexadecimal form
475			   as snort stores it, this consumes 16 bytes */
476			list(, $phdr['timeval_usec']) = unpack('L', pack('N', hexdec(substr($pcap_header, 16, 8))));
477		}
478		else
479		{
480			/* A 32-bit tv_sec in a struct timeval are 4 bytes.  In hexadecimal form
481			   as snort stores it, this consumes 8 bytes */
482			list(, $phdr['timeval_usec']) = unpack('L', pack('N', hexdec(substr($pcap_header, 8, 8))));
483		}
484
485		list(, $phdr['caplen']) =     unpack('L', pack('N', hexdec(substr($pcap_header, (strlen($pcap_header)) - 16, 8))));
486		list(, $phdr['len']) =        unpack('L', pack('N', hexdec(substr($pcap_header, strlen($pcap_header) - 8, 8))));
487
488
489		if ($debug_mode > 0)
490		{
491			error_log("phdr[timeval_sec]  = \"" . $phdr['timeval_sec']  . "\"");
492			error_log("phdr[timeval_usec] = \"" . $phdr['timeval_usec'] . "\"");
493			error_log("snaplen            = $snaplen bytes.<BR>\n");
494			error_log("phdr[caplen]       = \"" . $phdr['caplen'] . "\"");
495			list(, $tmp['caplen_new'])    = unpack('L', pack('N', hexdec(substr($pcap_header, 32, 8))));
496			error_log("phdr[caplen] new   = \"" . $tmp['caplen_new'] . "\"");
497			error_log("phdr[len]          = \"" . $phdr['len']    . "\"");
498			list(, $tmp['len_new'])       = unpack('L', pack('N', hexdec(substr($pcap_header, 40, 8))));
499			error_log("phdr[len] new      = \"" . $tmp['len_new'] . "\"");
500		}
501	}
502	/* For non-flop databases */
503	else
504	{
505		$ts_sql = "SELECT timestamp FROM event ";
506		$ts_sql.= "WHERE sid='".$sid."' AND cid='".$cid."'";
507		$ts_res = $db->baseExecute($ts_sql);
508		$ts_string = $ts_res->baseFetchRow();
509		$ts_res->baseFreeRows();
510		$ts = strtotime($ts_string[0]);
511		list(, $phdr['timeval_sec']) =  unpack('L', pack('L', $ts));
512		list(, $phdr['timeval_usec']) = unpack('L', pack('L', 0));
513		list(, $phdr['caplen']) =       unpack('L', pack('L', $snaplen));
514		list(, $phdr['len']) =          unpack('L', pack('L', $snaplen));
515	}
516
517	/* Copy header to packet, convert hex to dec and from dec to char. */
518	$packet = "";
519	for ($i = 0; $i < strlen($data_header); $i = $i + 2)
520		$packet .= chr(hexdec(substr($data_header, $i, 2)));
521
522	/* Copy payload to packet, convert hex to dec and from dec to char. */
523	for ($i = 0; $i < strlen($data_payload); $i = $i + 2)
524		$packet .= chr(hexdec(substr($data_payload, $i, 2)));
525
526	ob_start();
527
528	/* Writing pcap file header */
529	foreach ($hdr as $value)
530		echo $value;
531
532	/* Writing pcap packet header */
533	foreach ($phdr as $value)
534		echo pack('L', $value);
535
536	/* Writing packet */
537	echo $packet;
538
539	ob_end_flush();
540	/* nothing should come after ob_end_flush(). */
541}
542else // else if ($download == 2 || $download == 3)
543{
544	header ('HTTP/1.0 200');
545	header ('Content-Type: text/html');
546	print "<h1> File not found:</h1>";
547	print "<br>This page is only intended for downloading purposes; it has no content.";
548	print "<br><br><hr><i>Generated by base_payload.php</i><br>";
549} // if ($download == 1)
550?>
551