The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Net::MitM - Man in the Middle - connects a client and a server, giving visibility of and control over messages passed.

VERSION

Version 0.03_02

SYNOPSIS

Net::MitM is designed to be inserted between a client and a server. It proxies all traffic through verbatum, and also copies that same data to a log file and/or a callback function, allowing a data session to be monitored, recorded, even altered on the fly.

MitM acts as a 'man in the middle', sitting between the client and server. To the client, MitM looks like the server. To the server, MitM looks like the client.

MitM cannot be used to covertly operate on unsuspecting client/server sessions - it requires that you control either the client or the server. If you control the client, you can tell it to connect via your MitM. If you control the server, you can move it to a different port, and put a MitM in its place.

When started, MitM opens a socket and listens for connections. When that socket is connected to, MitM opens another connection to the server. Messages from either client or server are passed to the other, and a copy of each message is, potentially, logged. Alternately, callback methods may be used to add business logic, including potentially altering the messages being passed.

MitM can also be used as a proxy, allowing two processes on machines that cannot 'see' each other to communicate via an intermediary machine that is visible to both.

There is an (as yet unreleased) sister module Net::Replay that allows a MitM session to be replayed.

Usage

Assume the following script is running on the local machine:

    use Net::MitM;
    my $MitM = Net::MitM->new("cpan.org", 80, 10080);
    $MitM->log_file("MitM.log");
    $MitM->go();

A browser connecting to http://localhost:10080 will now cause MitM to open a connection to cpan.org, and messages sent by either end will be passed to the other end, and logged to MitM.log.

For another example, see samples/mitm.pl in the MitM distribution.

Modifying messages on the fly - a worked example.

However you deploy MitM, it will be virtually identical to having the client and server talk directly. The difference will be that either the client and/or server will be at an address other than the one its counterpart believes it to be at. Most programs ignore this, but sometimes it matters.

For example, HTTP browsers pass a number of parameters, one of which is "Host", the host to which the browser believes it is connecting. Often, this parameter is unused. But sometimes, a single HTTP server will be serving content for more than one website. Such a server generally relies on the Host parameter to know what it is to return. If the MitM is not on the same host as the HTTP server, the host parameter that the browser passes will cause the HTTP server to fail to serve the desired pages.

Further, HTTP servers typically return URLs containing the host address. If the browser navigates to a returned URL, it will from that point onwards connect directly to the server in the URL instead of communicating via MitM.

Both of these problems can be worked around by modifying the messages being passed.

For example, assume the following script is running on the local machine:

    use Net::MitM;
    sub send_($) {$_[0] =~ s/Host: .*:\d+/Host: cpan.org/;}
    sub receive($) {$_[0] =~ s/cpan.org:\d+/localhost:10080/g;}
    my $MitM = Net::MitM->new("cpan.org", 80, 10080);
    $MitM->client_to_server_callback(\&send,callback_behaviour=>"modify");
    $MitM->server_to_client_callback(\&receive,callback_behaviour=>"modify");
    $MitM->log_file("http_MitM.log");
    $MitM->go();

The send callback tells the server that it is to serve cpan.org pages, instead of some other set of pages, while the receive callback tells the browser to access cpan.org URLs via the MitM process, not directly. The HTTP server will now respond properly, even though the browser sent the wrong hostname, and the browser will now behave as desired and direct future requests via the MitM.

For another example, see samples/http_mitm.pl in the MitM distribution.

A more difficult problem is security aware processes, such as those that use HTTPS based protocols. They are actively hostname aware. Precisely to defend against a man-in-the-middle attack, they check their counterpart's reported hostname (but not normally the port) against the actual hostname. Unless client, server and MitM are all on the same host, either the client or the server will notice that the remote hostname is not what it should be, and will abort the connection. There is no good workaround for this, unless you can run an instance of MitM on the server, and another on the client - but even if you do, you still have to deal with the communication being encrypted.

SUBROUTINES/METHODS

new( remote_ip_address, remote_port_num, local_port_num )

Creates a new MitM

