#!/usr/bin/perl # Copyright (C) 2008 Hinnerk Altenburg # # This file is part of PerlIDS. # # PerlIDS is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # PerlIDS is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PerlIDS. If not, see . =head1 NAME PerlIDS (CGI::IDS) - example application =head1 DESCRIPTION This example application provides a textarea that will be parsed by the IDS and the scan result will be shown if any attack has been detected. Two log files are created by the application: =over 4 =item 1 I Logging all filtered key/value pairs to finetune the whitelist. =item 2 I Logging all attacks with detailed information. =back A sample whitelist file is provided in I. Please have a look into L for details. =head1 AUTHOR Hinnerk Altenburg, C<< >> =head1 SEE ALSO L =head1 COPYRIGHT & LICENSE Copyright (C) 2008 Hinnerk Altenburg This file is part of PerlIDS. PerlIDS is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PerlIDS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PerlIDS. If not, see . =cut #------------------------- Pragmata -------------------------------------------- use strict; use warnings; #------------------------- Libs ------------------------------------------------ use CGI; use CGI::IDS; use CGI::Pretty; use Data::Dumper; # use CGI::Carp qw(fatalsToBrowser); #------------------------- Globals --------------------------------------------- my $ids; my $impact = 0; #------------------------- Main ------------------------------------------------ my $query = new CGI; # Start HTML output print $query->header, $query->start_html( -title=>'PerlIDS (CGI::IDS) demo application / smoke test', -style=>{'src'=>'style.css'}, ), $query->h1('PerlIDS (CGI::IDS) demo application / smoke test'); eval { # Check request for possible attacks (for check_request() see sub below) $impact = check_request( request => scalar $query->Vars, # filter_file => './ids/default_filter.xml', whitelist_file => './ids/param_whitelist.xml', filter_log => './ids/filtered_keys.log', attack_log => './ids/attacks.log' ); # Generate input form print $query->h2('Put your vectors here:'); print $query->start_form( -method=>'post', -action=>'demo.pl'); print $query->textarea( -name => 'vector', -rows => 10, -columns => 50, -default => $query->param('vector') ); print $query->submit( -name => 'submit', -value => 'Test your vector'); print $query->endform; # Generate HTML scan result output showing details of the detected attack if ($impact > 0) { print $query->h2('Scan result:'); if ($query->param()) { my $attacks = $ids->get_attacks(); foreach my $attack (@$attacks) { print $query->start_table({-class => 'negative'}); print '' . join ( "\n", ( 'IMPACT: '. $attack->{impact}, 'TIME: '. $attack->{time_ms} . 'ms', 'FILTERS MATCHED: '. join("
", map {"#$_: " . $ids->get_rule_description(rule_id => $_)} @{$attack->{matched_filters}}), 'TAGS MATCHED: '. join(",", @{$attack->{matched_tags}}), # 'KEY: '. $query->escapeHTML($query->escapeHTML($attack->{key})), # 'KEY CONV: '. $query->escapeHTML($query->escapeHTML($attack->{key_converted})), 'VALUE: '. $query->pre($query->escapeHTML($attack->{value})), 'VALUE CONVERTED: '. $query->pre($query->escapeHTML($attack->{value_converted})), ) ) . "\n"; print 'REQUEST: '.$query->pre($query->escapeHTML(Dumper({$query->Vars}))).''; print $query->end_table(); } } } elsif ($query->param()){ print $query->p({-class => 'positive'}, 'No attack found!'); print $query->p('PerlIDS did not detect your vector? Please help us to improve the filter set an send your vector to ', $query->em('hinnerk at cpan.org'), '!'); } }; if ($@) { print $query->h2({-class => 'negative'}, 'An Error occurred:'); print $query->p({-class => 'negative'}, $@); } # end the HTML print $query->end_html; #------------------------- Subs ------------------------------------------------ #****f* IDS/check_request # NAME # check_request # DESCRIPTION # This sub runs IDS->detect_attacks() on the request. # If an impact is detected, a record is appended to the log file. # INPUT # HASH # + request # filter_file # whitelist_file # attack_log # filter_log # OUTPUT # INT impact or UNDEF if no request or filter_file present # EXAMPLE # check_request( # request => $query->Vars, # filter_file => '../res/default_filter.xml', # whitelist_file => '../res/test_param_whitelist.xml', # attack_log => '../res/attacks.log', # filter_log => '../res/filtered_keys.log', # ); #**** sub check_request { my %args = @_; return undef unless ($args{request}); my $request = $args{request}; # create global ids object on first call if (!defined($ids)) { $ids = new CGI::IDS( filters_file => $args{filter_file}, whitelist_file => $args{whitelist_file}, scan_keys => 0, disable_filters => [58,59,60], ); } # check request my $impact = $ids->detect_attacks( request => $request ); # log filtered keys for whitelist tuning # (add rules for keys to the whilelist if the filters are applied to the keys' standard values) if ($args{filter_log} && @{$ids->{filtered_keys}}) { # open logfile for appending umask(02); if (open(LOG, '>>' . $args{filter_log})) { flock(LOG, 2); print LOG "--------------------------------\n"; print LOG "Filtered:\n"; # log all filtered keys foreach my $key (@{$ids->{filtered_keys}}) { print LOG "\t" . $key->{reason} . "\t" . $key->{key} . " -> " . $key->{value} . "\n"; } # log all non-filtered keys as environment if ($ids->{non_filtered_keys}) { print LOG "Environment:\n"; foreach my $key (@{$ids->{non_filtered_keys}}) { print LOG "\t" . $key->{reason} . "\t" . $key->{key} . " -> " . $key->{value} . "\n"; } } close(LOG); } else { warn "Can't open ".$args{filter_log}.": $!"; } } # log attack details if an impact was detected if ($impact && $args{attack_log}) { # open logfile for appending umask(02); if (open(LOG, ">>".$args{attack_log})) { my $attacks = $ids->get_attacks(); flock(LOG, 2); print LOG "--------------------------------\n"; print LOG scalar localtime() . "\n"; print LOG "Attack details:\n\t"; foreach my $attack (@$attacks) { print LOG join ( "\n\t", ( 'TIME: '. $attack->{time_ms} . 'ms', 'KEY: '. $attack->{key}, 'KEY CONV: '. $attack->{key_converted}, 'VALUE: '. $attack->{value}, 'VALUE CONV: '. $attack->{value_converted}, 'IMPACT: '. $attack->{impact}, 'FILTERS MATCHED: '. join(",", @{$attack->{matched_filters}}), 'TAGS MATCHED: '. join(",", @{$attack->{matched_tags}}), 'REQUEST: '. Dumper({$query->Vars}), ) ); } close(LOG); } else { warn "Can't open ".$args{attack_log}.": $!"; } } return $impact; }