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:
NameVersionDescription
Digest::HMAC_SHA1>=0Required
Digest::SHA>=0Required
HTML::Parser>=0Required

PackageForm edit

Author Crawford Currie http://wikiring.com, Jan Krueger (Modell Aachen GmbH) -- from an original idea by Sven Dowideit http://wikiring.com
Version 2.0
Release 2.0
Description Secure your Foswiki so it can't be used for mounting phishing attacks
Repository https://github.com/foswiki/SafeWikiPlugin
Copyright © 2007-2017 C-Dot Consultants http://c-dot.co.uk
© 2012-2013 Modell Aachen GmbH http://modell-aachen.de © 2008-2017 Foswiki Contributors
License GPL (Gnu General Public License)
Home http://foswiki.org/Extensions/SafeWikiPlugin
Support http://foswiki.org/Support/SafeWikiPlugin
Topic revision: r1 - 25 Apr 2017, UnknownUser
This site is powered by FoswikiCopyright © by the contributing authors. All material on this site is the property of the contributing authors.
Ideas, requests, problems regarding GSICS Wiki? Send feedback