Parameters

  • remote_ip_address - the remote hostname/IP address of the server

  • remote_port_num - the remote port number of the server

  • local_port_num - the port number to listen on

  • Returns - a new MitM object

Usage

To keep a record of all messages sent:

    use Net::MitM;
    my $MitM = Net::MitM->new("www.cpan.org", 80, 10080);
    $MitM->log_file("MitM.log");
    $MitM->go();

go( )

Listen on local_port, accept incoming connections, and forwards messages back and forth.

Parameters

  • --none--

  • Returns --none--

Usage

When a connection on local_port is received a connect to remote_ip_address:remote_port_num is created and messages from the client are passed to the server and vice-versa.

If parallel() was set, which is not the default, there will be a new process created for each such session.

If any callback functions have been set, they will be called before each message is passed.

If logging is on, messages will be logged.

By default, go() does not return. The function stop_when_idle() can be called to force go() to return. You may want to fork before calling it.

If new_server() was used instead of new(), messages from client are instead passed to the server callback function.

name( [name] )

Names the object - will be reported back in logging/debug

Parameters

  • name - the new name (default is MitM1, MitM2, ...)

  • Returns - the current or new setting

Usage

For a minimal MitM:

    use Net::MitM;
    my $MitM = Net::MitM->new("www.cpan.org", 80, 10080);
    $MitM->go();

verbose( [level] )

Turns on/off reporting to stdout.

Parameters

  • level - how verbose to be. 0=nothing, 1=normal, 2=debug. The default is 1.

  • Returns - the current or new setting

Usage

Setting verbose changes the amount of information printed to stdout.

client_to_server_callback( callback [callback_behaviour => behaviour] )

Set a callback function to monitor/modify each message sent to server

Parameters

  • callback - a reference to a function to be called for each message sent to server

  • callback_behaviour - explicitly sets the callback as readonly, modifying or conditional.

  • Returns - the current or new setting

Usage

If a client_to_server_callback callback is set, it will be called with a copy of each message received from the client before it is sent to the server.

What the callback returns determines what will be sent, depending on the value of callback_behaviour:

  • If callback_behaviour = "readonly", the return value from the callback is ignored, and the original message is sent.

  • If callback_behaviour = "modify", the return value from the callback is sent instead of the original message, unless the return value is undef, in which case nothing is sent

  • If callback_behaviour = "conditional", which is the default, that the return value from the callback is sent instead of the original message, or if the return value is undef, then the original message is sent.

For example, to modify messages:

    use Net::MitM;
    sub send_($) {$_[0] =~ s/Host: .*:\d+/Host: cpan.org/;}
    sub receive($) {$_[0] =~ s/www.cpan.org(:\d+)?/localhost:10080/g;}
    my $MitM = Net::MitM->new("www.cpan.org", 80, 10080);
    $MitM->client_to_server_callback(\&send, callback_behaviour=>"modify");
    $MitM->server_to_client_callback(\&receive, callback_behaviour=>"modify");
    $MitM->go();

If the callback is readonly, it should either return a copy of the original message, or undef. Be careful not to accidentally return something else - remember that perl methods implicitly returns the value of the last command executed.

For example, to write messages to a log:

    sub peek($) {my $msg = shift; print LOG; return $msg;}
    my $MitM = Net::MitM->new("www.cpan.org", 80, 10080);
    $MitM->client_to_server_callback(\&peek, callback_behaviour=>"readonly");
    $MitM->server_to_client_callback(\&peek, callback_behaviour=>"readonly");
    $MitM->go();

For historical reasons, "conditional" is the default. It is not recommended, and may be deprecated in a future release.

"conditional" may be used for readonly or modify type behaviour. For readonly behaviour, either return the original message, or return null. For example:

    sub peek($) {my $msg = shift; print LOG; return $msg;}
    my $MitM = Net::MitM->new("www.cpan.org", 80, 10080);
    $MitM->client_to_server_callback(\&peek,callback_behaviour=>"readonly");
    ...

    sub peek($) {my $msg = shift; print LOG; return undef;}
    my $MitM = Net::MitM->new("www.cpan.org", 80, 10080);
    $MitM->client_to_server_callback(\&peek,callback_behaviour=>"readonly");
    ...

