Project

General

Profile

Feature #12963 » nmap_scan-v15.patch

Phil Wardt, 03/28/2022 05:09 AM

View differences:

src/usr/local/www/nmap_scan.php
1
<?php
2
/*
3
 * nmap_scan.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2022-2022 Rubicon Communications, LLC (Netgate)
7
 * All rights reserved.
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 * http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
require_once("guiconfig.inc");
22
/* require_once("pfsense-utils.inc"); */
23
require_once("ipsec.inc");
24

  
25
if ($_POST['downloadbtn'] == gettext("Download Results")) {
26
	$nocsrf = true;
27
}
28

  
29
$fp = "/root/";
30
$fn = "nmap.result";
31
$fe = "nmap.error"; // stderr
32
$max_display_size = 50*1024*1024; // 50MB limit on GUI results display. See https://redmine.pfsense.org/issues/9239
33

  
34
$interfaces = get_configured_interface_with_descr();
35
if (ipsec_enabled()) {
36
	$interfaces['enc0'] = "IPsec";
37
}
38
$interfaces['lo0'] = "Localhost";
39

  
40
foreach (array('server' => gettext('OpenVPN Server'), 'client' => gettext('OpenVPN Client')) as $mode => $mode_descr) {
41
	if (is_array($config['openvpn']["openvpn-{$mode}"])) {
42
		foreach ($config['openvpn']["openvpn-{$mode}"] as $id => $setting) {
43
			if (!isset($setting['disable'])) {
44
				$interfaces['ovpn' . substr($mode, 0, 1) . $setting['vpnid']] = $mode_descr . ": ".htmlspecialchars($setting['description']);
45
			}
46
		}
47
	}
48
}
49

  
50
$interfaces = array_merge($interfaces, interface_ipsec_vti_list_all());
51
$interfaces = array_merge(array('' => 'Any'), $interfaces);
52

  
53
$scan_types = array(
54
	'syn' => gettext('SYN'),
55
	'connect' => gettext('TCP connect()'),
56
	'icmp' => gettext('Ping'),
57
	'udp' => gettext('UDP'),
58
	'arp' => gettext('ARP (directly connected networks only!)')
59
);
60

  
61
function nmap_get_running_process(){
62
	$processcheck = (trim(shell_exec("/bin/ps axw -O pid= | /usr/bin/grep '/usr/local/bin/nmap' | /usr/bin/grep '{$fn}' | /usr/bin/egrep -v '(pflog|grep)'")));
63
	return $processcheck;
64
}
65

  
66
// only accept alphanumeric, spaces (040) and '.,-_' chars, and do not allow '-o? ' for output files
67
function valid_input($str) {
68
	$valid_str = (preg_match('/^[a-z0-9\040\.,\-_]+$/i', $str) === 1) && (preg_match('/-o.\040/', $str) === 0);
69
	//$valid_str = (preg_match('/^[a-z0-9\040\.,\-_]+$/i', $str) === 1) && (strpos($str, '-o') === false);
70
	return $valid_str;
71
}
72

  
73
$input_errors = array();
74
$do_nmapscan = false;
75
if ($_POST) {
76
	$hostname = $_POST['hostname'];
77
	$interface = $_POST['interface'];
78
	$scantype = $_POST['scantype'];
79
	$noping = isset($_POST['noping']);
80
	$servicever = isset($_POST['servicever']);
81
	$osdetect = isset($_POST['osdetect']);
82
	$custom_scantype = $_POST['custom_scantype'];
83
	$custom_options = $_POST['custom_options'];
84
	$custom_targetspecs = $_POST['custom_targetspecs'];
85

  
86
	if ($_POST['startbtn'] != "") {
87
		$action = gettext("Start");
88

  
89
		// check for input errors
90
		if (empty($hostname)) {
91
			$input_errors[] = gettext("You must enter an IP address to scan.");
92
		} elseif (!(is_ipaddr($hostname) || is_subnet($hostname) || is_hostname($hostname))) {
93
			$input_errors[] = gettext("You must enter a valid IP address to scan.");
94
		}
95

  
96
		if(!empty($interface)) {
97
			if (!array_key_exists($interface, $interfaces)) {
98
				$input_errors[] = gettext("Invalid interface.");
99
			}
100
		}
101

  
102
		// process scan options
103
		if (!count($input_errors)) {
104

  
105
			// prevent use of any deprecated option
106
			$nmap_options = " -d";
107

  
108
			if (is_ipaddrv6($hostname) || is_subnetv6($hostname)) {
109
				$nmap_options .= " -6";
110
			}
111

  
112
			// scan type
113
			if (empty($custom_scantype)) {
114
				switch($scantype) {
115
					case 'syn':
116
						$nmap_options .= " -sS";
117
						break;
118
					case 'connect':
119
						$nmap_options .= " -sT";
120
						break;
121
					case 'icmp':
122
						$nmap_options .= " -sn"; // previously -sP
123
						break;
124
					case 'udp':
125
						$nmap_options .= " -sU";
126
						break;
127
					case 'arp':
128
						$nmap_options .= " -sn -PR";
129
						break;
130
				}
131
			} elseif (valid_input($custom_scantype)) {
132
					$nmap_options .= " " . $custom_scantype;
133
			} else {
134
					$input_errors[] = gettext("Invalid characters in Custom Scan Type. Only alphanumeric, spaces and '.,-_' chars are allowed. Custom output options -o are not allowed");
135
			}
136

  
137
			// scan options
138
			if ($noping) {
139
				$nmap_options .= " -P0";
140
			}
141
			if ($servicever) {
142
				$nmap_options .= " -sV";
143
			}
144
			if ($osdetect) {
145
				$nmap_options .= " -O";
146
			}
147

  
148
			if (!empty($custom_options)) {
149
				if (valid_input($custom_options)) {
150
					$nmap_options .= " " . $custom_options;
151
				} else {
152
					$input_errors[] = gettext("Invalid characters in custom options. Only alphanumeric, spaces and '.,-_' chars are allowed. Custom output options -o are not allowed");
153
				}
154
			}
155

  
156
			// append summary output to results file (doesn't contain stderr)
157
			// prevent using source stylesheets for xls output (not really needed)
158
			$nmap_options .= " -oN {$fp}{$fn} --append-output --no-stylesheet";
159

  
160
			if (!empty($interface)) {
161
				$nmap_options .= " -e " . get_real_interface($interface);
162
			}
163

  
164
			if (!empty($custom_targetspecs)) {
165
				if (valid_input($custom_targetspecs)) {
166
					$nmap_options .= " " . $custom_targetspecs;
167
				} else {
168
					$input_errors[] = gettext("Invalid characters in custom target options. Only alphanumeric, spaces and '.,-_' chars are allowed. Custom output options -o are not allowed");
169
				}
170
			}
171

  
172
			$nmap_options .= " " . escapeshellarg($hostname);
173

  
174
			if (!count($input_errors)) {
175
				$do_nmapscan = true;
176
			}
177
		}
178
	} elseif ($_POST['stopbtn'] != "") {
179
		$action = gettext("Stop");
180

  
181
		/* check if nmap scan is already running */
182
		$processes_running = nmap_get_running_process();
183
		$processisrunning = ($processes_running != "");
184

  
185
		//explode processes into an array, (delimiter is new line)
186
		$processes_running_array = explode("\n", $processes_running);
187

  
188
		if ($processisrunning != true) {
189
			$input_errors[] = gettext("Process nmap already completed. Check results below.");
190
		} else {
191
			//kill each of the nmap processes
192
			foreach ($processes_running_array as $process) {
193
				$process_id_pos = strpos($process, ' ');
194
				$process_id = substr($process, 0, $process_id_pos);
195
				exec("kill $process_id");
196
			}				
197
		}
198
	} elseif ($_POST['viewbtn'] != "" or $_POST['refreshbtn'] != "") {
199
		$action = gettext("View");
200
	} elseif ($_POST['downloadbtn'] != "") {
201
		$action = gettext("Download");
202
		//download file
203
		send_user_download('file', $fp.$fn);
204
	} elseif ($_POST['clearbtn'] != "") {
205
		$action = gettext("Delete");
206
		//delete previous nmap results file if it exists
207
		unlink_if_exists($fp.$fn);
208
		unlink_if_exists($fp.$fe);
209
	}
210
}
211

  
212
$pgtitle = array("Package", "Diagnostics: Nmap");
213
include("head.inc");
214

  
215
/*
216
$tab_array = array();
217
$tab_array[] = array(gettext("Nmap Scan"), true, "/nmap_scan.php");
218
display_top_tabs($tab_array);
219
*/
220

  
221
if ($input_errors) {
222
	print_input_errors($input_errors);
223
}
224

  
225
$form = new Form(false); // No button yet. We add those later depending on the required action
226

  
227
$section = new Form_Section('General Options');
228

  
229
$section->addInput(new Form_Input(
230
	'hostname',
231
	'*IP or Hostname',
232
	'text',
233
	$hostname
234
))->setHelp('Enter the IP address or hostname that you would like to scan.');
235

  
236
$section->addInput(new Form_Select(
237
	'interface',
238
	'Interface',
239
	$interface,
240
	$interfaces
241
))->setHelp('Select the source interface here.');
242

  
243
$section->addInput(new Form_Select(
244
	'scantype',
245
	'*Scan Type',
246
	$scantype,
247
	$scan_types
248
))->setHelp('Select the scan type');
249

  
250
$section->addInput(new Form_Checkbox(
251
	'noping',
252
	'-P0',
253
	'Do not attempt to ping hosts before scanning',
254
	$_POST['noping']
255
))->setHelp('Allow scanning of networks that do not answer echo requests.%1$s' .
256
			'%2$smicrosoft.com is an example of such a network, and thus you should always use -P0 or -PT80 when port scanning microsoft.com.%3$s' .
257
			'Note the "ping" in this context may involve more than the traditional ICMP echo request packet. Nmap supports many such probes, including arbitrary combinations of TCP, UDP, and ICMP probes.%3$s' .
258
			'By default, Nmap sends an ICMP echo request and a TCP ACK packet to port 80.%4$s',
259

  
260
			'<span class="infoblock" style="font-size:90%"><br />',
261
			'<p style="margin:0px;padding:0px">',
262
			'<br />',
263
			'</p></span>');
