HTTP Inspektion und Manipulation mit Net::IMP

HTTP Inspektion
und Manipulation
mit Net::IMP
Steffen Ullrich, genua mbH
Deutscher Perl-Workshop 2013, Berlin

who am I

Übersicht Talk

Padiofire

Padiofire

Was ist Padiofire

Web 2.0 Angriffe auf Client

ausgewählte Attacken im Bild

existente Lösungsansätze

neue Ideen gebraucht

IMP

IMP

was ist IMP

Net::IMP::HTTP

Connection und Request im Bild

Implementationen

existierende Implementationen

Datenquelle

Analysen

Beispiel: FlipImg

Beispiel: FlipImg

FlipImg

Bild FlipImg

Includes etc

package Net::IMP::HTTP::Example::FlipImg;
use base 'Net::IMP::HTTP::Request';
use fields (
    'ignore',  # irrelevant content
    'image',   # collected octets for image
);

use Net::IMP;  # import IMP_ constants
use Net::IMP::Debug;
use Image::Magick;
use constant MAX_SIZE => 2**16;

Analyzer erstellen

sub RTYPES { (IMP_PASS,IMP_REPLACE) }
sub new_analyzer {
    my ($factory,%args) = @_;
    my $self = $factory->SUPER::new_analyzer(%args);
    # request data do not matter
    $self->run_callback([ IMP_PASS,0,IMP_MAXOFFSET ]);
    return $self;
}

ungebrauchte Dummies

sub request_hdr {}
sub request_body {}
sub any_data {}

Response Header 1/3

sub response_hdr {
    my ($self,$hdr) = @_;
    my $ignore;
    # we only want selected image/ content types and not too big
    my ($ct) = $hdr =~m{\nContent-type:[ \t]*([^\s;]+)}i;
    my ($clen) = $hdr =~m{\nContent-length:[ \t]*(\d+)}i;
    my ($code) = $hdr =~m{\AHTTP/1\.[01][ \t]+(\d+)};
    ...

Response Header 2/3

    ...
    if ( $code != 200 ) {
        debug("will not rotate code=$code");
        $ignore++;
    } elsif ( ! $ct or $ct !~m{^image/(png|gif|jpeg)$} ) {
        debug("will not rotate content type $ct");
        $ignore++;
    } elsif ( $clen and $clen > 2**16 ) {
        debug("image is too big: $clen" );
        $ignore++;
    }
    ...

Response Header 3/3

    ...
    if ( $ignore ) {
        $self->run_callback([ IMP_PASS,1,IMP_MAXOFFSET ]);
        $self->{ignore} = 1;
        return;
    }

    # pass header
    $self->run_callback([ IMP_PASS,1,$self->offset(1) ]);
}

Response Body 1

sub response_body {
    my ($self,$data) = @_;
    $self->{ignore} and return;
    my $off = $self->offset(1);

    if ( $data ne '' ) {
        ....

Response Body 2 - $data ne ''

...
$self->{image} .= $data;  # append to image
# replace with '' in caller to save memory there
# at the end we will replace eof with the flipped image
$self->run_callback([ IMP_REPLACE,1,$off,'' ]);

# with chunked encoding we don't get a length up front, so check now
if ( length($self->{image}) > MAX_SIZE ) {
    debug("image too big");
    $self->run_callback(
        [ IMP_REPLACE,1,$off,$self->{image} ], # unchanged image
        [ IMP_PASS,1,IMP_MAXOFFSET ]
    );
    $self->{ignore} = 1;
}

return;
...

Response Body 3

        ...
    }

    # end of image reached, flip with Image::Magick
    debug("flip image size=%d",length($self->{image}));
    my $img = Image::Magick->new;
    debug("failed to flip img: $@") if ! eval {
        $img->BlobToImage($self->{image});
        $img->Flip;
        ($self->{image}) = $img->ImageToBlob;
        debug("replace with ".length($self->{image})." bytes");
        1;
    };

    $self->run_callback(
        [ IMP_REPLACE,1,$self->offset(1),$self->{image} ],
        [ IMP_PASS,1,IMP_MAXOFFSET ],
    );
}

FlipImg done

http_proxy_imp --filter Example::FlipImg 127.0.0.1:8888

Beispiel: Deny per Content-Type

Beispiel: Deny per Content-Type

Ziel

Includes etc

package Net::IMP::HTTP::Example::BlockContentType;
use base 'Net::IMP::HTTP::Request';
use Net::IMP;  # import IMP_ constants
use Net::IMP::Debug;

Config validieren

sub validate_cfg {
    my ($class,%cfg) = @_;
    my @err;
    for my $k (qw(whiterx blackrx)) {
        my $rx = delete $cfg{$k} or next;
        ref($rx) and next;
        push @err,"$k is no valid regexp: $@" if ! eval { qr/$rx/ };
    }
    return (@err,$class->SUPER::validate_cfg(%cfg));
}

Config aus String

sub str2cfg {
    my ($class,$str) = @_;
    my %cfg = $class->SUPER::str2cfg($str);
    for my $k (qw(whiterx blackrx)) {
        next if ! $cfg{$k} or ref $cfg{$k};
        $cfg{$k} = eval { qr/$cfg{$k}/ }
            or die "invalid rx in $k: $@";
    }
    return %cfg;
}

http_proxy_imp \
  --filter 'Example::BlockContentType=whiterx=^text/&blackrx=...' \
  127.0.0.1:1235

neuer Analyzer

sub RTYPES { ( IMP_PASS, IMP_DENY ) }
sub new_analyzer {
    my ($factory,%args) = @_;
    my $self = $factory->SUPER::new_analyzer(%args);
    # request data do not matter
    $self->run_callback([ IMP_PASS,0,IMP_MAXOFFSET ]);
    if ( ! $self->{factory_args}{whiterx}
        && ! $self->{factory_args}{blackrx} ) {
        # nothing to analyze
        $self->run_callback([ IMP_PASS,1,IMP_MAXOFFSET ]);
    }
    return $self;
}

Dummies

sub request_hdr {}
sub request_body {}
sub response_body {}
sub any_data {}

Response Header 1/4 - get content-type

sub response_hdr {
    my ($self,$hdr) = @_;
    # we only want selected image/ content types and not too big
    my $ct = $hdr =~m{\nContent-type:[ \t]*([^\s;]+)}i && lc($1)
        || 'unknown/unknown';

    my $reason;
    ...

Response Header 2/4 - check white list

    ...
    if ( my $white = $self->{factory_args}{whiterx} ) {
        if ( $ct =~ $white ) {
            debug("allowed $ct because of white list");
            goto pass;
        } else {
            debug("denied $ct because not in white list");
            $reason = "denied $ct because not in white list";
            goto deny;
        }
    }
    ...

Response Header 3/4 - check black list

    ...
    if ( my $black = $self->{factory_args}{blackrx} ) {
        if ( $ct =~ $black ) {
            debug("denied $ct because in black list");
            $reason = "denied $ct because in black list";
            goto deny;
        } else {
            debug("allow $ct because not in black list");
            goto pass;
        }
    }
    ...

Response Header 4/4 - do pass/deny

    ...
    pass:
    $self->run_callback([ IMP_PASS,1,IMP_MAXOFFSET ]);
    return;

    deny:
    $self->run_callback([ IMP_DENY,1,$reason ]);
    return;
}

mögliche Verbesserungen

weitere Ideen:

Fragen

Fragen ?