But be careful. This is unlikely to do what you would want: sub peek($) {my $msg = shift; print LOG} my $MitM = Net::MitM->new("www.cpan.org", 80, 10080); $MitM->client_to_server_callback(\&peek,callback_behaviour=>"readonly"); ...

Assuming print LOG succeeds, print will return a true value (probably 1), and MitM will send that value, not $msg.

server_to_client_callback( [callback] [,callback_behaviour=>behaviour] )

Set a callback function to monitor/modify each message received from server.

Parameters

  • callback - a reference to a function to be called for each inward message

  • callback_behaviour - explicitly sets the callback to readonly, modify or conditional.

  • Returns - the current or new setting of callback

Usage

If a server_to_client_callback callback is set, it will be called with a copy of each message received from the server before it is sent to the client.

What the callback returns determines what will be sent, depending on the value of callback_behaviour:

  • If callback_behaviour = "readonly", the return value from the callback is ignored, and the original message is sent.

  • If callback_behaviour = "modify", the return value from the callback is sent instead of the original message, unless the return value is undef, in which case nothing is sent

  • If callback_behaviour = "conditional", which is the default, that the return value from the callback is sent instead of the original message, or if the return value is undef, then the original message is sent.

timer_callback( [interval, callback] )

Set a callback function to be called at regular intervals

Parameters

  • interval - how often the callback function is to be called - must be > 0 seconds, may be fractional

  • callback - a reference to a function to be called every interval seconds

  • Returns - the current or new setting, as an array

Usage

If the callback is set, it will be called every interval seconds.

Interval must be > 0 seconds. It may be fractional. If interval is passed as 0 it will be reset to 1 second. This is to prevent accidental spin-wait. If you really want to spin-wait, pass an extremely small but non-zero interval.

The time spent in callbacks is not additional to the specified interval - the timer callback will be called every interval seconds, or as close as possible to every interval seconds.

Please remember that if you have called fork before calling go() that the timer_callback method will be executed in a different process to the parent - the two processes will need to use some form of IPC if they are to communicate.

Historical note: Prior to version 0.03_01, if the callback returned false, mainloop would exit and return control to the caller. (FIXME It still does.) stop_when_idle() can be used to persuade go() to exit. (FIXME check what happens if go() is called after stopping. TODO Add an unconditional stop() method)

parallel( [level] )

Turns on/off running in parallel.

Parameters

  • level - 0=serial, 1=parallel. Default is 0 (run in serial).

  • Returns - the current or new setting

Usage

If running in parallel, MitM starts a new process for each new connection using fork.

Running in serial still allows multiple clients to run concurrently, as so long as none of them have long-running callbacks. If they do, they will block other clients from sending/recieving.

serial( [level] )

Turns on/off running in serial

Parameters

  • level - 0=parallel, 1=serial. Default is 1, i.e. run in serial.

  • Returns - the current or new setting

Usage

Calling this function with level=$l is exactly equivalent to calling parallel with level=!$l.

If running in parallel, MitM starts a new process for each new connection using fork.

Running in serial, which is the default, still allows multiple clients to run concurrently, as so long as none of them have long-running callbacks. If they do, they will block other clients from sending/recieving.

log_file( [log_file_name] ] )

log_file() sets, or clears, a log file.

Parameters

  • log_file_name - the name of the log file to be appended to. Passing "" disables logging. Passing nothing, or undef, returns the current log filename without change.

  • Returns - log file name

Usage

The log file contains a record of connects and disconnects and messages as sent back and forwards. Log entries are timestamped. If the log file already exists, it is appended to.

The default timestamp is "hh:mm:ss", eg 19:49:43 - see mydate() and hhmmss().

stop_when_idle( boolean )

Wait for remaining children to exit, then exit

Parameters

  • flag - whether to exit when idle, or not. The default is true (exit when idle).

  • Returns the current status (true=exit when idle, false=keep running)

Usage

Causes MitM or Server to return from go() once its last child exits.

If go() is called after stop_when_idle() then stop_when_idle() only takes effect after at least one child has been created.