264

  
265
$section->addInput(new Form_Checkbox(
266
	'servicever',
267
	'-sV',
268
	'Attempt to identify service versions',
269
	$_POST['servicever']
270
))->setHelp('Try to detect services running on discoverd ports.%1$s' .
271
			'%2$sAfter TCP and/or UDP ports are discovered using one of the other scan types, version detection communicates with those ports to try and determine more about what is actually running.%3$s' .
272
			'A file called nmap-service-probes is used to determine the best probes for detecting various services and the match strings to expect.%3$s' .
273
			'Nmap tries to determine the service protocol (e.g. ftp, ssh, telnet, http), the application name (e.g. ISC Bind, Apache httpd, Solaris telnetd), ' .
274
			'the version number, and sometimes miscellaneous details like whether an X server is open to connections or the SSH protocol version).%4$s',
275

  
276
			'<span class="infoblock" style="font-size:90%"><br />',
277
			'<p style="margin:0px;padding:0px">',
278
			'<br />',
279
			'</p></span>');
280

  
281
$section->addInput(new Form_Checkbox(
282
	'osdetect',
283
	'-O',
284
	'Enable Operating System detection',
285
	$_POST['osdetect']
286
))->setHelp('Try to identify remote host via TCP/IP fingerprinting.%1$s' .
287
			'%2$sIn other words, it uses techniques to detect subtleties in the underlying operating system network stack of the computers being scanned.%3$s' .
288
			'It uses this information to create a "fingerprint" which it compares with its database of known OS fingerprints' .
289
			'(the nmap-os-fingerprints file) to determine the operating system of the target host.%4$s',
290

  
291
			'<span class="infoblock" style="font-size:90%"><br />',
292
			'<p style="margin:0px;padding:0px">',
293
			'<br />',
294
			'</p></span>');
