File Coverage

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