MitM or Server will exit immediately if there are currently no children or if MitM or Server is running in parrallel. Otherwise it will stop accepting new children and exit when the last child exits.

defrag_delay( [delay] )

Use a small delay to defragment messages

Parameters

  • Delay - seconds to wait - fractions of a second are OK

  • Returns - the current setting.

Usage

Under TCPIP, there is always a risk that large messages will be fragmented in transit, and that messages sent close together may be concatenated.

Client/Server programs have to know how to turn a stream of bytes into the messages they care about, either by repeatedly reading until they see an end-of-message (defragmenting), or by splitting the bytes read into multiple messages (deconcatenating).

For our purposes, fragmentation and concatenation can make our logs harder to read.

Without knowning the protocol, it's not possible to tell for sure if a message has been fragmented or concatenated.

A small delay can be used as a way of defragmenting messages, although it increases the risk that separate messages may be concatenated.

Eg: $MitM->defrag_delay( 0.1 );

protocol( [protocol] )

Set protocol to tcp (default) or udp

Parameters

  • protocol - either 'tcp' or 'udp'

  • Returns - the current setting.

Usage

Eg: $MitM->protocol( 'udp' );

SUPPORTING SUBROUTINES/METHODS

The remaining functions are supplimentary. new_server() and new_client() provide a simple client and a simple server that may be useful in some circumstances. The other methods are only likely to be useful if you choose to bypass go() in order to, for example, have more control over messages being received and sent.

new_server( local_port_num, callback_function )

Returns a very simple server, adequate for simple tasks.

Parameters

  • local_port_num - the Port number to listen on

  • callback_function - a reference to a function to be called when a message arrives - must return a response which will be returned to the client

  • Returns - a new server

Usage

  sub do_something($){
    my $in = shift;
    my $out = ...
    return $out;
  }

  my $server = Net::MitM->new_server(8080,\&do_something) || die;
  $server->go();
 

The server returned by new_server has a method, go(), which tells it to start receiving messages (arbitrary strings). Each string is passed to the callback_function, which is expected to return a single string, being the response to be returned to caller. If the callback returns undef, the original message will be echoed back to the client.

go() does not return. You may want to fork before calling it.

See, for another example, samples/echo_server.pl in the MitM distribution.

new_client( remote_host, remote_port_num )

new_client() returns a very simple client, adequate for simple tasks

The client returned has a method, send_and_receive(), which sends a message and receives a response.

Alternately, send_to_server() may be used to send a message, and receive_from_server() may be used to receive a message.

Explicitly calling connect_to_server() is optional, but may be useful if you want to be sure the server is reachable. If you don't call it explicitly, it will be called the first time a message is sent.

Parameters

  • remote_ip_address - the hostname/IP address of the server

  • remote_port_num - the Port number of the server

  • Returns - a new client object

Usage

  my $client = Net::MitM->new_client("localhost", 8080) || die("failed to start test client: $!");
  $client->connect_to_server();
  my $resp = $client->send_and_receive("hello");
  ...

See, for example, samples/client.pl or samples/clients.pl in the MitM distribution.

log( string )

log is a convenience function that prefixes output with a timestamp and PID information then writes to log_file.

Parameters

  • string(s) - one or more strings to be logged

  • Returns --none--

Usage

log is a convenience function that prefixes output with a timestamp and PID information then writes to log_file.

log() does nothing unless log_file is set, which by default, it is not.

echo( string(s) )

Prints to stdout and/or the logfile

Parameters

  • string(s) - one or more strings to be echoed (printed)

  • Returns --none--

Usage

echo() is a convenience function that prefixes output with a timestamp and PID information and prints it to standard out if verbose is set and, if log_file() has been set, logs it to the log file.

send_to_server( string(s) )

send_to_server() sends a message to the server

Parameters

  • string(s) - one or more strings to be sent

  • Return: true if successful

Usage

If a callback is set, it will be called before the message is sent.

send_to_server() may 'die' if it detects a failure to send.

send_to_client( string(s) )

Sends a message to the client

Parameters

  • string(s) - one or more strings to be sent

  • Return: true if successful