295

  
296
$section->addInput(new Form_Input(
297
	'custom_scantype',
298
	'Custom Scan Type',
299
	'text',
300
	$custom_scantype
301
))->setHelp('Enter a custom Scan Type. It will override above selected Scan Type.%1$s' .
302
			'%2$sExp: -sA -sW -sFs%3$s',
303

  
304
			'<br />',
305
			'<p style="margin:0px;padding:0px;font-size:90%">',
306
			'</p>');
307

  
308
$section->addInput(new Form_Input(
309
	'custom_options',
310
	'Custom Options',
311
	'text',
312
	$custom_options
313
))->setHelp('Enter extra scan options.%1$s' .
314
			'%2$sExp: --traceroute --dns-servers server1 -p <port ranges>%3$s',
315

  
316
			'<br />',
317
			'<p style="margin:0px;padding:0px;font-size:90%">',
318
			'</p>');
319

  
320
$section->addInput(new Form_Input(
321
	'custom_targetspecs',
322
	'Custom Target Specs',
323
	'text',
324
	$custom_targetspecs
325
))->setHelp('Enter additional hosts or extra target options.%1$s' .
326
			'%2$sExp: ip hostname --exclude host1,host2 -iR <num hosts>%3$s',
327

  
328
			'<br />',
329
			'<p style="margin:0px;padding:0px;font-size:90%">',
330
			'</p>');
