SafeWikiPlugin
Secure your Foswiki so it can't be used for mounting phishing attacks
All of the configuration options below refer to those available in
configure
in "Extensions --
SafeWikiPlugin", or
{Plugins}{SafeWikiPlugin}
in
lib/LocalSite.cfg
.
What it does
This plugin helps prevent evil people from using your wiki to mount
cross-scripting
attacks. It is intended to:
- defuse malicious (or accidentally malformed) raw HTML entered in topics by an attacker,
- disable script in URL parameters
Cross-scripting attacks don't just affect public wiki sites. For example,
a footpad could mail one of your users with a crafted URL that, when
clicked on, compromises your entire corporate intranet.
All wikis, public
or private, need protection against these attacks.
The plugin works by filtering the HTML output by Foswiki as late as
possible in the rendering process. It removes anything dodgy from the
HTML, such as inline script tags, Javascript event handlers
containing complex script, and URIs that refer to objects outside a
controlled range of sites.
Whenever anything is filtered, a report is written to the Foswiki warning
log.
The plugin filters all HTML it thinks is dodgy from the output. There is
a chance that one or more of the extensions you are using works by embedding
naughty HTML. If you find that
SafeWikiPlugin kills one or more of your other extensions, then you are
advised to seek fixes from the authors of those extensions.
SafeWikiPlugin also has a 'clean html' switch that can make it report
an error if malformed HTML is generated by Foswiki.
It is unavoidable that there will be a performance penalty when using the
plugin. The size of this penalty depends on your exact configuration, but
benchmarks suggest that on average it is less than 1% of the total
rendering time.
WARNING
This software is provided in the hope that it may be useful.
The authors make no warranty, implied or otherwise, about the
suitability of this software for safety or security purposes.
The authors shall not in any case (except as required by applicable laws) be
liable for special, incidental, consequential, indirect or other similar
damages arising from the use of this software.
In clearer words: while the authors are confident that the plugin does
exactly what is documented, we can't make an ironclad guarantee. Additionally,
the safety checks introduced by SafeWikiPlugin can be circumvented by
plugins, by misconfiguration, and by JavaScript code making use of macros
without sufficient validation. This plugin does not replace common sense!
It should prevent attacks out of the box, but additional plugins may weaken
the protection, so administrators are advised to be careful about installing
new plugins, signing new JavaScript code, or making changes to the filter
expressions in the configuration.
How to authorize/sign JavaScript code
The easiest way to get script code into the wiki without having to deal with
signing is to put it in a
.js
file that is included by URL. By default,
scripts attached in the System web are considered safe; you can define
additional safe locations in the configuration. Unsafe locations will, of
course, be filtered.
Authorizing static scripts in topics or templates
For good reasons that are a bit complicated to explain, SafeWikiPlugin can't
magically exempt certain topics from XSS filtering. Therefore, each individual
piece of script code has to be authorized explicitly. SafeWikiPlugin provides
three ways to do that:
- an administrator can add a signature to the wiki configuration;
- a plugin can include a signature;
- script code can be cryptographically authenticated by people who know a secret key (chosen per wiki in
/bin/configure
).
The third option is meant to be used for providing wiki apps that users can
install without needing administrator rights; for instance, a wiki consultant
might use their customers' secret keys to distribute pure wiki apps without
causing any administrative overhead for the customers -- all the customers
need to do is copy and paste the wiki app's code, and it's already signed so
SafeWikiPlugin won't destroy it.
The first two ways use simple SHA-256 signatures of the script code; the third
way uses a HMAC/SHA-1 hash instead (HMAC means that you can only calculate the
"right" hash value if you know the secret key). We're going to call these
hashes *MAC*s from now on.
The two types of hashes are easy to distinguish: while both use a Base64
encoding and thus look like gibberish, the SHA-256 is 43 characters long
whereas the MAC is only 27 characters long.
While it's possible to calculate the SHA/MAC values yourself if you want to,
it's much easier to let SafeWikiPlugin do it instead. For security reasons,
console access to the server is required. After writing a piece of
JavaScript code and embedding it in a topic or template, just run
./safewiki-sign from within Foswiki's
tools
directory and paste the code.
As always, you can terminate your input with Ctrl-Z + Enter (Windows server)
or Ctrl-D (Linux etc.; you may need to press it twice).
The script will output the correct SHA/MAC hashes (using the currently
configured secret key in the case of MAC).
The section for SafeWikiPlugin in
/bin/configure
has an option
{SignaturesList}
where you can add your own signatures, like this:
['2rvfFrggTCtyF5WOiTri1gDS8Boibj4Njn0e+VCBmDI',
'vLFBaEsZg+AgnWTdBIct8XO/NFeDNlsQjoDGtMPz+ew']
Plugins can add signatures by installing a file to a special location. For
example, EmptyPlugin might install
lib/Foswiki/Plugins/SafeWikiPlugin/Signatures/EmptyPlugin.pm
.
This file should contain Perl code and do nothing but return a reference to
an array of signatures, like this:
['2rvfFrggTCtyF5WOiTri1gDS8Boibj4Njn0e+VCBmDI',
'vLFBaEsZg+AgnWTdBIct8XO/NFeDNlsQjoDGtMPz+ew'];
MACs are simply inserted as a specially crafted JavaScript comment at the
very beginning of the inline code, be it a
script
tag, an
on*
attribute,
or a
javascript:
URL (in that case the comment goes right after the
javascript:
part. Here's an example:
<script>
/*safewiki:U2AGOBOv0pY4R4poBM/EXQNRFoE*/alert('This works (if the MAC is correct)');
</script>
Authorizing dynamically generated or altered code
If you are shipping dynamically generated code with a plugin (example:
JQueryPlugin's code to make Foswiki preference values available to scripts),
just embed the code using a call to
Foswiki::addToZone
. It will
automatically bypass filtering. In addition,
=Foswiki::Plugins::SafeWikiPlugin::Signatures::permitInlineCode temporarily
(for the current request) whitelists a piece of code to be used in a handler
attribute or an inline script tag (as with normal signatures, it only works if
that piece of code is the exact content of the handler attribute or script
tag). In either of these cases, it's your responsibility to validate any code
you add to a page this way.
Alternatively, if that's not an option for some reason or another, you can
add a signature even for code that contains macros and is embedded in a topic
or template file. There are a few requirements for making this work:
- The code must be added to the page with the
ADDTOZONE
macro.
- Macros used within the script code must be escaped (using
$percnt
syntax).
- Special syntax must be wrapped around macros used in script code, in order to allow SafeWikiPlugin to verify that, for instance, the expanded macro doesn't break out of a JavaScript string constant.
The syntax for verifying macro expansions is very simple. You use it to tell
SafeWikiPlugin which pattern the expanded macro (the "result") is matched
against. Any macro that doesn't have such a pattern associated with it, or
that fails to match the pattern, will not be expanded nor unescaped.
At this point there are two patterns:
-
identifier
permits an expansion that is made up of nothing but alphanumeric characters and underscores;
-
string
permits any character but backslashes and the quotes surrounding the macro.
Matching strings more exactly is not currently implemented, so it's not
possible to use macros that expand to values containing quotes or backslashes.
Here are examples of how to use the syntax:
%ADDTOZONE{"script" id="test" text="
<script>
var x = /*safewiki:identifier*/'$percntSPECIALWEB$percnt'/*safewiki:end*/;
var y = /*safewiki:string*/"$percntBROADCASTMESSAGE$percnt"/*safewiki:end*/;
var z = 'a'+/*safewiki:string*/'$percntQUERY{\"...\"}$percnt'/*safewiki:end*/;
</script>
"}%
If the macro expansions are accepted by the patterns (for example,
BROADCASTMESSAGE
must not contain double quotes or backslashes, and
SPECIALWEB
is restricted to alphanumeric characters and the underscore),
this will expand to something like the following, in the script zone:
<script>
var x = 'Special';
var y = "We are currently doing maintenance work on the wiki. Sorry for any inconvenience.";
var z = 'a'+'(result of query)';
</script>
Please note that these special comments are only recognized if there is no
space between them and the string constant containing the macro expression,
and neither may there be anything else in that string constant than the macro
expression.
Annotating macros is not necessary for a number of standard Foswiki macros
(e.g.
BASEWEB
,
PUBURL
,
SYSTEMWEB
) -- these have the appropriate types
pre-defined, so in many cases your script will actually work without adding
the annotations.
Now that these prerequisites have been fulfilled, all that's left to do is to
determine the MAC. You can't use exactly the same method as above because the
assertions are generated
after dynamic scripts got their chance, i.e. macros
have already been expanded.
To tell SafeWikiPlugin that it should try to protect the zone snippet, add a
signature
parameter to the
ADDTOZONE
macro, with any non-empty value. Now,
the above
./view
command will output something like this somewhere near the
top:
SafeWikiPlugin: SHA <hash> MAC <hash> for this zone content: <unexpanded code>
We have to let the ADDTOZONE macro know about that MAC so that
SafeWikiPlugin
won't attempt to filter the zone snippet. That's what the
signature
parameter you just read about comes in: just put the MAC in there. Done!
(You could also add the SHA to the list of signatures instead if you prefer.)
Gory Details
This explains the additional filtering steps
if none of the signatures apply.
JavaScript
The values of all Javascript on* handlers (such as
onload
,
onmouseover
,
onblur
etc) are automatically compared against a list of filter-in regular
expressions, one of which must match, or the handler will be replaced by a
disarming string.
By default only simple function calls with atomic parameters are
permitted in on* handlers. For example:
javascript: fn(param1, "param2")
is permitted,
but
javascript: alert(window.open("http://evilsite.cn"))
is not.
Inline scripts (SCRIPT tags without a
src
parameter) are always
filtered out (removed). URIs used in certain parameters are compared
against a whitelist of filter-in regular expressions, one of which must match
or the URI will be replaced with a disarming string.
In addition, script snippets interpreted by
JQueryPlugin's METADATA plugin
(notably
{key:value}
constructs in class/data attributes) are filtered in
the same way.
URIs
The tags filtered, the "whitelist" regular expressions, and the placeholder
string used to mark disarmed code are all defined using the
configure
interface. See the setup for
SafeWikiPlugin for more help.
Notes for extensions (Plugins etc) authors
As mentioned above, there is a risk that use of SafeWikiPlugin might
prevent your extension from working. If that is the case, it will usually
be because you have tried to embed something in the HTML that the
SafeWikiPlugin regards as "naughty" - for example, inline script,
complex expressions in handlers etc.
The way to overcome this in your plugins is described in the section about
authorizing dynamically generated code, but for your convenience here's
something to copy & paste:
Add code to a zone
Foswiki::Func::addToZone("script", "MYZONETHINGY", "my script here", "JQUERYPLUGIN::MYDEPENDENCY");
Add code in an event handler or inline tag
my $code = encode_entities("alert(\"This is my shiny code\");");
if ( Foswiki::Func::getContext()->{SafeWikiSignable} ) {
Foswiki::Plugins::SafeWikiPlugin::Signatures::permitInlineCode($code);
}
# Presumably added to output later on
return "<script>$code</script>";
Installation Instructions
WARNING the current version is only compatible with Foswiki 2.0 and later. If you need to install on an
earlier Foswiki, you will need to download the Dec 2009 package.
You do not need to install anything in the browser to use this extension. The following instructions are for the administrator who installs the extension on the server.
Open configure, and open the "Extensions" section. Use "Find More Extensions" to get a list of available extensions. Select "Install".
If you have any problems, or if the extension isn't available in
configure
, then you can still install manually from the command-line. See
http://foswiki.org/Support/ManuallyInstallingExtensions for more help.
You must run and save
configure
at least once to complete installation.
Plugin Info
Sponsors for support and improvements are always welcome.
Change History: |
|
25 Apr 2017 |
Foswikitask:Item14335: support for signatures, and unicode support. Foswiki 2 only! |
15 Jan 2013 |
Foswikitask:Item12327: revamp signing system; assorted fixes |
5 Feb 2012 |
Foswikitask:Item1928: remove nop tags, support UTF-8 |
18 Nov 2009 |
Foswikitask:Item1963: add configure checkers for basic sanity of {SafeURI} and {UnsafeURI} filter values; also complain if {AllowRedirectUrl} is true |
12 Oct 2009 |
Foswikitask:Item8255: fix extraneous (missing '!') <[endif]--> shown by IEs at top of page |
17 Sep 2009 |
Foswikitask:Item8220: support filtering of eval() calls by supporting filter-out for handlers, and URIs too while I was in there Foswikitask:Item1963: hardened the regex that selects where to get JS from to restrict it to the Foswiki System web, which is not normally writable by ordinary users |
14 Jun 2009 |
Foswikitask:Item8181: plugin made aware of use of foswikiStrikeOne which is needed to work with Foswiki 1.0.6 and later versions. |
30 Apr 2009 |
Foswikitask:Item8143: First public release |
Dependencies: |
Name | Version | Description |
---|
Digest::HMAC_SHA1 | >=0 | Required | Digest::SHA | >=0 | Required | HTML::Parser | >=0 | Required | |