Usage

If a callback is set, it will be called before the message is sent.

receive_from_server( )

Receives a message from the server

Parameters

  • --none--

  • Returns - the message read, or undef if the server disconnected.

Usage

Blocks until a message is received.

This method used to be called read_from_server(), and may still be called via that name.

read_from_server( ) [Deprecated]

use instead: receive_from_server( )

send_and_receive( )

Sends a message to the server and receives a response

Parameters

  • the message(s) to be sent

  • Returns - message read, or undef if the server disconnected.

Usage

Blocks until a message is received. If the server does not always return exactly one message for each message it receives, send_and_receive() will either concatenate messages or block forever.

connect_to_server( )

Connects to the server

Parameters

  • --none--

  • Returns true if successful

Usage

This method is automatically called when needed. It only needs to be called directly if you want to be sure that the connection to server succeeds before proceeding.

Changed in v0.03_01: return true/false if connect successful/unsuccessful. Previously died if connect fails. Failure to resolve remote internet address/port address is still fatal.

disconnect_from_server( )

Disconnects from the server

Parameters

  • --none--

  • Returns --none--

Usage

disconnect_from_server closes any connections.

It is only intended to be called on clients.

For MitM, like for any server, disconnection is best triggered by the other party disconnecting, not by the server. If a server disconnects while it has an active client connection open and exits or otherwise stops listening, it will not be able to reopen the same port for listening until the old connection has timed out which can take up to a few minutes.

graceful_shut_down( )

Shut down the server gracefully

Parameters

  • --none--

  • Returns --none--

Usage

graceful_shut_down closes the LISTEN socket so that no more clients will be accepted. When the last client has exited, mainloop will exit.

If running in parallel mode, graceful_shut_down will take effect immediately, the children will keep running. This might change in a future release.

hhmmss( )

The default timestamp function - returns localtime in hh:mm:ss format

Parameters

  • --none--

  • Returns - current time in hh:mm:ss format

Usage

This function is, by default, called when a message is written to the log file.

It may be overridden by calling mydate().

mydate( )

Override the standard hh:mm:ss datestamp

Parameters

  • datestamp_callback - a reference to a function that returns a datestamp

  • Returns - a reference to the current or updated callback function

Usage

For example:

  sub yymmddhhmmss {
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
    return sprintf "%02d/%02d/%02d %02d:%02d:%02d", 
      $year+1900,$mon+1,$mday,$hour,$min,$sec;
  }
  mydate(\&yymmddhhmmss);

listen( )

Listen on local_port and prepare to accept incoming connections

Parameters

  • --none--

  • Return --none--

Usage

This method is called by go(). It only needs to be called directly if go() is being bypassed for some reason.

Exports

MitM does not export any functions or variables. If parallel() is turned on, which by default it is not, MitM sets SIGCHD to IGNORE, and as advertised, it calls fork() once for each new connection.

AUTHOR

Ben AVELING, <ben dot aveling at optusnet dot com dot au>

BUGS

Please report any bugs or feature requests to bug-Net-MitM at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-MitM. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Net::MitM

You can also look for information at:

ACKNOWLEDGEMENTS

I'd like to acknowledge W. Richard Steven's and his fantastic introduction to TCPIP: "TCP/IP Illustrated, Volume 1: The Protocols", Addison-Wesley, 1994. (http://www.kohala.com/start/tcpipiv1.html). It got me started. Recommend. RIP. The Blue Camel Book is also pretty useful, and Langworth & chromatic's "Perl Testing, A Developer's Notebook" is also worth a hat tip.

ALTERNATIVES

If what you want is a pure proxy, especially if you want an ssh proxy or support for firewalls, you might want to evaluate Philippe "BooK" Bruhat's Net::Proxy.

And if you want a full "portable multitasking and networking framework for any event loop", you may be looking for POE.

LICENSE AND COPYRIGHT

Copyleft 2013 Ben AVELING.

This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

http://www.perlfoundation.org/artistic_license_2_0

Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, have, hold and cherish, use, offer to use, sell, offer to sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SO THERE.