331

  
332
$form->add($section);
333

  
334
/* check if nmap scan is already running */
335
$processes_running = nmap_get_running_process();
336
$processisrunning = ($processes_running != "");
337

  
338
if ($processisrunning or $do_nmapscan) {
339
	$form->addGlobal(new Form_Button(
340
		'stopbtn',
341
		'Stop',
342
		null,
343
		'fa-stop-circle'
344
	))->addClass('btn-warning');
345

  
346
	$form->addGlobal(new Form_Button(
347
		'refreshbtn',
348
		'Refresh Results',
349
		null,
350
		'fa-retweet'
351
	))->addClass('btn-primary');
352
} else {
353
	$form->addGlobal(new Form_Button(
354
		'startbtn',
355
		'Start',
356
		null,
357
		'fa-play-circle'
358
	))->addClass('btn-success');
359

  
360
	if (file_exists($fp.$fn) or file_exists($fp.$fe)) {
361
		$form->addGlobal(new Form_Button(
362
			'viewbtn',
363
			'View Results',
364
			null,
365
			'fa-file-text-o'
366
		))->addClass('btn-primary');
367

  
368
		$form->addGlobal(new Form_Button(
369
			'downloadbtn',
370
			'Download Results',
371
			null,
372
			'fa-download'
373
		))->addClass('btn-primary');
374

  
375
		$form->addGlobal(new Form_Button(
376
			'clearbtn',
377
			'Clear Results',
378
			null,
379
			'fa-trash'
380
		))->addClass('btn-danger');
381
	}
382
}
383

  
384
if (file_exists($fp.$fn)) {
385
	$section->addInput(new Form_StaticText(
386
		'Last scan results',
387
		date("F jS, Y g:i:s a.", filemtime($fp.$fn))
388
	));
389
}
390

  
391
if (file_exists($fp.$fe) and filesize($fp.$fe) > 0) {
392
	$section->addInput(new Form_StaticText(
393
		'Last scan error',
394
		date("F jS, Y g:i:s a.", filemtime($fp.$fe))
395
	));
396
}
397

  
398
print($form);
399

  
400
if ($do_nmapscan) {
401
	$cmd = "/usr/local/bin/nmap {$nmap_options} >/dev/null 2>{$fp}{$fe} &";
402
	exec($cmd);
403
	print_info_box(gettext('Nmap scan is running' . '<br />' . 'Press info button to show command'), 'info');
404
	?>
405
	<div class="infoblock">
406
	<? print_info_box(gettext('Command line') . ': ' . htmlspecialchars($cmd), 'info', false); ?>
407
	</div>
408
	<?php
409

  
410
} elseif ($action == gettext("View") or $action == gettext("Stop")) {
411
		if (file_exists($fp.$fe) and filesize($fp.$fe) > 0) {
412
?>
413

  
414
<div class="panel panel-default">
415
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Scan Errors')?></h2></div>
416
	<div class="panel-body">
417
		<div class="form-group">
418
<?php
419

  
420
			print('<textarea class="form-control" rows="10" style="font-size: 13px; font-family: consolas,monaco,roboto mono,liberation mono,courier;">');
421
			if (filesize($fp.$fe) > $max_display_size) {
422
				print(gettext("Nmap scan error file is too large to display in the GUI.") .
423
					"\n" .
424
					gettext("Download the file, or view it in the console or ssh shell.") .
425
					"\n" .
426
					gettext("Error file: {$fp}{$fe}"));
427
			} else {
428
				print(file_get_contents($fp.$fe));
429
			}
430
			print('</textarea>');
431

  
432
?>
433
		</div>
434
	</div>
435
</div>
436
<?php
437
		}
438

  
439
?>
440

  
441
<div class="panel panel-default">
442
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Scan Results')?></h2></div>
443
	<div class="panel-body">
444
		<div class="form-group">
445
<?php
446

  
447
		print('<textarea class="form-control" rows="20" style="font-size: 13px; font-family: consolas,monaco,roboto mono,liberation mono,courier;">');
448
		if (file_exists($fp.$fn) and (filesize($fp.$fn) > $max_display_size)) {
449
			print(gettext("Nmap scan results file is too large to display in the GUI.") .
450
				"\n" .
451
				gettext("Download the file, or view it in the console or ssh shell.") .
452
				"\n" .
453
				gettext("Results file: {$fp}{$fn}"));
454
		} elseif (!file_exists($fp.$fn) || (filesize($fp.$fn) === 0)) {
455
			print(gettext("No nmap scan results to display."));
456
		} else {
457
			print(file_get_contents($fp.$fn));
458
		}
459
		print('</textarea>');
460

  
461
?>
462
		</div>
463
	</div>
464
</div>
465
<?php
466
}
467

  
468
include("foot.inc");
(22-22/30)