File Coverage

blib/lib/Plack/Middleware/Security/Common.pm
Criterion Covered Total %
statement 44 44 100.0
branch n/a
condition n/a
subroutine 26 26 100.0
pod 20 20 100.0
total 90 90 100.0


line stmt bran cond sub pod time code
1             package Plack::Middleware::Security::Common;
2              
3             # ABSTRACT: A simple security filter for Plack with common rules.
4              
5 1     1   190744 use v5.14;
  1         10  
6              
7 1     1   7 use warnings;
  1         3  
  1         34  
8              
9 1     1   6 use parent qw( Plack::Middleware::Security::Simple Exporter::Tiny );
  1         2  
  1         8  
10              
11 1     1   4310 use Regexp::Common qw/ net /;
  1         2678  
  1         5  
12              
13             our @EXPORT = qw(
14             archive_extensions
15             backup_files
16             cgi_bin
17             cms_prefixes
18             document_extensions
19             dot_files
20             exchange_prefixes
21             fake_extensions
22             header_injection
23             ip_address_referer
24             misc_extensions
25             non_printable_chars
26             null_or_escape
27             protocol_in_path_or_referer
28             require_content
29             script_extensions
30             system_dirs
31             unexpected_content
32             webdav_methods
33             wordpress
34             );
35              
36             our $VERSION = 'v0.11.0';
37              
38              
39              
40             sub archive_extensions {
41 1     1 1 212 my $re = qr{\.(?:bz2|iso|rar|tar|u?zip|[7glx]?z|tgz)\b};
42             return (
43 1         6 PATH_INFO => $re,
44             QUERY_STRING => $re,
45             );
46             }
47              
48              
49             sub backup_files {
50             return (
51 1     1 1 4 misc_extensions(),
52             PATH_INFO => qr{(?:backup|database|db|dump|localhost)\.},
53             );
54             }
55              
56              
57             sub cgi_bin {
58 1     1 1 4 my $re = qr{/cgi[_\-](?:bin|wrapper)};
59             return (
60 1         4 PATH_INFO => $re,
61             QUERY_STRING => $re,
62             );
63             }
64              
65              
66             sub cms_prefixes {
67 1     1 1 4 my $re = qr{/(?:docroot|drupal|ftproot|include|inetpub|joomla|laravel|lib|magento|plugin|plus|vendor|webroot|wp|wordpress|yii|zend)};
68             return (
69 1         4 PATH_INFO => $re,
70             );
71             }
72              
73              
74             sub document_extensions {
75 1     1 1 3 my $re = qr{\.(?:a[bz]w|csv|docx?|e?pub|od[pst]|pdf|pptx?|one|rtf|vsd|xlsx?)\b};
76             return (
77 1         5 PATH_INFO => $re,
78             QUERY_STRING => $re,
79             );
80             }
81              
82              
83              
84             sub dot_files {
85             return (
86 1     1 1 7 PATH_INFO => qr{(?:\.\./|/\.(?!well-known/))},
87             QUERY_STRING => qr{\.\./},
88             );
89             }
90              
91              
92             sub exchange_prefixes {
93             return (
94 1     1 1 5 PATH_INFO => qr{^/+(?:ecp|owa|autodiscover)/}i,
95             )
96             }
97              
98              
99             sub fake_extensions {
100 1     1 1 3 my $re = qr{;[.](?:\w+)\b};
101             return (
102 1         3 PATH_INFO => $re,
103             )
104             }
105              
106              
107             sub header_injection {
108 1     1 1 4 my $re = qr{(?:\%20HTTP/[0-9]|%0d%0a)}i;
109             return (
110 1         4 PATH_INFO => $re,
111             );
112             }
113              
114              
115              
116             sub ip_address_referer {
117             return (
118 1     1 1 9 HTTP_REFERER => qr{^https?://$RE{net}{IPv4}/},
119             HTTP_REFERER => qr{^https?://$RE{net}{IPv6}/},
120             );
121             }
122              
123              
124             sub misc_extensions {
125 1     1 1 5 my $re = qr{[.](?:backup|bak|bck|bkp|cfg|conf(?:ig)?|dat|ibz|in[ci]|npb|old|ps[bc]|rdg|to?ml|yml)\b};
126             return (
127 1         7 PATH_INFO => $re,
128             QUERY_STRING => $re,
129             )
130             }
131              
132              
133             sub non_printable_chars {
134 1     1 1 1152 return ( PATH_INFO => qr/[^[:print:]]/ )
135             }
136              
137              
138             sub null_or_escape {
139 1     1 1 6 my $re = qr{\%(?:00|1b|1B)};
140             return (
141 1         4 REQUEST_URI => $re,
142             )
143             }
144              
145              
146             sub protocol_in_path_or_referer {
147 1     1 1 5 my $re = qr{\b(?:file|dns|jndi|unix|ldap|php):};
148             return (
149 1         6 PATH_INFO => $re,
150             QUERY_STRING => $re,
151             HTTP_REFERER => $re,
152             );
153             }
154              
155              
156             sub require_content {
157             return (
158             -and => [
159             REQUEST_METHOD => qr{^(?:POST|PUT)$},
160 3     3   563 CONTENT_LENGTH => sub { !$_[0] },
161 1     1 1 11 ],
162             );
163             }
164              
165              
166             sub script_extensions {
167 1     1 1 6 my $re = qr{[.](?:as[hp]x?|axd|bat|cc?|cfm|cgi|com|csc|dll|do|exe|jspa?|lua|mvc?|php5?|p[lm]|ps[dm]?[1h]|sht?|shtml|sql)\b};
168             return (
169 1         3 PATH_INFO => $re,
170             QUERY_STRING => $re,
171             )
172             }
173              
174              
175             sub system_dirs {
176 1     1 1 3 my $re = qr{/(?:s?adm|bin|etc|usr|var|srv|opt|__MACOSX|META-INF)/};
177             return (
178 1         4 PATH_INFO => $re,
179             QUERY_STRING => $re,
180             );
181             }
182              
183              
184             sub unexpected_content {
185             return (
186             -and => [
187             REQUEST_METHOD => qr{^(?:GET|HEAD|CONNECT|OPTIONS|TRACE)$},
188 6     6   1578 CONTENT_LENGTH => sub { !!$_[0] },
189 1     1 1 16 ],
190             );
191             }
192              
193              
194             sub webdav_methods {
195 1     1 1 6 return ( REQUEST_METHOD =>
196             qr{^(COPY|LOCK|MKCOL|MOVE|PROPFIND|PROPPATCH|UNLOCK)$} );
197             }
198              
199              
200             sub wordpress {
201 1     1 1 11 return ( PATH_INFO => qr{\b(?:wp(-\w+)?|wordpress)\b} );
202             }
203              
204              
205             1;
206              
207             __END__
208              
209             =pod
210              
211             =encoding UTF-8
212              
213             =head1 NAME
214              
215             Plack::Middleware::Security::Common - A simple security filter for Plack with common rules.
216              
217             =head1 VERSION
218              
219             version v0.11.0
220              
221             =head1 SYNOPSIS
222              
223             use Plack::Builder;
224              
225             # import rules
226             use Plack::Middleware::Security::Common;
227              
228             builder {
229              
230             enable "Security::Common",
231             rules => [
232             archive_extensions, # block .tar, .zip etc
233             cgi_bin, # block /cgi-bin
234             script_extensions, # block .php, .asp etc
235             unexpected_content, # block GET with body params
236             ...
237             ];
238              
239             ...
240              
241             };
242              
243             =head1 DESCRIPTION
244              
245             This is an extension of L<Plack::Middleware::Security::Simple> that
246             provides common filtering rules.
247              
248             Most of these rules don't directly improve the security of your web
249             application: they simply block common exploit scanners from getting
250             past the PSGI layer.
251              
252             Note that they cannot block any exploits of proxies that are in front
253             of your PSGI application.
254              
255             See L</EXPORTS> for a list of rules.
256              
257             You can create exceptions to the rules by adding qualifiers, for
258             example, you want to block requests for archives, except in a
259             F</downloads> folder, you could use something like
260              
261             builder {
262              
263             enable "Security::Common",
264             rules => [
265             -and => [
266             -notany => [ PATH_INFO => qr{^/downloads/} ],
267             -any => [ archive_extensions ],
268             ],
269             ...
270             ];
271              
272             ...
273              
274             };
275              
276             Note that the rules return an array of matches, so when qualifying
277             them you will need to put them in an array reference.
278              
279             =head1 EXPORTS
280              
281             =head2 archive_extensions
282              
283             This blocks requests with common archive file extensions in the path
284             or query string.
285              
286             =head2 backup_files
287              
288             This includes L</misc_extensions> plus filename suffixes associated
289             with backup files, e.g. F<example.com-database.zip>.
290              
291             Added in v0.8.0.
292              
293             =head2 cgi_bin
294              
295             This blocks requests that refer to the C<cgi-bin> directory in the path
296             or query string, or a C<cgi_wrapper> script.
297              
298             =head2 cms_prefixes
299              
300             This blocks requests that refer to directories with common CMS
301             applications, libraries, or web servers.
302              
303             Added in v0.8.0.
304              
305             =head2 document_extensions
306              
307             This blocks requests for file extensions associated with common document formats, e.g. Office documents or spreadsheets.
308              
309             This does not include audio, video or image files.
310              
311             If you provide downloads for specific files, then you may need to add exceptions for this rule based on the file type
312             and path.
313              
314             Added in v0.9.2.
315              
316             =head2 dot_files
317              
318             This blocks all requests that refer to dot-files or C<..>, except for
319             the F</.well-known/> path.
320              
321             =head2 exchange_prefixes
322              
323             This blocks paths associated with Exchange servers.
324              
325             =head2 fake_extensions
326              
327             This blocks requests with fake extensions, usually done with image extensions, e.g.
328             F</some/path;.jpg>.
329              
330             Added in v0.5.1.
331              
332             =head2 header_injection
333              
334             This blocks requests that attept to inject a header in the response. e.g.
335             C<GET /%20HTTP/1.1%0d%0aX-Auth:%20accepted%0d%0a>.
336              
337             Any path with an HTTP protocol suffix or newline plus carriage return
338             will be rejected.
339              
340             Added in v0.7.0.
341              
342             =head2 ip_address_referer
343              
344             This blocks all requests where the HTTP referer is an IP4 or IP6
345             address.
346              
347             Added in v0.5.0.
348              
349             =head2 misc_extensions
350              
351             This blocks requests with miscellenious extensions in the path or
352             query string.
353              
354             This includes common extensions and suffixes for backups, includes or
355             configuration files.
356              
357             =head2 non_printable_chars
358              
359             This blocks requests with non-printable characters in the path.
360              
361             =head2 null_or_escape
362              
363             This blocks requests with nulls or escape chatacters in the path or
364             query string.
365              
366             =head2 protocol_in_path_or_referer
367              
368             This blocks requests that have non-web protocols like C<file>, C<dns>,
369             C<jndi>, C<unix>, C<ldap> or C<php> in the path, query string or referer.
370              
371             Added in v0.5.1.
372              
373             =head2 require_content
374              
375             This blocks POST or PUT requests with no content.
376              
377             This was added in v0.4.1.
378              
379             =head2 script_extensions
380              
381             This blocks requests that refer to actual scripts or source code file
382             extension, such as C<.php> or C<.asp>. It will also block requests
383             that refer to these scripts in the query string.
384              
385             =head2 system_dirs
386              
387             This blocks requests that refer to system or metadata directories in
388             the path or query string.
389              
390             =head2 unexpected_content
391              
392             This blocks requests with content bodies using methods that don't
393             normally have content bodies, such as GET or HEAD.
394              
395             Note that web sites which do not differentiate between query and body
396             parameters can be caught out by this. An attacker can hit these
397             website with GET requests that have parameters that exploit security
398             holes in the request body. The request would appear as a normal GET
399             request in most logs.
400              
401             =head2 webdav_methods
402              
403             This blocks requests using WebDAV-related methods.
404              
405             =head2 wordpress
406              
407             This blocks requests for WordPress-related pages.
408              
409             =head1 SOURCE
410              
411             The development version is on github at L<https://github.com/robrwo/Plack-Middleware-Security-Simple>
412             and may be cloned from L<git://github.com/robrwo/Plack-Middleware-Security-Simple.git>
413              
414             =head1 BUGS
415              
416             Please report any bugs or feature requests on the bugtracker website
417             L<https://github.com/robrwo/Plack-Middleware-Security-Simple/issues>
418              
419             When submitting a bug or request, please include a test-file or a
420             patch to an existing test-file that illustrates the bug or desired
421             feature.
422              
423             Suggestions for new rules or improving the existing rules are welcome.
424              
425             =head1 AUTHOR
426              
427             Robert Rothenberg <rrwo@cpan.org>
428              
429             =head1 COPYRIGHT AND LICENSE
430              
431             This software is Copyright (c) 2014,2018-2023 by Robert Rothenberg.
432              
433             This is free software, licensed under:
434              
435             The Artistic License 2.0 (GPL Compatible)
436              
437             =cut