File Coverage

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