forked from zenfish/ipmi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathipmi_scan.pl
executable file
·485 lines (408 loc) · 12.9 KB
/
ipmi_scan.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
#!/usr/bin/perl
#
# Network scan that looks for signs of systems running IPMI.
#
#
# Written by dan farmer/[email protected].
#
# Not that its ever stopped anyone before from using my stuff (ahem... yes,
# that's you all scan vendors... forensic companies... audit tools... OS
# vendors... but, w/e.) All rights reserved, code preserved, patents applied
# for, trademarks ensuing, lawyers waitin, mercenaries armed, weapons locked
# and loaded....
#
# Well, at least some of the above is true. But as you know, all coders are liars.
#
# Hmm. Need guns. Lots more guns.
#
#
# Requires nmap and ipmitools.
#
# Either run as root or have nmap SUID root.
#
#
# How to use:
#
# At the minimum, give a target type that nmap understands...
# that is a huge range of things going from individual hosts
# to wildcards to CIDRs and so on, but at the simplest an
# IP address or hostname. So:
#
# $0.pl [options] target
#
# Options:
#
# -A Scan with all the vendor options set. If used
# I'll ignore the -V options
#
# -f file read the targets from a file
#
# -h/-help print how to use tool stuff & exit
#
# -I /path/to/ipmitool if you want to use a specific ipmitool binary, path here
#
# -N /path/to/nmap if you want to use a specific nmap binary, , path here
#
# -O value for OS scanning... values can be either "yes" or "high";
# high simply does more aggressive scanning.
# Defaults to not scanning OS... but maybe should change this.
#
# --tcp enable TCP port scanning
# --udp enable UDP port scanning
#
# UDP scanning makes paint drying look fun. If you
# specify both of these there will be no change; both
# udp & tcp will run. Specifying only one of them says
# scan that proto's ports; let me assure you under normal
# circumstances --tcp will speed things up, but you won't
# see the UDP stuff... this may or may not matter, depending
# on what you're looking for, doing, etc.
#
# -v verbose
#
# -V vendor foo,bar,baz comma sep'd vendors (don't fuck with me and put
# commas in a vendor or I'll parse you wrong out
# of spite). Could split this further into tcp/udp,
# but for now....
#
# -version print version number & exit
#
#
# Thoughts/notes
#
# -O/OS option... might want to do an OS scan and then go back and scan with
# the appropriate opts, or make a better analysis of the target... hopefully
# with data gathered and more experience this will become clearer as to
# what to do.
#
#
#
# a few key vars for use down below
#
#
# pretty much always this... certainly for starters!
#
$IPMI_PORT = 623;
# number of IPMI ping attempts to send
$IPMI_PING_ATTEMPTS = 2;
#
# includes... need these... builtin, shouldn't be an issue unless things are whacked
#
use Getopt::Long qw(:config no_ignore_case);
# this runs as root or SUID... chmod it for now, figure out what to do later
umask 000;
#
# process command line arg stuff
#
# nifty new (probably over a decade old, I hadn't known ;)) perly bits
sub main::VERSION_MESSAGE {
print "$1 0.0.1\n";
exit;
};
sub main::HELP_MESSAGE {
print "Usage: $0 [options] target\n".
"\t-A\t\t\tScan with all vendor specific tests on... will ignore -V flag if set.\n".
"\t-f file\t\t\t\tRead the targets from a file\n".
"\t-h/help\t\t\t\tPrint help text and exit\n".
"\t-I /path/to/ipmitool\t\tUse a specific verion of ipmitool\n".
"\t-N /path/to/nmap\t\tUse a specific verion of nmap\n".
"\t-O yes|high\t\tOS scanning: do it (yes) or do it agressively (high)\n".
"\t-tcp\t\t\t\tOnly do TCP scans... unles -udp is also given\n".
"\t-udp\t\t\t\tOnly do UDP scans... unles -tcp is also given\n".
"\t-v\t\t\t\tVerbose\n".
"\t-V vendor1,v2,v3\t\tVendor specific tests; one+ comma sep'd vendor names\n".
"\t-version\t\t\tPrint version #\n";
exit;
};
GetOptions(
'A' => \$ALL_vendors,
'f=s' => \$file,
'h' => \$help,
'help' => \$help,
'I=s' => \$ipmitool,
'N=s' => \$nmap,
'O=s' => \$OS_scan,
'tcp' => \$scan_tcp,
'udp' => \$scan_udp,
'v' => \$verbose,
'V=s' => \$vendors,
'version' => \$version
) || die main::HELP_MESSAGE();
if (defined($verbose)) { $verbose = 1; }
if (defined($ALL_vendors)) { $ALL_vendors = 1; }
if (defined($scan_tcp)) { $scan_tcp = 1; }
if (defined($scan_udp)) { $scan_udp = 1; }
#
# -tcp & -udp flags can cancel each other out
#
if ($scan_tcp && $scan_udp) {
$scan_tcp = $scan_udp = 0;
}
#
# who are the lucky few?
#
if ($file eq "") {
$targets = join(" ", @ARGV);
}
else {
$targets = "-iL $file";
}
die main::HELP_MESSAGE() unless $targets ne "";
print "Targets are $targets\n" if $verbose;
#
# Nmap results written to file: $target + .txt
# ipmiping results written to file: $target + .ipmi
#
# strip /'s so I can write to the file (e.g. 10/8 => 10_8)
($tmp_tar = $targets) =~ s@/@_@g;
$tmp_tar = $file if $file ne "";
$results_nmap = "$tmp_tar.nmap.txt";
$results_ipmi = "$tmp_tar.ipmi.txt";
# truncate in case of previous results
open(IPMI_RES, "> $results_ipmi") || die "can't open $results_ipmi file for ipmiping results\n";
close(IPMI_RES);
#
#
# Find tools required (currently nmap & ipmitool), do permission checks
#
#
@toolz = ('nmap', 'ipmitool');
#
# toolname = 1/0: 1 means must be SUID, 0 just executable
#
%toolz = (
'nmap', 1,
'ipmitool', 0,
'ipmiping', 0
);
# if system binaries won't work, point to your private stash
if (defined($opts{I})) { $tool_location{'ipmitool'} = $opts{I}; }
else { $tool_location{'ipmitool'} = "ipmitool"; }
if (defined($opts{N})) { $tool_location{'nmap'} = $opts{N}; }
else { $tool_location{'nmap'} = "nmap"; }
$tool_location{'ipmiping'} = "ipmiping";
# not sure what to do... probably won't require ipmiping; for now just
# pass over it if can't find it
$ipmiping = 0;
# for $tool (@toolz) {
for $tool (keys %toolz) {
# print "T: $tool => " . $toolz{$tool} . "\n";
next if ($tool eq "");
# yeah or nay?
$suid = $toolz{$tool};
# out there anywhere?
print "which $tool_location{$tool}\n" if $verbose;
chomp($tool_location{$tool} = `which $tool_location{$tool}`);
print "looking for $tool...\n" if $verbose;
if ($tool_location{$tool} eq "") {
if ($tool eq "ipmiping") {
print "couldn't find ipmiping, tool won't be as accurate without...\n";
$tool_location{$tool} = "ipmiping";
$ipmiping = 1;
}
else {
die "\t$0 requires $tool to run\n";
}
}
else {
print "\tfound $tool_location{$tool}\n" if $verbose;
}
if ($suid) {
# gotta laugh at perl somtimes....
$euid = $<;
# euid = 0 = root
if ($euid) {
# check for SUID
die "Must run $tool_location{$tool} as root or have $tool SUID\n" unless -u $tool_location{$tool};
}
}
if (! -x $tool_location{$tool} && ! $ipmiping) {
die "$tool_location{$tool} isn't executable ($ipmiping)\n";
}
}
#
# figure out how to do this ;)
#
# require "j_vendor.pl";
$file = "j_vendor.pl";
unless ($return = do $file) {
die "couldn't parse $file: $@" if $@;
die "couldn't do $file: $!" unless defined $return;
die "couldn't run $file" unless $return;
}
# ($all_IPMI_udp_ports, $all_IPMI_tcp_ports, $all_vendor_ports) = vendorish();
#
#
# Finally... first, we run nmap with the ports constructed above against the
# targets specified.
#
#
#
# NMAP
# NMAP below
# NMAP
#
#
# construct nmap's options... when doing both UDP/TCP, nmap requires a small bit of
# trickery; the T/U flags on the ports to distinguish between U/T
#
$udp_opts = "";
$tcp_opts = "";
$all_ports = "-p ";
if ($all_IPMI_udp_ports ne "") {
$udp_opts = "-sU";
$all_ports .= "U:$all_IPMI_udp_ports";
}
if ($all_IPMI_tcp_ports ne "") {
#
# default nmap scan... we'll use it for now, but have to specify
# when doing tcp+udp simultaneously
#
$tcp_opts = "-sS";
# put a comma between if both
if ($all_IPMI_udp_ports ne "") {
$all_ports .= ",";
}
$all_ports .= "T:$all_IPMI_tcp_ports";
}
#
# from the nmap docs:
#
# OS DETECTION:
# -O: Enable OS detection
# --osscan-guess: Guess OS more aggressively
#
# parse the -O option to figure out what to do
#
if (defined($OS_scan)) {
if ($OS_scan eq "yes") {
$OS_opts = "-O";
}
elsif ($OS_scan eq "high") {
$OS_opts = "--osscan-guess";
}
else {
die "unknown value for -O: $OS_scan\n";
}
}
else {
$OS_opts = "";
}
print "OS scan options to be passed to nmap are \"$OS_opts\"\n" if $verbose;
#
# Nmap options; from nmap:
#
# -sV: Probe open ports to determine service/version info
# --script=banner: uses an nmap NSE script to dump out more data on banners found
#
$nmap_options = "-vvv --reason -T4 -sV --script=banner $OS_opts ";
# verbose might be required, I think, to do real-timish processing to
# speed things along
$nmap_options .= "$udp_opts $tcp_opts $all_ports -v";
# for testing... output to file
$nmap_options = "-oN $results_nmap $nmap_options";
$nmap_exe = "$tool_location{'nmap'} $nmap_options $targets";
print "\nRunning $nmap_exe \n\n" if $verbose;
# if ($fooooo) {
open(NMAP_EXE, "$nmap_exe |") || die "can't run $nmap_exe\n";
while (<NMAP_EXE>) {
print $_;
# if see any open TCP/UDP 623s/IPMI_PORT, hit again with ipmtool
# to see if it's really ipmi/alive
if (/Discovered open port $IPMI_PORT\/..p on/) {
if (!defined($ipmi_ping_results{$target})) {
print "... checking...\n";
m/Discovered open port $IPMI_PORT\/(..p) on (.*)$/;
$proto = $1;
$target = $2;
# print "forking off $1 ipmi scan vs. $2\n" if $verbose;
print "forking off $proto ipmi scan vs. $target\n";
#
# ipmitool
# ipmitool run in function below
# ipmitool
#
if (!ipmi_ping($target, $proto)) {
$ipmi_ping_results{$target} = 1;
}
else {
$ipmi_ping_results{$target} = 0;
}
}
}
}
close(NMAP_EXE);
#
# A little sub that uses ipmiping to determine if a host is up or
# down, IPMI-wise. Send two tries (this is UDP, after all, try
# to be a bit safer; 1 too few, 3+ too many?), although may send
# more/less as I learn more....
#
# looks something like this when run:
#
# Non-IPMI:
#
# # ipmiping -c 1 -v 192.168.0.55
# ipmiping 192.168.0.55 (192.168.0.55)
# response timed out: rq_seq=56
# --- ipmiping 192.168.0.55 statistics ---
# 1 requests transmitted, 0 responses received in time, 100.0% packet loss
#
# IPMI host:
#
# # ipmiping -c 1 -v 192.168.0.69
# ipmiping 192.168.0.69 (192.168.0.69)
# response received from 192.168.0.69: rq_seq=20, auth: none=clear md2=set md5=set password=clear oem=set anon=clear null=clear non-null=set user=clear permsg=clear
# --- ipmiping 192.168.0.69 statistics ---
#
sub ipmi_ping {
local($target, $proto) = @_;
print "off to i-ping with ipmiping $target\n" if $verbose;
if ($ipmpiping) {
print "... oops, I take that back... ipmiping wasn't found, remember?\n" if $verbose;
return 6;
}
open(IPMI_RES, "> $results_ipmi") || die "can't open $results_ipmi file for ipmiping results\n";
$valid_stuff = "[a-zA-Z0-9\.\-]";
#
# only allow letters, numbers, and a few diff types chars for a target
# (should be simple IP or hostname)
#
if ($target !~ /$valid_stuff/) {
print "weirdy chars in target2 ($target)!\n" if $verbose;
return 1;
}
# damn the torpedos, pingify that sucker, Ms. Sulu!
# print "ipmiping -c 1 -v $target |\n";
print "\nrunning: ipmiping -c 1 -v $target \|\n" if $verbose;
open(IPING, "ipmiping -c 1 -v $target |") || die "can't run ipmiping against $target\n";
#
# expecting something like (all one line):
#
# response received from 192.168.0.69: rq_seq=20, auth: none=clear \
# md2=set md5=set password=clear oem=set anon=clear null=clear \
# non-null=set user=clear permsg=clear
#
# For now just throw away results, looking for that magic response line... I'll be
# ripping into this more later, or perhaps doing it here... haven't decided yet....
#
$success = "";
while (($_iping = <IPING>)) {
# print "KKK: $_iping";
chomp($_iping);
if ($_iping =~ /response received/) {
$success = $_iping;
}
}
close(IPING);
if ($success ne "") {
print "Successful ipmi-ping: $success\n" if $verbose;
print IPMI_RES "Successful ipmi-ping: $success\n";
}
else {
print "ipmi-ping failed\n" if $verbose;
print IPMI_RES "ipmi-ping failed\n";
}
close(IPMI_RES);
return 0;
}