MUSHCode for Ian's Big Guide to Securing your MUX/MUSH

Ian's Big Guide to Securing your MUX/MUSH (and other complaints)

What this document is
Far too long have I been observing MUX/MUSHs (which we'll call MU*s in this document) that are just asking to be abused. In this document I will discuss many of the coding problems and poor configuration choices that MU*s suffer. I commonly hear people say every system of some complexity will always have exploitables problems. Not only do I know this is not the case, but this is not the case. Your MUX or MUSH can be exploit-free or virtually exploit-free without too much grief. Note that this is ONLY a document on MUXes and MUSHes and their softcode and configuration issues. Hardcode is not covered, for the most part.

Who this document is for
For the most part, this document is meant for coders, MU* gods/godesses, and staffers. Sections I and III-VII are suited more for the non-coder, while Section II (a long section) requires prior coding knowledge.

Why this document was written
Because it needed to be.

Who wrote this document
My name is Ian Elliot, known on most MU*s as Ian. As a former softcoder for several MU*s and a MU* exploiter, I've near seen it all. I've dealt with the original MIAM (MUX-In-A-Minute, security hazard galore) and even _worse_ systems. I've audited a large amount of softcode for security purposes. I've had bad things happen to my own MU*s.

Why you should trust the author
Simply put, you shouldn't. Never trust anyone. (Paranoid answer, no? :)

If you have any comments/suggestions on this document, please email Ian Elliot with the subject "GUIDE TO SECURING MU*s" at <ian@hfs.dhs.org>.

TABLE OF CONTENTS:

Section I: Overview for the impatient
Twenty need-to-know basics and summarization of the topics to follow.
Section II: The fun begins...
The low-level coding problems that allow for evil things to be done.
Section III: And you thought this stuff was just for geeks
Information both geeks and all MU* staffers should know
Section IV: Upping the ante
Now since we've made it really hard to find bugs, we're going to upgrade
to 'near impossible.'
Section V: All out war!
What to do when all they want is to take you out.
Section VI: Managing it all
This may seem like a lot to manage, but with a few utilities one can make
securing a trivial task
Section VII: Misc Security Risks
Section IIX: Summary
Section IX: A few snippets of softcode
Various little snippets of softcode to make life easier.
Section X: Credits

Section I: Overview for the impatient

Out of all problems, these following problems are the ones just _asking_ for it the most (not in order):

1. JUMP_OK rooms/objects
Most MU*s set half the rooms on the grid (RP area) JUMP_OK to encourage RP. This is nice and all, but this also (usually) means people whom you wish to keep out of the grid can enter the grid. Later I will discuss the finer details of this.

2. ABODE rooms/objects
Rooms should ALMOST NEVER BE SET ABODE. This is asking for someone to pull the ol' set-my-home-and-go-home routine. Ah, but you say the person is set 'FIXED,' so this really isn't a concern. Nice try, but this is just asking for someone to set themselves sticky and get dropped by an object (which forces them to go home). And you can't really prevent that unless you restrict sticky, but then, why don't we just restrict Abode while we're at it, and avoid taking out yet another flag? Again, we'll discuss the finer details of this.

3. LINK_OK rooms/objects
By now you should be noticing a general trend. This flag is asking for someone to open up an exit to this room and step through. I noticed many MU*s have their staff nexus LINK_OK without a link lock. Lets see, how can I word this... BAD? Yeah, that'll do. Never, never, never allow this flag to be used without a link lock. This usually means restricting the flag to trusted people (staff). Again, more on this later, etc, etc...

4. Non-semaphored @switch-check commands
Many commands have @switches to check for states set by the command itself... And @switches are delayed... So if two of that command are entered into the command queue, they both get executed as if there's nothing set. This is bad. In many of these cases you can manage to vote for people multiple times, manage to get extra stat points of some sort, or similar things. I'll bitch you out for it later in this document.

5. "Master Room" named room
Never name your room Master Room. Who says Security by Obscurity isn't effective? Naming your master room "Master Room" (or something similar) is just an invitation to find a way into it.

6. Exits to the master room
Some of the most surprising objects in MU*s are the exits linked to the master room. Seriously, the #dbref of your master room is viewable via your in-MU* option-listing command. Armed with that tidbit of knowledge and the ability to @tel anywheres as a wizard, there should be no need for exits. Remember, by making an exit, even if it has a lock on it, you're just increasing the chances of insecurity. If it doesn't really have any value, don't add it in.

7. Insecure master room objects
Master room objects should be set UNFINDABLE so that they cannot be associated with the master room and/or the master room cannot be associated with them. Also, the objects should be set SAFE/indestructable. Note that the master room itself should also be UNFINDABLE and SAFE/indestructable.

8. Objects set WIZARD/Similar (excluding PennMUSH and similar configurations)
If you're using PennMUSH ignore this. It doesn't apply. On with the complaining: On many MU*s if the object is set inherit it gets the same powers as its owner. Note that if it has higher powers, then the owner can use the object to do anything with those higher powers. So, if you've got a wizard named Joe and Joe has an object named SomeObj, why not just set SomeObj WIZARD? The powers of the object aren't higher, right? Well, despite what you may like to delude yourself of, you don't really know that Joe will always be set WIZARD, and when he is demoted, the objects set still WIZARD will empower him to perform WIZARD actions. Inherit makes sure this will never happen. Again, don't ignore the advice to NOT SET OBJECTS WIZARD by using the excuse that you'll take care of the objects when you demote Joe. Usually this demotion is surrounded by turmoil of some sort, and it won't actually get done. I've seen it happen. I've seen what's left of the databases after the demoted staffer nukes all the important parts of the MU*.

9. The evils of VISUAL objects
Never NEVER let any softcoded wizard (or similar) owned objects be set visual, or even royalty owned objects be set visual. Why, one might ask? Besides the fact people can scan the object for potential exploits in the softcode, if there's any functions whatsoever on the object, they can very possibly be used to malicious purposes if sideeffect functions are supported by the MU*/object. Even if the object doesn't have wizard/royalty powers, the object can be used to modify other objects owned by that player, obvjects which may have higher powers. I will delve into the finer points of malicious softcode usage later in this document.

10. Insufficient locks
You can't restrict everything with flags. This is where locks become useful... But, like flags, locks are seriously under-used. Guests can walk into CharGens, normal players can join staff channels, and half the objects in the MU* can be picked up by any normal player. Again, we'll talk more about this later.

11. Over-bitting (over-powering) objects
Only give objects powers/flags they need. Anything more is a security risk. Note that this excludes flags like NO_COMMAND, HALT, and anti-sideeffects mechanisms which all take away abilities instead of add abilities. In otherwords, only allow objects the minimum abilities they need to complete their task.

12. Quotas
Quotas are meant to be earned. They are not a right. Start players out with low (or no) quota. And if you have some sort of registration process (which I recommend) start them off with no quota before they register. Also, if you do allow robots (a special class of player-objects) make robots quota requirements (how much quota a robot takes up to create) very very high. I tend to set it to 10,000. Players should not be able create robots. (Or you could just restrict @robot to wizards or the like.)
As a side note, I tend to have objects take up one quota, rooms take two, and robots take around 10,000. My reasoning for this is the following: Objects are transient and typically don't sit around long. Rooms sit around a while, so you don't want too many of them. As I stated above, players shouldn't have robots. Actually, staffers shouldn't have robots either. I'd personally restrict robot creation abilities to #1 (or the equivilent there of).

13. Robots
Pretty much what I said in Quotas.

14. Too little use of UNFINDABLE
UNFINDABLE is a good thing to use, as it helps security out a lot. One can usually trace a path from room to room with a few scripts that use the loc() function to see where exits lead. It is of my personal opinion that one of the default flags for exits should be unfindable.

15. Long fingers
In fact, many of the problems mentioned above (using loc() to see where exits lead among other things) comes from the fact that the defaults on many MU* servers is to allow people long-fingers type access. That is, they can find out information from far away. Some of the best configured MU*s don't allow long-fingers type access to the default player. Long fingers is a very good thing to make sure you have disabled to the normal player. Your security at least tripples if not quadruples. Yay security by obscurity.

16. Slamability
Slamability: How slammable something is.
Slammable: Adverb form of "Slam"
Slam: To cause an immense amount of resources (processor, memory, or similar) to be used by invoking a repetitious cycle of code/commands/functions or resource-consuming set of code/commands/functions.

Find out what your MU*'s counter-measures to slamming are. These are the various types of slams:

Attribute slams. (Write many attributes, read many attributes. Most common type.)
Object slams. (Creating many objects, deleting many objects, etc. This is rare.)
Processor slams. (Functions that take a large amount of processor freezing the command queue. Common.)

Attribute slams are especially bad, as they can invoke a large amount of hard disk usage and/or memory usage. Processor slams might require you to resort to reseting your MU* in an undesirable way that may or may not cause data loss. Object slams are generally ineffective as they're slow.

A sub note of slamability:
Increasing your FUNCTION_INVOCATION_LIMIT or FUNCTION_RECURSION_LIMIT is an invitation to processor-slam and attribute-slam. If you have code that requires either one of these two settings to be raised, consider obtaining new code. Some pre-coded packages like MIAM (all versions) require these stats be raised. These packages are not secure, nor are they safe to use.

17. The staff factor
The one permenate aspect of running a successful MU* is politics. Politics will never increase the value of a MU*. It is an emergent destructive force in human communities. Keep a close eye on political activity. With the suggestions above most things should be running themselves enough to allow you to do this.

18. The coder factor
These are the people who hold all the keys, all the knowledge, and all the code. Be wary of these people. They're flakes. Believe me, I know. I've been a coder on several places. Expect these people to be the first to screw up your MU* intentionally. And treat them very well. This is the one problem left in MU* security that cannot be solved unless you're the coder. The coder can always leave back doors. The key is to have a head coder whom you trust and whom you know will do the job right.

And last but not least...

19. The twink factor
While this really isn't security related, this falls under 'annoyance-control.' Be sure you have code in place that allows you to block these people with ease. This includes booting mechanisms, sitelock enforcing, and auto-player-smiting. I typically use a softcoded booting mechanism that nukes them if their site matches and "intelligently" modifies the sitelock table. This has some problems that will be discussed later along with problems in guest systems.

20. Okay, so the twink factor wasn't the last item...
Security of your MU* will never be perfect, just like there is no perfect encryption. But there's such thing as damn-near it. There will always be an exploit, the trick is to make it extremely hard to find. Likewise, in encryption, there will always be a key. It's just a crapshoot as to which key it is, so it's virtually impossible to find the key. The key is well hidden. There's no reason why your keys shouldn't be well hidden from players.




Section II: The fun begins...

Lets not bother looking at theory yet, but rather examples. Comments noted with a preceeding //

> @create Object
Object has arrived.
Object created as object #3
> look
Welcome Room
Contents:
Object
> &cmd_test object=$test *:@pemit %#=setq(0,0)[mid(iter(%0,setq(0,add(%q0,##))),0,0)]The sum is: %q0
Set.
> test 1 2 3 4 5
The sum is: 15
// Okay, so this all looks good so far.
// Note that this command just sums numbers delimited by spaces.
// Note that I coded it for efficiency, not ease of coding.
> test 1 2 [add(2,3)]
The sum is: 8
// This seams reasonable. It adds 2 and 3, so the first argument of the command is '1 2 5'...
// That sums to 8... Fair enough.
> test 1 2 %[add(2,3)%]
The sum is: 8
// Okay, that's weird. Surely it would error, right? I mean, it's trying to sum a non-number...
// Or at the very LEAST it would result in '3' if it ignored the code.
// But it's almost as if.. It evaluated it. Hmm. Lets do a little test.
> test 1 2 %[pemit(%%#,name(me))%]
Object
The sum is: 3
// Hmm, that's rather bad. We just got it to evaluate some
// arbitrary softcode by escaping it. But why does this happen?
> test 1 2 %[set(WIZARD_POWERED_OBJECT,ATTRIBUTE:MALICIOUS CODE)%]
// If I happen to be a wizard this could be done. And it wouldn't be good.

This definitely deserves further investigation.

All the functions are well defined.. In fact, the only two questionable ones would be setq() and iter(). add() and mid() are pretty well defined. But setq() doesn't ever touch %0, so it doesn't have a chance to evaluate the code. It must be iter(). But iter() just replaces the double-sharps (##) with the code and executes the second argument with that substitution in place... Wait, there it is! The third item in the list is a piece of code. As long as it doesn't have a space it will be performed.

So iter() will cause and expression like this: setq(0,add(%q0,MALICIOUS_CODE_HERE))...

This is quite bad. ESPECIALLY because this is being evaluated from the object's point of view, and the malicious code gets whatever powers the object has. What can we do about this?

Well, we can secure the lists argument (first argument) of an iter() call with secure() or even escape it with escape() but this seems like a poor solution. Escape() allows other functions to potentially run the code. Secure() seems like it bastardizes the list by removing out potentially needed characters.

For example...

> think secure(tester %(example%))
teser example
> think escape(tester %(example%))
\tester (example)

Note that older versions of escape() and secure() didn't work properly, still leaving security holes around. In any case, relying on secure() and escape() to make all your softcode secure is like playing with fire. You are going to get burnt, eventually. It's only a matter of when. When one needs to use escape() or secure(), the situation tends to be rather complex and fragile, the worst time to rely on quick-hack solutions like secure() and escape().

So we don't use iter(). We use map(). Map() uses a second attribute and passes the argument as %0. This means no direct substitution is done. Unfortunately, map() does have a disadvantage. The attribute map() uses to pass each element through doesn't have access to all the stack values (%0-%9) the calling code does, but you can use registers (%q0-%q9 and possibly others) to pass information. While seemingly icky, ulocal() can be used to avoid potential complications.
Intelligently modularizing code (which maps() help encourage) allows one to use snippets of code over and over, so this really shouldn't be looked at as a chore, but rather a time saver.

To reiterate (no pun intended): Iter() evaluates items in its first argument.

So, we've covered how to make sure that happens, but when do we not need to worry about it? If the user will never be allowed to control directly what first argument of iter() (and similar functions) such that an undesired piece of softcode can be entered, then iter() will work. It may not be as efficient because it does try to evaluate each argument again, however.

For example, iterating over a list of #dbrefs is alright. So are names, as they have no insecure characters (-usually-. Check this for your specific MU* version. When in doubt, be paraoid!)

A brief note on the s() function: Don't use it unless you know what you're doing. There's not much it's good for, and there's a loooot it's bad for. Same goes for eval().

In general, anything that forces evaluation of its parameters is dangerous. Also note that this really isn't so bad if the ability to use sideeffects is disabled for the executing object. Unfortunately, most MU*s don't support this. If your MU* does support this, make sure that objects hafe sideeffect functions disabled by default to increase security. Again, abide by the 'let-an-object-have-no-more-powers-than-it-needs' rule.

Anyhow, back to the fun...

> @set Object=halt
Set.
// The object isn't safe. Lets make sure people don't abuse it.
> @set object=visual
Set.
// Also, lets show the code to people so they don't make the same mistake.
> ex object/cmd_test
CMD_TEST: $test *:@pemit %#=setq(0,0)[mid(iter(%0,setq(0,add(%q0,##))),0,0)]The sum is: %q0

WAIT WAIT WAIT!?!? HOLD THE PHONE!?
Remember what I said about visual objects? BAD!?

So, what's the problem, you might wonder? It's just a lousy command that doesn't even work right. Well, the bug in this command is a function. And is it turns out, the function part can still be evaluated with u(). For example:

> think u(object/cmd_test,%[pemit(%%#%,name(me))%])
Object
$test *:@pemit #1748=setq(0,0)The sum is: 0
// Yeah, so it doesn't look pretty, but it still allows execution of arbitrary softcode.
> @set object=!visual
// Okay, so maybe that wasn't such a hot idea. This way normal players can't abuse it...
// But royalty and people with see_all still can. If you don't trust your staff,
// make SURE you have no code of this type around.

The unfortunate truth is that it's too easy and too convinient to make these kind of mistakes. These are petty coding mistakes. Lets look at some higher-level errors.

> @set object=destroy_ok
Set.
> @destroy Object
Object has left.
// Besides, it was crap, anyhow. :) And a danger.

> @create Vote Object
Vote Object has arrived.
Vote Object created as object #4
// I was talking about something similar earlier, right?
> &cmd_vote vote object=$vote *:@switch get(%#/votes)=<[v(VOTEMAX)],{@trigger %!/TR_VOTEFOR=%#,%0},@pemit %#=You have used up all your votes.
Set.
> &tr_votefor vote object=@switch isdbref(num(*%1))=1,{&votes %0=add(get(%0/votes),1);@pemit %0=You voted for [name(num(*%1))]!;@pemit num(*%1)=name(%0) just voted for you!},@pemit %0=No such player.
Set.
> &VOTEMAX vote object=2
Set.
> WHO
Player Name On For Idle Room Cmds Host
Ian 2d 03:31 0s #0 1041 snoopy.radiantstar.org
1 Player logged in, 17 record, no maximum.
> vote Ian
You voted for Ian!
Ian just voted for you!
> vote Ian
You voted for Ian!
Ian just voted for you!
> vote Ian
You have used up all your votes.
> @wipe me/votes
Wiped.
> @fo me={vote Ian;vote Ian;vote Ian;vote Ian}
You voted for Ian!
Ian just voted for you!
You voted for Ian!
Ian just voted for you!
You voted for Ian!
Ian just voted for you!
You voted for Ian!
Ian just voted for you!
> vote Ian
You have used up all your votes.
> ex me/votes
VOTES: 4
// Hmm, we don't want to really allow that.
// We can fix this using semaphores. This is very unhappy looking, though...
// If I lose you here, skip down to the explanation of semaphores.
> &cmd_vote vote object=$vote *:@wait %!={@switch get(%#/votes)=<[v(VOTEMAX)],{@trigger %!/TR_VOTEFOR=%#,%0},{@pemit %#=You have used up all your votes.;@notify %!}}
Set.
> &tr_votefor vote object=@switch isdbref(num(*%1))=1,{&votes %0=add(get(%0/votes),1);@pemit %0=You voted for [name(num(*%1))]!;@pemit num(*%1)=name(%0) just voted for you!;@notify %!},{@pemit %0=No such player.;@notify %#}
Set.
> @startup vote object=@drain %!;@notify %!
Set.
// This should give itself a kickstart when the MU* is booted up.
> @trigger vote object/startup
// Kickstart it manually so we can use it now without restarting.
> @reboot
Game: Restart by Ian.
Game: Your connection will pause, but will remain connected. Please wait...
Game: New server image successfully loaded.
// This is RhostMUSH. I'll explain why RhostMUSH is good later.
// Gotta love that ending message. "New server image successfully loaded."
// Sounds all techish and stuff. :)
> @wipe me/votes
Wiped.
// Okay, lets start anew, here.
> @dolist lnum(10)=vote Ian
You voted for Ian!
Ian just voted for you!
You voted for Ian!
Ian just voted for you!
You have used up all your votes.
You have used up all your votes.
You have used up all your votes.
You have used up all your votes.
You have used up all your votes.
You have used up all your votes.
You have used up all your votes.
You have used up all your votes.
// Ah, yes. There we are. Now I should probably explain a bit about semaphores.

Semaphores are a way of queueing things. PennMUSH has an excellent helpfile on semaphores (which I recommend for an extended explanation) as well as many other topics. I'm only going to explain the little bit of semaphores I've used here.

We can say '@wait me=' to signify we want to wait until we've been notified that we can continue. Note that this notification can occur _before_ we come to the '@wait me=' command (or @wait %!= in my code). This is because semaphores use a counter to know when the next item queued up can be executed. In our startup we initially set this counter to 0 by first draining everything in the queue such that the counter is reset to zero. Then we @notify ourselves which sets the counter to -1.

So now we execute the vote command. First thing it does is wait for ourselves to be notified. And we have been. Before we were ever called we were notified. So we continue executing code.. While this code is executing, we try to run the code again. It seems, however, that currently the semaphores status is 0, so it waits. Once the first execution of the code is done, it notifies the object running it, and the next thing to be executed is triggered.

Note that we MUST have a @drain at the end of every branch, and we shouldn't drain before we're done executing all our commands. This means you have to be careful about where you put your @drain. This does seem to be rather annoying, however. This whole thing can be avoided by sticking to mostly functional programming, but sometimes you just can't only use functional programming. *sigh*

Again, I stress that the PennMUSH helpfile text on semaphores is very good. Read it. You'll actually understand them that way. :)

Commands really are icky in MU*s. But as you've seen, functions can be icky, as well. In general, this MU* coding isn't happy stuff. It is a lot like trying to represent a completely unambiguous idea in very natural language like English. (Not like French. French isn't as natural as English, IMHO, but I'm probably biased, being that I only speak English.) And that's what MU*s are supposed to be good at. Anyhow, this is really just a stolen idea from someone else (partially)...

ENOUGH with the philosophy of MU*s... Back to this security junk.

// Now since we've got this wonderful flawless(?) semaphore-using object
// that does everything right....
> ex vote object/*
CMD_VOTE: $vote *:@wait %!={@switch get(%#/votes)=<[v(VOTEMAX)],{@trigger %!/TR_VOTEFOR=%#,%0},{@pemit %#=You have used up all your votes.;@notify %!}}
TR_VOTEFOR: @switch isdbref(num(*%1))=1,{&votes %0=add(get(%0/votes),1);@pemit %0=You voted for [name(num(*%1))]!;@pemit num(*%1)=name(%0) just voted for you!;@notify %!},{@pemit %0=No such player.;@notify %#}
VOTEMAX: 2
Startup: @drain %!;@notify %!
Semaphore: -1
// Ugly as hell, isn't it? Gotta love softcode! :)
// Actually, this should be secure, now. Of course, if it isn't,
// this manual's integrity is in serious in jepordy.
> @set vote object=destroy_ok
> @destroy vote object
Vote Object has left.
// My, do I feel destructive today...

> @create Bla
Bla has arrived.
Bla created as object #5
> &cmd_test bla=$test *:@switch first(art(%0))=a,@pemit %#=Cycle commencing.,@pemit %#=Cycle started.;@pemit %#=The cycle for %0 is complete.
Set.
> test Wibble
The cycle for Wibble is complete.
Cycle commencing.
// Hmm, that's odd. It's in the wrong order!
// Lets see if both outcomes of the switch do that...
> test A-bomb
The cycle for A-bomb is complete.
Cycle starting.
// Yea, it does it again. Lets see, we trace through the code...
// Ah, yes. There it is. The @switch and the last @pemit get
// entered in the same queue... but we want the last @pemit to delay
// until the code in the @switch has completed.
> &cmd_test bla=$test *:@switch first(art(%0))=a,@pemit %#=Cycle commencing.,@pemit %#=Cycle started.;@fo me={@pemit %#=The cycle for %0 is complete.}
Set.
// Hey, now, that's pretty efficient. This does nothing more than wait a queue and execute the code inside the brackets. Nifty.
> test Wibble
Cycle commencing.
The cycle for Wibble is complete.
// Not bad.
// Lets humour ourselves and try the good ol' escaped-code trick.
// Please note that this is NOT a comprehensive way to test for security bugs.
> test Wibble%[pemit(*Ian%,name(me))%]
// Note how I'm using *Ian instead of %%#. That's because depending on where we are,
// the %%# might not be evaluated right. If in doubt, use *YOURNAME.
Cycle commencing.
Bla
The cycle for Wibble is complete.
// Uhoh! Okay, okay, don't panic.. Well, lets see here..
// The problem was AFTER the "Cycle commencing" line...
// So, it must be someplace in:
// @fo me={@pemit %#=The cycle for %0 is complete.}
// Lets see, what does @fo do again.. @force, right... It forces an object
// to execute the code inside the brackets.. Ah, but it treats that like a
// parameter, so it evalutes the %0 down into our little snippet of code and
// then forces itself to evaluate that. Hmm. That's bad. We should probably
// replace that with something.. But what? How about @wait 0?
&cmd_test bla=$test *:@switch first(art(%0))=a,@pemit %#=Cycle commencing.,@pemit %#=Cycle started.;@wait 0={@pemit %#=The cycle for %0 is complete.}
> test Wibble%[pemit(*Ian%,name(me))%]
Cycle commencing.
The cycle for Wibble[pemit(*Ian,name(me))] is complete.
// Much better. Still, this doesn't feel like 'clean' coding. I personally
// would avoid this kind of stuff at all reasonable costs.
// I'm covering this because I don't really know what kind of coder
// you are. :) Besides, now and then when you have to use it, it's important
// to know.

Here are three very common mistakes made in code that can have very serious side effects if intentionally or unintentionally misused. These are simple examples compared to most code. When other code gets tangled in with the mess of mistakes the probability of a serious security hazard goes up exponentially. Secure coding doesn't always mean the most efficient coding. Many times it means reasonably efficient code that is maintainable (readable) to the point that it is possible to secure it, despite what some people (like me) try to tell you. (I just being a hypocrite. :)

Secure coding is about creatively avoiding potential disasters. Remember, anything you're unsure of to any degree is just that much more likely to be insecure (or incorrect). Code it safely. Make it maintainable. (Yes, an oxymoron for softcode, I know.) If it works, works securely, and works correctly, there's no problem. Don't sweat the efficiency of it (unless it's horribly slow), as today's computers more than make up for any slowness (unless you run MU*s on 386's and 68k Macs like me). Secure coding isn't beating yourself up, it's about maintaining awareness.

Hmm, I'm beginning to repeat myself...
Must be time for...

Section III: And you thought this stuff was just for geeks

This stuff doesn't apply only to geeks. It applies to ALL special-powered players. Because of this I've decided to devote section #3 to you non-geek people. Geeks, you read this, too.

All MU*ers (and especially staffers) should be aquainted with flags. If you don't know what a flag does that is talked about in this section, look it up in the help file. Again, PennMUSH's helpfile is great, but PennMUSH's flags aren't the same as all MU*s.

Controlling access is a big issue. Access to whatever you can think of. Objects, rooms, commands, etc... We'll concern ourselves with rooms right now. Rooms which should be restricted to certain classes of players. Typically these classes are 'unregistered' and 'normal' players. For everyone else it usually doesn't matter, as they're staff.

Unregistered players certainly shouldn't really be able to teleport to anyplace in particular. If they absolutely _must_ code a +teleport command. There's no other reasonably secure and convinient way of doing. To prevent them from using @teleport the FIXED flag should be set on them. This should be a default player flag on the MU* (Meaning that the flag gets set on every new player). When they register successfully their fixed flag will be removed.

Of course, @teleport isn't the only way to get around. (Geeks, listen up, here.) Setting onself sticky, @linking oneself to an object, and then being dropped will force you to go home (which is now to that object). This is exactly why the STICKY flag should be restricted or the ABODE flag should be restricted. Personally, I recommend both, as ABODE isn't really that useful. If ABODE really needs to be set on a room, it should go through the say-so of a staffer, anyhow.

Please be sure to give unregistered people no quota and very few monitary units (Pennies by default). I recommend around 20 pennies at best. We don't want any malicious code being executed.

When a player is registered they aquire more pennies and a small amount of quota. Do not over-quota people, as quota should be earned, not free. I've seen too many large databases full of crap. I'll bring this up again later.

Normal players have the ability to go home and @teleport. Restrict JUMP_OK, however, as you don't want players just setting any old object JUMP_OK. When I say 'restrict' I usually mean 'make it so only staff can set it.' Consult your coder on this. (This can usually be done with a configuration option, easily findable in your help or wizhelp file). Actually, I should be a bit more cautious. Don't restrict JUMP_OK if you don't care about people jumping around to various places on the grid. Remember, unregistered players have already been locked down. (You DID lock them down, right?)

A particularly popular flag seems to be LINK_OK for some reason. Particularly in staff areas. If you do this, be sure to have your resident coder link-lock the room/object for security.

LINK_OK is not your friend. Nor is ABODE. Nor is JUMP_OK. Be weary of these flags.

A large part of security is preventing the ability to find things to exploit. One such way is to make the potentially exploitable items unrecognizable (within reason). For example, your master room should be named something like "OOC Place" instead of "Master Room." I mean, come on, "Master Room" is a dead give-away. But since your master room will be set unfindable (to prevent finding objects within it) and the objects within the master room will be set unfindable (because we want redundant security), objects inside the master room need not be called obscure things.

UNDER NO CIRCUMSTANCES have a link to the master room. People can @tel there. Also make sure your master room is locked adaquately, and all the objects are masked adaquately. Do NOT set the master room or any of the objects in it dark, as they immediately become suspicious to anyone looking at the flags. I personally like to find a nice high-#dbref room to make my master room, making it very very hard to find. On most MU*s the master room seems to be within the range of #2-#50. Very very easy to find.

< ENGAGE SIMULATION MODE >
Royalty pages: Hey, there, boss.
You paged Royalty with 'Yo.'
Royalty pages: This object I'm codin' up for you needs some wizard powers. Would you please set object #1053 wizard? Just log onto #1 and do it. @set #1053=wizard
< SUSPEND SIMULATION MODE >

Hmm, what do you do? Well, giving an object that Royalty owns wizard powers would essentially give Royalty wizard powers. This isn't a good thing. He's set Royalty for a reason. Furthermore, as I said in section one, don't do this. Lets play out both choices.

< RESUME WITH SITUATION ASSESSMENT #1 >
You paged Royalty with 'I'm not that stupid, dude.'
Royalty pages: Oh. Um... Okay, I quit.
You paged Royalty with 'Fine.'
Royalty has been made a normal player and has been removed from the staff list.
< TERMINATE SITUATION ASSESSMENT >

Sounds like the guy was just a power-hungry twink. /* DO NOT CLIP THIS NEXT SENTENCE */ Never trust a coder.

< RESUME WITH SITUATION ASSESSMENT #2 >
You paged Royalty with 'Okay. There you go.'
You have been gently shown out the door by Royalty.
*** TinyMU* Disconnected ***
MAIL: Mailbox purged.
% Connection to wrld closed by foreign host.
---- World wrld ----
Welcome to TinyMU*
------------------------------------------------------------------------------
"connect <name> <password>" connects you to an existing character.
"create <name> <password>" creates a new character.
"WHO" tells you who is logged in to the game (case sensitive).
"QUIT" exits the game and saves your character.
"news" informs you about recent program changes and items of interest.
"help" gives help on the commands, "help commands" for a list.
------------------------------------------------------------------------------
connect Ian password
Either that player does not exist, or has a different password.
connect #1 password
< TERMINATE SITUATION ASSESSMENT >

The prick changed your password! And somehow screwed your #1 character! *sigh* Perhaps it would be best to be cautious. As I've said earlier, and I'll say it again, have a head coder you trust and can defer to when these types of situations pop up. If someone requests for access, defer them to your head coder who should know who is on the level and who isn't.

Note that almost all exits should be set UNFINDABLE. This makes it so people can't remotely trace where they go and find paths into places they shouldn't be. This also makes it so they can't find dark exits that lead to places they shouldn't be. I personally recommend having your coder add UNFINDABLE to the default exit flags list.

Most of what was mentioned should be done by your coder (which will be discussed more for the coder later in this document), but it is still important to fix these things should you run across them.

Remember: If you don't know how things should be set, then whether you're trying to or not, you're opening security holes.

As the last part of this section, let me reiterate three key points from Section I:

The staff factor
The one permenate aspect of running a successful MU* is politics. Politics will never increase the value of a MU*. It is an emergent destructive force in human communities. Keep a close eye on political activity. With the suggestions above most things should be running themselves enough to allow you to do this.

The coder factor
These are the people who hold all the keys, all the knowledge, and all the code. Be weary of these people. They're flakes. Believe me, I know. I've been a coder on several places. :) Expect these people to be the first to screw up your MU* intentionally. And treat them very well. This is the one problem left in MU* security that cannot be solved unless you're the coder. The coder can always leave back doors. The key is to have a head coder whom you trust and whom you know will do the job right.

And last but not least...

The twink factor
While this really isn't security related, this falls under 'annoyance-control.' Be sure you have code in place that allows you to block these people with ease. This includes booting mechanisms, sitelock enforcing, and auto-player-smiting. I typically use a softcoded booting mechanism that nukes them if their site matches and "intelligently" modifies the sitelock table. This has some problems that will be discussed later along with problems in guest systems.

Section IV: Upping the ante

This is a fairly short but effective section on securing your MU* via yet another security by obscurity technique. It is hard to screw with what you can't find, isn't it? This is why I've been boasting the UNFINDABLE flag and locks.

We can do better than this. Let's up the ante.

All we have to do is make it so the normal and unregistered player(s) can't find out information about far away objects. Unfortunately, not all MU* types support this. This enhances security an incredible amount, however. I definitely recommend this.

Because this is too short of a section, let me add in some miscallaneous notes:

Some general warnings: Descs are always readable by default. This means one can u() them. If one has a coded @desc, be sure it uses nothing exploitable. There's no hiding it.

One might wish to disable pemitting to far away objects and players, as well as remote description viewing. After all, we don't want twinks spoofing people constantly. It can get obnoxious; and most people consider NOSPOOF obnoxious for some reason.

Section V: All out war!

Security isn't just about having secure code. It's about making sure people don't do bad things. Including avoiding resource slamming. Above I talked about what it is. Here I talk about how to prevent it, but not how it is done. How it is done I will leave for the jerks who actually do it to decide. I need not spread effective slamming code around to the MU* equivilent of script kiddies. I've already given them ideas to play with up above (but it requires intelligence, something they most likely lack).

Make sure your FUNCTION_INVOCATION_LIMIT or FUNCTION_RECURSION_LIMIT isn't set unreasonably high. In fact, I wouldn't go above the defaults. If you do, you're treading on thin ice.

Some MU* variants support optional anti-slamming mechanisms. Among these are killing executing commands that have eaten up too much CPU time and limiting the number of attributes that can be written on an object. Both these features are available in RhostMUSH. The CPU time feature is offered in MUSH3.1 (but I haven't seen it actually work anywheres nearly as well as RhostMUSH's implimentation).

The next step is to boot them before they do anything malicious, or better yet, not allow them to connect. Some handy commands for sitelocking people would be in order. Note that, again, RhostMUSH's implimentation of sitelocking is the best among all the MU*s. Also, make an auto-booter with more abstract matching of their Site attributes. This will be your backup. :)

The guest system problem... Many guest systems are coded such that everytime a new guest logs on, a new guest character is made. The old ones don't get deleted quickly enough should someone decide to log into and out of many guest characters. This problem has been resolved in RhostMUSH and recently in MUX2.1 (thanks to Zenty). MUSH3.1 and PennMUSH are still prone to this attack.

Note my intent is not to plug RhostMUSH or sway anyone to use it.

Most MU*ers are stupid. They connect from their home machine. Including the twinks. If someone is realling bugging you, log their IP, and contact their ISP stating that this IP at this time logged into your game and disrupted your service unlawfully. They should have logs of which user was using that IP during that time.

These measures should usually be sufficient to take care of the problem people.

Section VI: Managing it all

I've talked about making sure links are set and the like. The key to being able to efficiently and sanely managing it all is to code tools that allow you to do such conviniently and easily. Basically, a scanner is in order. A scanner that scans the entire database for objects of certain types that should have certain flags. If they don't and they're not on the exceptions list, the command complains about them so further investigation will ensue. Note the semi-modular scanner code at the end of this document. This is not a full version of a security scanner, but it is a place to begin with.

Keeping notes is also essential. Most code is so large with so many attributes and usually with small names to keep the code size smaller that it becomes impossible to recognize what is exactly going on. It isn't a bad idea to have a staff-restricted +codehelp (or something similar) that just stores notes on all your systems and particularly the way the MU* is layed out for security.

Section VII: Misc security risks

The following is a list of security risks from various platforms that I've become aware of.

TinyMUXx.x and TinyMUSH 3.x have a default behavior for @function (user-defined functions) that causes any user-defined functions defined with @function to be executed with the powers of the person calling the function. A PennMUSH developer pointed out that this was bad for back-end security, in that the following could happen: If one gains access to an object with a function and modify the function, all they have to do is wait for a wizard powered object/player to use the function and their obnoxious piece of code gets executed.
Since the PennMUSH developer was a jackass about it, I'm not inclined to talk too much about how to avoid the problem. Either protect the objects with the functions very well or use @function/privileged.

Section IIX: Summary

Be careful with JUMP_OK, ABODE, and LINK_OK

Use locks skillfully

UNFINDABLE is your friend (mostly)

Name the master room something unsuspicious

Secure the master room and all its objects by setting them unfindable

Watch out for places where sideeffect functions could be used

NEVER let powered players have objects set visual

NEVER set objects/rooms wizard. (Again, ignore this if you use PennMUSH)

Non-semaphored @switch testing is asking for trouble

Only give objects/rooms/players powers they need. No more.

Quotas are meant to be earned. Don't give quotas away.

Slim down your maximum allowed number of commands allowed in the command queue if you don't need that many.

Keep an eye on the politics developing in your game.

Never trust the coders.

Always have sitelocking and booting code handy.

Disable long-fingers type access for normal players

Disable pemitting to far away objects and players

Disable viewing far away descriptions

Add UNFINDABLE to the list of default flags for exits

Disable sideeffect functions for newly created objects/exits/rooms/players

Use code to make maintaining your MU* easier

Have staff keep out an eye for things that don't look right

Be paranoid!

Section IX: A few snippets of softcode

@@ Note that this next bit of code is a scanner for various things.
@@ Unlike @search eval or similar, it doesn't monopolize the CPU and make
@@ the whole MU* wait.

@@ NOTE: data_num_objects needs to be set to the number of objects in the MU*.
@@ (as shown by @stats)

@create Scanner
&cmd_scan Scanner=$scan *=*:@dolist lnum(ceil(fdiv(v(data_num_objects),100)))=think setq(0,mul(##,100))[u(fn_scangroup,%0,%1)]
&fn_scangroup Scanner=iter(lnum(100),u(fn_%0,#[add(%q0,##)],%1))
&FN_OF Scanner=ifelse(orflags(%0,%1),pemit(%#,%0 [name(%0)] [flags(%0)]),)
&FN_LO Scanner=ifelse(member(loc(%0),%1),pemit(%#,%0 [name(%0)] [flags(%0)]),)
&FN_WH Scanner=ifelse(member(where(%0),%1),pemit(%#,%0 [name(%0)] [flags(%0)]),)
&FN_AF Scanner=ifelse(andflags(%0,%1),pemit(%#,%0 [name(%0)] [flags(%0)]),)
&FN_DC Scanner=ifelse(strmatch(flags(%0),*D*c*),pemit(%#,%0 [name(%0)] [flags(%0)]),)

@@ OF: Orflags
@@ LO: Location
@@ WH: Where
@@ AF: Andflags
@@ DC: Dark and connected. (works on some MU*s)

@@ Any decent coder can infer from this code how to add his/her own extensions. Note while this may not be as efficient as @search, it certainly does interact nicer with the MU*. This can also be adapted to make a census type collector.

@@ Unfortunately, this code can be abused as well. Please do not abuse this code.

@@ I'll add more snippets of code here as I generate them. There's nothing really worth generating that's not already trival to create.

@@ Note that other things to code would be: A scanner for potentially dangerous functions (iter(), eval(), s(), and sideeffect functions) and function additions to the Scanner (such as object-is-visual-and-owned-by-staff).

@@ If you do decide to code any of these and wish to share the code, pleast contact me so that I may add it to this document. (ian@hfs.dhs.org)

Section X: Credits

Original Author:

Ian Elliot (Ian@BrazilMUX)



The following people helped revise this document:

Moe@ChicagoMUSH
(Spelling and consistancy checking)