• logo.png

icon menu-mobile.svg

From wikibase-solutions.com

Developer log #10 :: Wiki Security Preparations

Recently, while I was working for one of our customers, the subject of wiki security came up and how they’d hired an external company to perform a penetration test of the wiki. At first, I felt incredibly overwhelmed by this topic and the prospect of having to undergo a test by an external company. Especially since previously, I knew very little to nothing about this subject. Fortunately though, information was easy to come by. Better yet, you’d be surprised how much MediaWiki handles on its own or makes it easy for you to handle yourself. So, I've decided to write a little (although knowing myself, it may become extensive) log about what I did to prepare for the pen test and what easy steps you can take to make sure your wiki will stand up to a professional amount of scrutiny.

Get your wiki on HTTPS

Although this may come as a bit of a ‘duh’ to most of you, making sure you transmit all your data over a secured connection is the first (and very important) step. So get your SSL certificates in order and make sure all your requests are redirected to https. There are multiple ways to do this.

For the site I’m working with, I was working with the AWS services platform, and I asked the engineers there to make sure it happened before the request hit the load balancer. But there are ways to do it on your own server. We use apache for our servers, and I know that you can redirect any requests through your .htaccess file.

In the htaccess in our root, we have the following code:

# This will enable the Rewrite capabilities
RewriteEngine On
## Force HTTPS
RewriteCond %{HTTP:X-Forwarded-Proto} =http
RewriteRule .* https://%{HTTP:Host}%{REQUEST_URI} [L,R=permanent]

Getting your site to only work through HTTPS is an important step in this process and several other steps will be relying on this one, hence why I felt it important to mention.

Update to jQuery 3.5.0 (or later)

In jQuery 3.5.0, there’s been an important security fix concerning XSS injection (which I’ll get back to extensively later). Since MediaWiki uses jQuery, it’s probably a good idea to update to a later version of jQuery. A minor drawback of this upgrade will be that the Gadgets extension will no longer function, but it will be worth the additional security upgrade.

Remove folders from the root...

...unless they need to be there for your wiki to function. So for example, in our case, we have some folders that are used (and sometimes managed) by extensions that are in the root. We move those folders one level up, so they aren’t normally accessible and make sure we alter references in settings for those extensions.

Essentially, we don’t want people to directly access any of the files we don’t want to give them access to, while still giving our server full rein to perform its functions. I’ll get into an alternative for this later.

Specific issues you’ll want to look into

Block all access to your mw-config folder

Honestly, if you want a nice adrenaline rush, you should try to navigate to <yourwikinamehere>/mw-config/index.php?page=Restart&lastPage=Language and see what's there. Because for us, we got a big old button asking if we wanted to restart the installation and clear all the data. That’s really not something we want to make externally available.

We fixed this by adding:

RedirectMatch 404 /\mw-config

Use WSForm

I have the rare privilege of being allowed to work with the awesome WSForm extension, which since v0.8.0.8.0 has a security setting. Starting from this version, a lot of security options have been shored up and generally improved. Okay, this is a little mean as since the moment of writing, this extension isn’t publicly available. Hopefully though, by the time of you’re reading this, it will be and you can use this as well!

Limit information you're sharing with the outside world

It is a possibility for some users to find information about your application simply by inspecting some files. For example, any file that exposes information about your installation may lead to malicious users finding common avenues of attack. So if you’re using MediaWiki 1.31.10, that specific version may have weaknesses that are shared on some platform and serve as a potential freebie to attackers. At least make them work for it.

Here’s another code snippet for what we have in our .htaccess to prevent some of these, but this shouldn't be seen as a full list. Use your own discretion for files that you want to hide from the world:

# This will enable the Rewrite capabilities
RewriteEngine On
# 1) If NOT the current host
RewriteCond %{HTTP_HOST}@@%{HTTP_REFERER} !^([^@]*)@@https?://\1/.*
# 2) Deny access to these specific files
RewriteRule "INSTALL$" "-" [F]
RewriteRule "COPYING$" "-" [F]
RewriteRule "CREDITS$" "-" [F]
# 3) Deny access to these file types
RewriteRule "\.(md|sample|json|js|dockerignore|lock|phar?)$" "-" [F]

Limit access to some folders on your server

So, another way to lock down pages from being viewed by outside sources is to just give a blanket denial for entire folders. For example, since we do a lot of settings, it would become a little bit messy to have everything in one LocalSettings.php file. We separated all the settings into different files and saved them in a Settings folder. In this folder, I’ve placed an .htaccess file which has the following simple code.

Deny from all

This will disallow any external access to the folder. Keep in mind though that this might also block some internal requests, so if this causes problems, there are alternatives. If you run Apache 2.4 or later, you can add this to your .htaccess (courtesy of Pieter):

<IfModule mod_authz_core.c>
    <Files *.php>
        Require all denied
    </Files>
    <Files *<specificfile>.php>
        Require all granted
    </Files>
</IfModule>

This will deny access to all .php files, but will grant access for <specificfile>.

Don’t allow users to redirect pages through URLs

Now you might want to test if this works for you, since I’ve been told by other developers that this doesn’t happen for them. You should try to do the following:

  • Navigate to <yourwikinamehere>//wikibase-solutions.com/_ .
  • If you are navigated to the wikibase-solutions site, you should add the following code snippet to your .htaccess file:
## Kill underscore redirects with junk character at end
RewriteCond %{REQUEST_URI} (_)$
RewriteRule .* %{REQUEST_URI}/ [L,R=permanent]

Other URL safety issues

Now MediaWiki has an inherent problem: we want to give users full content control, but we also want our users to be safe. Unfortunately, external linking can lead to dangerous situations, with a single malicious redirect being enough to spill much information to an actor where you might not want this information. For the customer we’ve been working for, we came up with a front-end solution. Whenever a user tries to navigate to an external site (or at least a site deemed to be external), we present the user with an explicit choice, showing the page they are navigating to and asking them if they are sure they want to do this. Here’s our Javascript solution (courtesy of Robis) to the problem, placed in Common.js:

$(document).on('click', 'a:not([role="link"])', function(e){
    console.log(e.target.closest('a').href);
    if(!e.target.href){
      e.target.href = e.target.closest('a').href;
    }
    if(this.host != window.location.host || this.host == null ){
           e.preventDefault();
      var msg = "You are currently navigating to an external site: "+ e.target.href +
"\nWe cannot promise this is a safe site to navigate to as it lies outside our domain."+
"\nIf you believe this site is unsafe (eg. a phishing site), contact your system administrator immediately."+
"\nIf you are not sure this is a safe site, click cancel. Otherwise, proceed by clicking confirm";
       if(e.target.parentNode.classList.contains('newwin')){        
         var r = confirm(msg);
         if (r == true) {
         	var otherWindow = window.open();
 			otherWindow.location = e.target.href;
			return false;  
          } else {
            return;
          } 
       }else{
            var r = confirm(msg);
            if (r == true) {
                window.location = e.target.href;
            } else {
              return;
            }
       }
    }else{
     if(e.target.parentNode.classList.contains('newwin')){
        e.preventDefault();
       var otherWindow = window.open();
 		otherWindow.location = e.target.href;
		return false;
     }else{
       return;
     }
    }
})

Tell your server to set a header

I didn’t find a way to set this header through MediaWiki, although there will be a bunch of headers later that can be set through MediaWiki settings.

Header set X-Permitted-Cross-Domain-Policies "none"

XSS vulnerabilities

One of the biggest issues we found is the vulnerability to XSS, or Cross site scripting. Essentially, this means people can insert malicious script into our wiki, which will execute and expose our data to the outside world. Luckily, there are many things you can do to make your wiki safer against this kind of vulnerability.

  • Most importantly, look in your MediaWiki settings. If you spot the underneath setting anywhere, set that to false. However, there may be situations where you need to have raw html on for whatever reason. In these cases, there are still things you can do, but understand that from a safety perspective, it’s probably better to eliminate the reason for your forced use of raw html.
$wgRawHtml = true;
  • Next, check anywhere on your wiki where you do inputs and make sure they are sanitized before sending them anywhere. Many extensions will make this easy for you as they do this internally (such as TinyMCE editor or WSForm), but it's always worthwhile testing this. You can test it by trying to save the following code snippet in any input field. If you get a popup at the top, your inputs are vulnerable to XSS.
<html><script>alert("Oops!")</script></html>
  • Update to jQuery 3.5.0, as mentioned above.
  • If you’re on MediaWiki version 1.32.0 or later, look into the $wgCSPHeader setting. It will assist in the blocking of xss, especially when used in combination with the above options. I can't speak for the efficacy of this specifically, since we were still using the last LTS (Long-term Support) of MediaWiki at the time of writing (1.31).

Minor fixes and things that MediaWiki made easy

  • There is always going to be a vulnerability inherent to your Special:Upload page, since it’s possible to upload through url there, if you have that switched on. There is an easy solution though, thanks to how easy MediaWiki is to configure. If you want to deny all users the ability to upload from url, you can set the code snippet underneath in your LocalSettings.php.
// Deny all users the right to upload by url.
$wgGroupPermissions['user']['upload_by_url'] = false;
// Allow only admins to upload by url.
$wgGroupPermissions['sysop']['upload_by_url'] = true;
  • If you do not run a public wiki, consider switching off anonymous viewing of your pages. MediaWiki has some good options in place for this through rights that are easily publicly available. Similarly, there are extensions which will allow you to control who can see your pages. My personal favourite is the Lockdown extension, but there are other options available. I would be remiss not to mention that one of our talented developers, Marijn, will soon start work on WSPermissions.
  • Consider setting cookie security settings for your wiki. Below I’ve put some code that shows how we’ve activated a bunch of security headers for our MediaWiki cookies.
// Only sends the cookie if on https
$wgCookieSecure = true;
// On the $wgCookieSecure page, it reads the following:
// "Sites using reverse proxies, load balancing or some other method which converts HTTPS requests into HTTP ones need to set the X-Forwarded-Proto header for detection to work correctly. (See also $wgVaryOnXFP)."
// This flag will set that header for cookies. Only do this for the AWS domains, do not do this for the wikibase domain.
if ($wgServer !== '//<a non-aws domain>') {
    $wgVaryOnXFP = true;
}
// Only make the cookies available to http protocol on some browsers (but not all).
$wgCookieHttpOnly = true;
// Set the Same-site cookies to be safer as well.
$wgCookieSameSite = "None";

Still reading?

Gosh, thanks. This list of things you can do is by no means exhaustive and you should feel free to look into alternatives. These are the findings of the pen test we had to go through and how we solved it. Thanks for reading all the way down here and hopefully, some of this was helpful to you!