File Coverage

blib/lib/App/Waf.pm
Criterion Covered Total %
statement 11 105 10.4
branch 0 46 0.0
condition 0 6 0.0
subroutine 4 10 40.0
pod 2 6 33.3
total 17 173 9.8


line stmt bran cond sub pod time code
1             package App::Waf;
2              
3 1     1   58250 use 5.006;
  1         3  
4 1     1   4 use strict;
  1         2  
  1         17  
5 1     1   5 use warnings;
  1         1  
  1         80  
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.08
22              
23             =cut
24              
25             our $VERSION = '0.08';
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   506 use File::ReadBackwards;
  1         2215  
  1         1085  
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              
258 0 0         unless ($bid) {
259 0           print "$btime,banip $ip\n";
260 0           print $nFD "deny $ip\;\n";
261 0           $pid = `cat $pid`;
262 0           chomp $pid;
263 0           `/usr/bin/kill -HUP $pid`;
264             }
265              
266              
267             }
268              
269             =head1 AUTHOR
270              
271             ORANGE, C<< >>
272              
273             =head1 BUGS
274              
275             Please report any bugs or feature requests to C, or through
276             the web interface at L. I will be notified, and then you'll
277             automatically be notified of progress on your bug as I make changes.
278              
279              
280             =head1 SUPPORT
281              
282             You can find documentation for this module with the perldoc command.
283              
284             perldoc App::Waf
285              
286              
287             You can also look for information at:
288              
289             =over 4
290              
291             =item * RT: CPAN's request tracker (report bugs here)
292              
293             L
294              
295             =item * AnnoCPAN: Annotated CPAN documentation
296              
297             L
298              
299             =item * CPAN Ratings
300              
301             L
302              
303             =item * Search CPAN
304              
305             L
306              
307             =back
308              
309              
310             =head1 ACKNOWLEDGEMENTS
311              
312              
313             =head1 LICENSE AND COPYRIGHT
314              
315             Copyright 2016 ORANGE.
316              
317             This is free software; you can redistribute it and/or modify
318             it under the same terms as the Perl 5 programming language system itself.
319              
320             =cut
321              
322             1; # End of App::Waf