File Coverage

blib/lib/App/Waf.pm
Criterion Covered Total %
statement 11 107 10.2
branch 0 48 0.0
condition 0 6 0.0
subroutine 4 10 40.0
pod 2 6 33.3
total 17 177 9.6


line stmt bran cond sub pod time code
1             package App::Waf;
2              
3 1     1   16481 use 5.006;
  1         5  
4 1     1   8 use strict;
  1         3  
  1         29  
5 1     1   7 use warnings;
  1         7  
  1         118  
6             require Exporter;
7              
8             =encoding utf8
9             =head1 NAME
10              
11             App::Waf - A sample Web Application Firewall,
12             analysis the web logs for illegal attempt in real time。
13             summary the source IP and other tpyes infomations ,using
14             this infomations for ban whith iptables.
15              
16             通过解析web访问日志,实时统计非法访问,结合防火期等进行
17             主动式防御。
18              
19             =head1 VERSION
20              
21             Version 0.07
22              
23             =cut
24              
25             our $VERSION = '0.07';
26              
27             our @ISA = qw(Exporter);
28             our @EXPORT = qw(tail initCount iptabBan nginxBan);
29              
30             =head1 SYNOPSIS
31             =head2 实例
32              
33             use App::Waf;
34             my $filename = "example.acess";#日志文件
35             my $numlines = 50000; #要处理的行数,从后读。
36             my $line=tail($filename,$$numlines);
37             ($log,$zcount,$zip,$zrequrl,$zstatus,$siteurl)=initCount($line);
38             print "==============Attack Summary ==================\n";
39             print "\nThe total attack count: $zcount \n";
40             print "\nThe count from source IP: \n\n";
41             print "$_\=> $zip->{$_} \n" for(sort keys %{$zip});
42             print "The count From request Url: \n\n";
43             print "$_\=> $zrequrl->{$_} \n" for(sort keys %{$zrequrl});
44             print "\n\nThe count From Http Status: \n\n";
45             print "$_\=> $zstatus->{$_} \n" for(sort keys %{$zstatus});
46             print "\n\nThe count From Site Url: \n\n";
47             print "$_\=> $siteurl->{$_} \n" for(sort keys %{$siteurl});
48            
49             =head2 结合nginx 和 iptables 进行实时banip的实例(example/banip.pl)
50              
51             加入crontab 每5分钟执行一次。
52              
53             echo "*/5 * * * * perl $dir/banip.pl >> bianip.logs 2>&1 " >> /var/spool/cron/root
54              
55             =head1 SUBROUTINES/METHODS
56              
57             =head2 tail()
58              
59             IN: $logfile,$count;
60              
61             OUT: return the the latest $count lines of the $logfile.
62              
63             =head2 initCount()
64              
65             IN: the content of need to cheack and count.
66              
67             OUT: all types count result.
68              
69             =cut
70              
71 1     1   599 use File::ReadBackwards;
  1         2102  
  1         1017  
72              
73             my $DEBUG = 0;
74              
75             my @validurl = (
76             'rfd.php\?include_file',
77             '\.\./',
78             'select.+(from|limit)',
79             '(?:(union(.*?)select))',
80             'having|rongjitest',
81             'sleep\((\s*)(\d*)(\s*)\)',
82             'benchmark\((.*)\,(.*)\)',
83             'base64_decode\(',
84             '(?:from\W+information_schema\W)',
85             '(?:(?:current_)user|database|schema|connection_id)\s*\(',
86             '(?:etc\/\W*passwd)',
87             'into(\s+)+(?:dump|out)file\s*',
88             'group\s+by.+\(',
89             'xwork.MethodAccessor',
90             '(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|concat|alert|showmodaldialog)\(',
91             'xwork\.MethodAccessor',
92             '(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/',
93             'java\.lang',
94             '\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[',
95             '\<(iframe|script|body|img|layer|div|meta|style|base|object|input)',
96             '(onmouseover|onerror|onload)\=',
97             '\.(bak|inc|old|mdb|sql|backup|java|class)$',
98             '\.(svn|htaccess|bash_history)',
99             '(vhost|bbs|host|wwwroot|www|site|root|hytop|flashfxp).*\.rar',
100             '(phpmyadmin|jmx-console|jmxinvokerservlet)',
101             '/xmlrpc.php',
102             '/(attachments|upimg|images|css|uploadfiles|html|uploads|templets|static|template|data|inc|forumdata|upload|includes|cache|avatar)/(\w+).(php|jsp|asp)',
103              
104             );
105              
106             sub tail {
107              
108 0     0 1   my ( $filename, $linenum ) = @_;
109 0 0         print "DEBUG :: tail() :: IN : $filename,$linenum \n" if $DEBUG;
110 0 0         my $bw = File::ReadBackwards->new($filename)
111             or die "can't read $filename $!";
112 0 0         $linenum=1000 unless $linenum;
113 0           my $count = 0;
114 0           my @lines;
115              
116 0           while ( defined( my $line = $bw->readline ) ) {
117 0           push @lines, $line;
118 0           $count++;
119 0 0         if ( $count == $linenum ) { last }
  0            
120             }
121              
122 0           @lines = reverse @lines;
123 0           return \@lines;
124             }
125              
126             sub initCount {
127              
128 0     0 1   my $line = shift;
129 0           my @re = @validurl;
130 0           my $kcount = shift;
131 0           my ( $zcount, $zip, $zrequrl, $zstatus, $siteurl );
132 0           my $rawlog;
133              
134 0           for (@re) {
135 0           my $result = scarlog1( $_, $line );
136 0           my ( $mycount, $mylog ) = count($result);
137 0           my $key = $_;
138 0 0         $rawlog .= $mylog->{$key} if $mylog->{$key};
139              
140 0 0         $zcount += $mycount->{$key}->[0] if $mycount->{$key}->[0];
141 0 0         print
142             "DEBUG\:: initCount()\::OUT $key $mycount->{$key}->[0] $zcount \n"
143             if $DEBUG;
144             $zip->{$_} += $mycount->{$key}->[1]->{$_}
145 0           for ( keys %{ $mycount->{$key}->[1] } );
  0            
146             $zrequrl->{$_} += $mycount->{$key}->[2]->{$_}
147 0           for ( keys %{ $mycount->{$key}->[2] } );
  0            
148              
149 0 0         if ($DEBUG) {
150             print
151             "DEBUG\:: initCount()\::OUT $key $zrequrl->{$_} $_\=> $mycount->{$key}->[2]->{$_} \n"
152 0           for ( keys %{ $mycount->{$key}->[2] } );
  0            
153             }
154             $zstatus->{$_} += $mycount->{$key}->[3]->{$_}
155 0           for ( keys %{ $mycount->{$key}->[3] } );
  0            
156             $siteurl->{$_} += $mycount->{$key}->[4]->{$_}
157 0           for ( keys %{ $mycount->{$key}->[4] } );
  0            
158              
159             }
160 0 0         if ($DEBUG) {
161             print "DEBUG\:: initCount()\::OUT\::\$zrequrl $_\=>$zrequrl->{$_}\n"
162 0           for ( keys %{$zrequrl} );
  0            
163             }
164 0           return ( $rawlog, $zcount, $zip, $zrequrl, $zstatus, $siteurl );
165             }
166              
167             sub count {
168              
169 0     0 0   my $result = shift;
170              
171 0           my ( $mcount, %rawlog );
172 0           my $count = 0;
173 0           for ( keys %{$result} ) {
  0            
174 0           my ( %ip, %requrl, %status, %siteurl );
175              
176 0 0         next if $result->{$_} eq "";
177 0           $rawlog{$_} .= $result->{$_};
178 0           my @seclogs = split /\n/ms, $result->{$_};
179 0           for (@seclogs) {
180 0           $count++;
181 0 0         print "DEBUG\:: count()\::IN $_\n" if $DEBUG;
182 0           my ( $ip, $requrl, $status, $siteurl ) = (split)[ 0, 6, 8, 10 ];
183 0 0         $ip{$ip}++ if $ip;
184 0 0         $requrl{$requrl}++ if $requrl;
185 0 0         $status{$status}++ if $status;
186 0 0         $siteurl{$siteurl}++ if $siteurl;
187 0 0         print
188             "DEBUG\:: count()\::OUT $ip\=>$ip{$ip} $requrl\=>$requrl{$requrl} $status\=>$status{$status} $siteurl\=>$siteurl{$siteurl} \n"
189             if $DEBUG;
190             }
191              
192 0           $mcount->{$_} = [ $count, \%ip, \%requrl, \%status, \%siteurl ];
193              
194             }
195              
196 0           return $mcount, \%rawlog;
197             }
198              
199             sub scarlog1 {
200              
201 0     0 0   my ( $patter, $lines ) = @_;
202              
203 0           my %result;
204              
205 0           my $code = 'for(@{$lines}) {';
206 0           $code .= 'if (m#';
207 0           $code .= qr($patter);
208 0           $code .= '#) {$result{' . q($patter) . '}.=$_}}';
209 0           eval $code;
210 0 0         die "Error ---: $@\n Code:\n$code\n" if ($@);
211              
212             #print "DEBUG scarlog1 :: OUT :: $_: $result{$_}\n" for(keys %result);
213 0           return \%result;
214             }
215              
216             sub iptabBan {
217              
218             # must be root user;
219             # 必须root用户才可以操作iptables,当然也必须有iptables服务跑动着
220              
221 0     0 0   my $IP = shift;
222              
223 0           my $ips = `/sbin/iptables-save`;
224 0           my @ipsline = split /\n/sm, $ips;
225 0           my $dist = 0;
226 0           for (@ipsline) {
227              
228 0 0 0       $dist = 1 if ( /$IP/ and /INPUT/ and /DROP/ );
      0        
229              
230             }
231 0 0         unless ($dist) {
232 0           `/sbin/iptables -I INPUT -s $IP -j DROP`;
233 0           my $btime = localtime( time() );
234 0           print "$btime :band $IP \n";
235             }
236             else {
237              
238 0           print "band alread!\n";
239              
240             }
241              
242             }
243              
244              
245             sub nginxBan {
246              
247 0     0 0   my $btime = localtime( time() );
248 0           my ( $ip, $conf, $pid ) = @_;
249 0           my $bid = 0;
250 0 0         open my $nFD, "<", $conf or die("Can not open the file!$!\n");
251 0           while (<$nFD>) {
252 0 0         print "DEBUG ::nginxBan :: $conf IN $_" if $DEBUG;
253 0 0         $bid = 1 if /$ip/;
254             }
255 0           close $nFD;
256              
257 0 0         open my $nFD, ">>", $conf or die("Can not open 1 the file!$!\n");
258              
259 0 0         unless ($bid) {
260 0           print "$btime,banip $ip\n";
261 0           print $nFD "deny $ip\;\n";
262 0           $pid = `cat $pid`;
263 0           chomp $pid;
264 0           `/usr/bin/kill -HUP $pid`;
265             }
266              
267 0           close $nFD;
268              
269             }
270              
271             =head1 AUTHOR
272              
273             ORANGE, C<< >>
274              
275             =head1 BUGS
276              
277             Please report any bugs or feature requests to C, or through
278             the web interface at L. I will be notified, and then you'll
279             automatically be notified of progress on your bug as I make changes.
280              
281              
282             =head1 SUPPORT
283              
284             You can find documentation for this module with the perldoc command.
285              
286             perldoc App::Waf
287              
288              
289             You can also look for information at:
290              
291             =over 4
292              
293             =item * RT: CPAN's request tracker (report bugs here)
294              
295             L
296              
297             =item * AnnoCPAN: Annotated CPAN documentation
298              
299             L
300              
301             =item * CPAN Ratings
302              
303             L
304              
305             =item * Search CPAN
306              
307             L
308              
309             =back
310              
311              
312             =head1 ACKNOWLEDGEMENTS
313              
314              
315             =head1 LICENSE AND COPYRIGHT
316              
317             Copyright 2016 ORANGE.
318              
319             This is free software; you can redistribute it and/or modify
320             it under the same terms as the Perl 5 programming language system itself.
321              
322             =cut
323              
324             1; # End of App::Waf