Security in OpenSim

There are, confusingly, working options for using secure connections to OpenSim but these are related to remote admin and other web interfaces, not to the more fundamental issue of securely logging into the simulator in the first place. (I will avoid the usual misuse of the term “SSL” here, since this has long ago been obsoleted by the more modern TLS, but in practice these terms are used interchangeably.)

OpenSim logins are done over an insecure HTTP-only connection. There is no reason that they could not be done over a secure connection but the code has not been written made functional. It would be quite a simple job. Grid administrators would need TLS (“SSL”) certificates in the same way as they mostly also use for the front-end web sites representing their grid online or whatever else they run securely on their server. It’s not a big deal to do.

The viewer sends an MD5 hash of the password, not the clear-text password, to the server. This, however, is not secure. The reason that MD5 is still used rather than changing to the much more secure SHA2 is that the entire password table for a every OpenSim grid would have to be changed. I wonder if the table could be modified by adding a column noting whether MD5 were used or, if available, the password had been migrated to SHA2? This however, would need coordination between viewer developers and OpenSim core developers, as the viewer would need to transmit the information to OpenSim grids. In practice, this is unlikely to happen. It would also require front-end software i.e. web GUIs to be changed.

Admittedly, MD5 uses salt so that rainbow tables can’t be used, but MD5 is still susceptible to collision attacks and passwords can be sniffed and recovered trivially in a matter of seconds. What other server technology, other than OpenSim, transmits MD5 hashed passwords in clear text? Answer: nobody! It’s not at all safe and HTTPS is a basic requirement for all server platforms. If this was in place, the MD5 hash would be transmitted using strong encryption anyway, so it wouldn’t matter any more that we were still using MD5 internally because only the sender and the server would see it. Ok, SHA2 would be ideal but there we are – at least things would be basically secure.

Here is the config section in OpenSim.ini that you need to look at:

; ssl config: Experimental! The auto https config only really works definately on windows XP now
; you need a Cert Request/Signed pair installed in the MY store with the CN specified below
; you can use https on other platforms, but you'll need to configure the httpapi yourself for now
http_listener_ssl = false ; Also create a SSL server
http_listener_cn = "localhost" ; Use the cert with the common name
http_listener_sslport = 9001 ; Use this port for SSL connections
http_listener_ssl_cert = "" ; Currently unused, but will be used for OSHttpServer

; HTTPS for "Out of band" management applications such as the remote
; admin module
;
; Create https_listener = "True" will create a listener on the port
; specified. Provide the path to your server certificate along with it's
; password
; https_listener = False
; Set our listener to this port
; https_port = 0
; Path to X509 certificate
; cert_path = "path/to/cert.p12"
; Password for cert
; cert_pass = "password"

The confusion here is that there are two separate “SSL” configurations here. One is for “out of band” connections like web interfaces and remote admin, i.e. https_listener, which comes second here. This does work (also see Robust{.HG}.ini etc). But it’s the first, http_listener_ssl, that is relevant here. This does not work. It’s for a code stub that hasn’t been completed and, if you try to use it, the region will never start successfully. The code in question is: [INSTALL_FOLDER]/opensim/OpenSim/Server/Base/HttpServerBase.cs

What is needed is for this code to be completed. It also needs the addition of an option to force logins over secure HTTP connections, as the existing options would only add HTTPS logins rather than replace them over HTTP if it worked as intended. For security, you need to actually prevent people from sending passwords in clear text only, i.e. by adding force_https_login = true in order to redirect all login requests to HTTPS, not simply enable optional HTTPS for the login URI. It might be worth also having a setting for force_http_traffic = false as default but allowing a setting of force_https_traffic = true for better security where sufficiently fast connections allow all traffic to be sent over HTTPS  – this would obviously affect only HTTP(S) and not UDP traffic to and from the viewer. Note that Second Life™ does use HTTPS for its login URI, and this has been true for many years. What is stopping OpenSim from developing this very simple, industry-standard security?

Editor’s note 2014-12-30: despite Melanie’s comment “… one really should not trust grids that don’t offer a https:// loginuri”, the only grids currently appearing to offer such URIs are Second Life™ (and test grids) and Avination. These have the wherewithal to develop and deploy solutions beyond what is available in the current OpenSim release code. While Melanie’s position is ideal, it is also currently impossible, either because the code simply doesn’t work or else because how to deploy it is undocumented and obscure to the point that nobody does it in practice. It seems likely that the former is true, given that the present writer and no doubt many other grid administrators have tried and failed to make it work. I also doubt Melanie’s assertion (from 2012) that MD5 is sufficiently difficult to break in practice: by 2014, it appears now to be trivial to crack MD5 hashes – corrections welcome from anybody with greater expertise in encryption hashes etc.

In-world web browsing in OpenSim #1

Prototype in-world browser in OpenSim grid

Prototype in-world browser in OpenSim grid

Web browsing in OpenSim

Web browsing inside OpenSim (or even Second Life™, for those still left there) is never going to be as seamless as through a browser. You might ask, “What’s the point?” but, in fact, there is a point in certain limited circumstances, i.e. when a number of people want to look at the same resources together in a virtual environment. The 3D metaverse isn’t about replicating real life at all but rather being able to meet people in a simulated physical environment in order to share the same space. It is an immersive discussion forum and therefore one can easily imagine wanting to show people things on the Web and discuss them within that immersive environment.

Media On A Prim doesn’t do the job

Why don’t I just use Media On A Prim (MOAP)? The reason that I am not using Media On A Prim (MOAP) is because (a) it seems to be broken in OpenSim at present, so every time you click on a link it reverts to the previous page straight away so you cannot navigate; (b) the texture does not immediately render without user intervention and it does not look the same for every viewer. Despite it being technically better in supporting Flash, audio, video and native scrolling (if it wasn’t apparently broken), it isn’t ideal for collaborative use. My method uses the OsDynamicTextureURL* functions, which ensure that all viewers see the same texture.

My image-based solution

Aside from MOAP, this has been done before but there is apparently no open source code available nor any advice on how it was done. Well, realising this made me want to find a way to replicate it and provide exactly that. I have now made a mostly functional prototype web browser inside OpenSim. It relies ultimately on WebKit through PhantomJS, itself described as a “headless WebKit scriptable with a JavaScript API”, i.e. a set of tools that can test web pages without a visible browser and, amongst other things, render the output into image files. However, manipulating the relatively arcane directives of PhantomJS directly proved difficult, so I naturally turned to CasperJS as suggested, which is “an open source navigation scripting & testing utility written in Javascript” in order to make PhantomJS easier to deploy. Although it’s not altogether different and there is still a level of complexity, especially for someone like me who only crudely hacks JavaScript, it does live up to its aim: it makes it considerably easier to use PhantomJS for its intended purpose.

The LSL/OSSL script communicates by HTTP POST with a PHP script on our server, which requests an image of the web site from the JavaScript, which processes clicks on the surface coordinates that it was sent to the links in the page and renders the resulting page as an image.

The JavaScript

Let’s call this imagecapture.js (see the code below) and put it on a server somewhere. The location does not have to be public once it’s out of testing – in fact, if you want the secret key to remain secret, it had better not be so. We should make this HTTP request from OpenSim using TLS i.e HTTPS, still often incorrectly known as SSL, it’s predecessor in cryptography. That way, we are not transmitting the secret key in clear text. Effectively, we are producing a secure image-based proxy.

There are instructions elsewhere on the Web for how to install PhantomJS and CasperJS, which I won’t repeat – not least because it will depend upon your *nix distribution. I found it reasonably easy to install PhantomJS using apt-get or aptitude on Ubuntu. It was mildly harder to install CasperJS on Ubuntu 14.04 but a little research should accomplish it easily.

There are some things that you will not be able to do, like plugins showing live video or audio of any kind. But you can do this in other ways in OpenSim already. Some JavaScript links will not work, although those using onClick="location.href='http://example.org/'", for example, will work fine. Normal anchor links with or without images will work fine too. In order to make anything work at all, I must tell you now that you will need access to a web server and know how to use it. Here is the JavaScript, to start with:

var casper = require('casper').create({
  pageSettings: {
    javascriptEnabled: true,
    loadImages: true,
    loadPlugins: true
  }
});

var webPage = require('webpage');
var page = webPage.create();

var args = require('system').args;
var startURL = args[4];

var width = 1024; // 1024;
var height = 768;
var zoom = 1; // DO NOT CHANGE THIS

casper.options.viewportSize = {width: width, height: height};

x = parseFloat(args[5]);
y = parseFloat(args[6]);

x = Math.round(x * width);
y = Math.round(y * height);

casper.start().zoom(zoom).thenOpen(startURL);

// code to click x, y
casper.then(function() {
  this.mouse.click(x, y); // clicks at coordinates x, y

  documentHeight = this.evaluate(function() {
    return __utils__.getDocumentHeight();
  });

});

var address = startURL;

var clipHeight = casper.evaluate(function(){
  return document.querySelector('body').offsetHeight;
});

casper.then(function() {
  address = this.getCurrentUrl();
  console.log(address);
  this.capture('cache/image.png', {
    top: 0,
    left: 0,
    width: width, // clipWidth,
    height: documentHeight // clipHeight
  });
});

casper.run();

This code is basically my own and is released under the GNU General Public Licence (GPL) 3.0 for public use. It has been created with the considerable help of the CasperJS and PhantomJS documentation. While not complete, this is pretty helpful and I have to take off my hat to the developers, who are clearly very clever and able people.

The PHP script

It won’t run without some PHP, which I have also written and release under the same licence. Change the “secret key”: may I suggest using SHA2 for a secret hash? Mine is just a single letter for testing, so far – terribly secure! Let’s call this imagecapture.php and put it on the same server.

<?php
$secretKey="l"; // change this after testing
if ( isset($_POST['secretKey']) && $_POST['secretKey'] == $secretKey ) {
  if ( isset($_POST['searchURL']) ) {
  $searchURL = stripslashes($_POST['searchURL']);
  if ( isset($_POST['x']) ) {
    $searchURL .= ' ' . $_POST['x'];
    if ( isset($_POST['y']) ) { $searchURL .= ' ' . $_POST['y']; }
  }
  $execString = '/usr/bin/casperjs --ignore-ssl-errors=yes ./imagecapture.js ' . $searchURL;
  //$execString = '/usr/bin/phantomjs --ssl-protocol=any ./imagecreate.js ' . $searchURL; // Old version before I used CasperJS
  exec($execString, $output, $return_var);
  echo addslashes('imageURL').'='.addslashes('http://oceangrid.net/imagecapture/cache/image.png'); //$imageURL;
  echo '&'.addslashes('returnURL').'='.addslashes($output[0]);
  list($width, $height, $type, $attr) = getimagesize("cache/image.png");
  echo '&'.addslashes('width').'='.addslashes($width);
  echo '&'.addslashes('height').'='.addslashes($height);
  }
}
?>

Put it all together

Once this is out of testing, adjust the location of the above imagecapture.js file and change the location in this script to reflect this. You ought to make sure that these are owned by the Apache or Nginx user (usually www-data), which will mean they will not write the file image.png when you run them from the command line. When we call them later via HTTP request to the web server from within OpenSim, however, they will do so properly.

This is all obviously quite snazzy, though it does have limitations like lack of audio or video. It has no Flash.  Audio can be done in other ways but MOAP isn’t optimal as described, but perhaps at some point these could be somehow resolved in one neat package. Until then, this fills a gap. Simple JavaScript links will work but most fancier bits won’t, so expect some broken links etc.

Now all we have to do is create a web browser in OpenSim. As we speak, I am about 75% through this process. I have to add scroll bars, back and forward functionality and do a fair bit of checking. However, the basic thing works. If you want to see it in its incomplete state, please get in touch with me personally and I will tell you where in my grid Ocean Grid you can find it. Once it is complete, I will then release a full version in IAR format as well as the various LSL/OSSL scripts that are required.

This is the end of part one. So far, we have created a way by which a set of HTTP POST variables can be sent to a PHP script, which in turn calls some JavaScript that writes an image of the web page and returns the URL (which may have changed e.g. via 301 redirects on the web server). The PHP then returns all of this stuff in POST data to the requesting script inside OpenSim.