Skip to content

ytoolshed/passwordmonkey-perl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

######################################################################
    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 <cpan@perlmeister.com>

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).

About

PasswordMonkey CPAN Module

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages