-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathREADME
467 lines (349 loc) · 16.4 KB
/
README
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
######################################################################
PasswordMonkey 0.09
######################################################################
NAME
PasswordMonkey - Password prompt responder
SYNOPSIS
use PasswordMonkey;
use PasswordMonkey::Filler::Sudo;
use PasswordMonkey::Filler::Adduser;
my $sudo = PasswordMonkey::Filler::Sudo->new(
password => "supersecrEt",
);
my $adduser = PasswordMonkey::Filler::Adduser->new(
password => "logmein",
);
my $monkey = PasswordMonkey->new(
timeout => 60,
);
$monkey->filler_add( $sudo );
$monkey->filler_add( $adduser );
# Spawn a script that asks for
# - the sudo password and then
# - the new password for 'adduser' twice
$monkey->spawn("sudo adduser testuser");
# Password monkey goes to work
$monkey->go();
# ==== In action:
# [sudo] password for mschilli:
# (waits two seconds)
# ******** (types 'supersecrEt\n')
# ...
# Copying files from `/etc/skel' ...
# Enter new UNIX password:
# ******** (types 'logmein')
# Retype new UNIX password:
# ******** (types 'logmein')
DESCRIPTION
PasswordMonkey is a plugin-driven approach to provide passwords to
prompts, following strategies human users would employ as well. It comes
with a set of Filler plugins who know how to deal with common
applications expecting password input (sudo, ssh) and a set of Bouncer
plugins who know how to employ different security strategies once a
prompt has been detected. It can be easily extended to support
additional applications.
That being said, let me remind you that USING PLAINTEXT PASSWORDS IN
AUTOMATED SYSTEMS IS ALMOST ALWAYS A BAD IDEA. Use ssh keys, custom sudo
rules, PAM modules, or other techniques instead. This Expect-based
module uses plain text passwords and it's useful in a context with
legacy applications, because it provides a slightly better and safer
mechanism than simpler Expect-based scripts, but it is still worse than
using passwordless technologies. You've been warned.
Methods
"new()"
Creates a new PasswordMonkey object. Imagine this as a trained
monkey who knows to type a password when prompt shows up on a
terminal.
Optionally, the constructor accepts a "timeout" value (defaults to
60 seconds), after which it will stop listening for passwords and
terminate the go() call with a 'timed_out' message:
my $monkey = PasswordMonkey->new(
timeout => 60,
);
"filler_add( $filler )"
Add a filler plugin to the monkey. A filler plugin is a module that
defines which password to type on a given prompt: "If you see
'Password:', then type 'supersecrEt' with a newline". There are a
number of sample plugins provided with the PasswordMonkey core
distribution, namely "PasswordMonkey::Filler::Sudo" (respond to sudo
prompts with a given password) and
"PasswordMonkey::Filler::Password" (respond to "adduser"'s password
prompts to change a user's password.
But these are just examples, the real power of PasswordMonkey comes
with writing your own custom filler plugins. The API is very simple,
a new filler plugin is just a matter of 10 lines of code. Writing
your own custom filler plugins allows you mix and match those
plugins later and share them with other users on CPAN (think
"PasswordMonkey::Filler::MysqlClient" or
"PasswordMonkey::Filler::SSH").
To create a filler plugin object, call its constructor:
my $sudo = PasswordMonkey::Filler::Sudo->new(
password => "supersecrEt",
);
and then add it to the monkey:
$monkey->filler_add( $sudo );
and when you say
$monkey->spawn( "sudo ls" );
$monkey->go();
later, the monkey fill in the "supersecrEt" password every time the
spawned program asks for something like
[sudo] password for joe:
As mentioned above, writing a filler plugin is easy, here is the
entire PasswordMonkey::Filler::Sudo implementation:
package PasswordMonkey::Filler::Sudo;
use strict;
use warnings;
use base qw(PasswordMonkey::Filler);
sub prompt {
my($self) = @_;
return qr(\[sudo\] password for [\w_]+:);
}
1;
All that's required from the plugin is a "prompt()" method that
returns a regular expression that matches the prompts the filler
plugin is supposed to respond to. You don't need to deal with
collecting the password, because it gets passed to the filler plugin
constructor, which is taken care of by the base class
"PasswordMonkey::Filler". Note that "PasswordMonkey::Filler::Sudo"
inherits from "PasswordMonkey::Filler" with the "use base"
directive, as shown in the code snippet above.
"spawn( $command )"
Spawn an external command (e.g. "sudo ls") to whose password prompts
the monkey will keep responding later.
"go()"
Starts the monkey, which will respond to password prompts according
to the filler plugins that have been loaded, until it times out or
the spawned program exits.
The $monkey->go() method call returns a true value upon success, so
running
if( ! $monkey->go() ) {
print "Something went wrong!\n";
}
will catch any errors.
"is_success()"
After go() has returned,
$monkey->is_success();
will return true if the spawned program exited with a success return
code. Note that hitting a timeout or a bad exit status of the
spawned process is considered an error. To check for these cases,
use the "exit_status()" and "timed_out()" accessors.
"exit_status()"
After "go()" has returned, obtain the exit code of spawned process:
if( $monkey->exit_status() ) {
print "The process exited with rc=", $monkey->exit_status(), "\n";
}
Note that "exit_status()" returns the Perl-specific return code of
"system()". If you need the shell-specific return code, you need to
use "exit_status() >> 8" instead (check 'perldoc -f system' for
details).
"timed_out()"
After "go()" has returned, check if the monkey timed out or
terminated because the spawned process exited:
if( $monkey->timed_out() ) {
print "The monkey timed out!\n";
} else {
print "The spawned process has exited!\n";
}
"fills()"
After "go()" has returned, get the number of password fills the
monkey performed:
my $nof_fills = $monkey->fills();
Fillers
The following fillers come bundled with the PasswordMonkey distribution,
but they're included only as fully functional study examples:
PasswordMonkey::Filler::Sudo
Sudo passwords
Running a command like
$ sudo ls
[sudo] password for mschilli:
********
PasswordMonkey::Filler::Password
Responds to any "password:" prompts:
$ adduser wonko
Copying files from `/etc/skel' ...
Enter new UNIX password:
********
Retype new UNIX password:
********
Read on, and later you'll find an expanation on how to write your own
custom fillers to talk to random programs asking for passwords.
Bouncer Plugins
You might be wondering: "What if I use a simple password filler
responding to 'password:' prompts and the mysql client prints 'password:
no' as part of its diagnostic output?"
With previous versions of PasswordMonkey you were in big trouble,
because PasswordMonkey would then send the password to an unsilenced
terminal, which echoed the password, which ended up on screen or in log
files of automated processes. Big trouble! For this reason,
PasswordMonkey 0.09 and up will silence the terminal the password gets
sent to proactively as a precaution.
Bouncer plugins can configure a number of security checks to run after a
prompt has been detected. These checks are also implemented as plugins,
and are added to filler plugins via their "bouncer_add" method.
Verifying inactivity after password prompts: Bouncer::Wait
To make sure that we are actually dealing with a sudo password prompt in
the form of
# [sudo] password for joeuser:
and not just a fly-by text string matching the prompt regular
expression, we add a Wait Bouncer object to it, which blocks the Sudo
plugin's response until two seconds have passed without any other
output, making sure that the application is actually waiting for input:
use PasswordMonkey;
my $sudo = PasswordMonkey::Filler::Sudo->new(
password => "supersecrEt",
);
my $wait_two_secs =
PasswordMonkey::Bouncer::Wait->new( seconds => 2 );
$sudo->bouncer_add( $wait_two_secs );
$monkey->filler_add( $sudo );
$monkey->spawn("sudo ls");
This will spawn sudo, detect if it's asking for the user's password by
matching its output against a regular expression, and, upon a match,
waits two seconds and proceeds only if there's no further output
activity until then.
Hitting enter to see prompt reappear: Bouncer::Retry
To see if a password prompt is really genuine, PasswordMonkey hits enter
and verifies the prompt reappears:
Password:
Password:
before it starts typing the password.
use PasswordMonkey;
my $sudo = PasswordMonkey::Filler::Sudo->new(
password => "supersecrEt",
);
my $retry =
PasswordMonkey::Bouncer::Retry->new( timeout => 2 );
$sudo->bouncer_add( $retry );
$monkey->filler_add( $sudo );
$monkey->spawn("sudo ls");
Filler API
Writing new filler plugins is easy, see the sudo plugin as an example:
package PasswordMonkey::Filler::Sudo;
use strict;
use warnings;
use base qw(PasswordMonkey::Filler);
sub prompt {
return qr(^\[sudo\] password for [\w_]+:\s*$);
}
That's it. All that's required is that you
* let your plugin inherit from the PasswordMonkey::Filler base class
and
* override the "prompt" method to return a regular expression for the
p rompt upon which the plugin is supposed to send its password.
But you can write fancier plugins if you want.
Optionally, you can add an "init()" method in the filler plugin that the
monkey will call during initialization time:
sub init {
my($self) = @_;
$self->{ my_secret_stash } = [];
# ...
}
Through inheritance, the plugin will then make sure that if you create a
new plugin object with a password setting like
my $sudo = PasswordMonkey::Filler::Sudo->new(
password => "supersecret",
);
then inside the plugin, the password is available as
"$self-$<gt"password()>. For example, if you don't like the default
password sending routine (which comes courtesy of the base class
PasswordMonkey::Filler), you could write your own:
sub fill {
my($self, $exp, $monkey) = @_;
$exp->send( $self->password(), "\n" );
}
What just happened? We overwrote "fill" method which the monkey calls in
order to fill in the password on a prompt that the plugin said it was
interested in earlier. Okay, we've got it covered now, here's the full
filler plugin API:
init
(Optional).
prompt
(Required). Returns a regular expression matching password prompts
the plugin is interested in.
fill
(Optional). Called by the monkey to have the plugin send over the
password. Receives "($self, $exp, $monkey)" as arguments, which are
references to the plugin object itself, the Expect object and the
PasswordMonkey object.
pre_fill
(Optional). Called by the monkey before the password fill. Receives
"($self, $exp, $monkey)" as arguments, which are references to the
plugin object itself, the Expect object and the PasswordMonkey
object.
post_fill
(Optional). Called by the monkey before the password fill. Receives
"($self, $exp, $monkey)" as arguments, which are references to the
plugin object itself, the Expect object and the PasswordMonkey
object.
Every filler plugin comes with three standard accessors which can also
be used as constructor parameters:
"name"
the name of the plugin, defaults to the class name
"password"
get/set the password
"dealbreakers"
get/set so-called dealbreakers. If one of those regular expressions
matches a pattern in the output of the controlled program,
PasswordMonkey will abort its "go" loop and exit with the given exit
code. For example, if you have
sub init {
$self->dealbreakers([
["Bad passphrase, try again:" => 255],
]);
}
and the spawned program says "Bad passphrase, try again", then the
monkey will stop immediately and report exit status 255. This is
useful for quickly aborting programs that have no chance to
continue, e.g. if one of the plugins has the wrong password, there's
no point in trying over and over again until the timeout kicks in.
If you want your plugin's constructor to take parameters which you can
later conventiently access in the plugin code via autogenerated
accessors, use PasswordMonkey's "make_accessor" call:
package PasswordMonkey::Filler::Wonky;
use strict;
use warnings;
use base qw(PasswordMonkey::Filler);
PasswordMonkey::make_accessor( __PACKAGE__, $_ ) for qw(
foo bar baz
);
This plugin can then be initialized by saying
my $wonky = package PasswordMonkey::Filler::Wonky->new(
foo => "moo",
bar => "neigh",
baz => "tweet",
);
Debugging
PasswordMonkey is Log4perl-enabled, which lets you remote-control the
amount of internal debug messages you're interested in. If you're not
familiar with Log4perl (most likely because you've been living in a cage
for the last 25 years), here's the easiest way to activate all debug
messages within PasswordMonkey:
use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);
For more granular control, please consult the Log4perl documentation.
Bouncer API
Bouncer plugins define checks to be executed right before we send over
the password to detect irregularities and pull the plug at the last
minute if something doesn't look right. A bouncer plugin is attached to
a filler plugin by the add_bouncer() method:
$filler->add_bouncer( $bouncer );
The filler then calls the bouncer plugin's "check()" method right before
it fills in the password with the "fill()" method. If "check()" returns
a true value, the filler proceeds. If "check()" comes back with a false
value, the filler plugin aborts and returns to the monkey without
sending the password to the spawned process.
If you need access to the "Expect"-Object (e.g. to find out what the
current match is or what the text previous to the match was), you can
use the "expect()" accessor that comes through inheritance with every
bouncer plugin:
my $expect = $self->expect();
To get a better idea about what can be done with bouncer plugins, check
out the source code of the two bouncers that come with the distribution,
PasswordMonkey::Bouncer::Wait and PasswordMonkey::Bouncer::Retry. Their
code is relatively simple and should be easy to follow.
AUTHOR
2011, Mike Schilli <[email protected]>
COPYRIGHT & LICENSE
Copyright (c) 2011 Yahoo! Inc. All rights reserved. The copyrights to
the contents of this file are licensed under the Perl Artistic License
(ver. 15 Aug 1997).