ID:48967
 
Keywords: security, tutorial
There are many different ways to make your BYOND skins work how you want. Lots of people like to assign verbs to perform their interface commands. However, until recently, there was an undocumented and relatively unknown function known as client/Command(). This is another, and in my opinion, easier method to control your interface buttons.

For test purposes, let's make a new DM environment. For the sake of consistence, we'll name it "Test". In this new environment, we'll need a code file and an interface file.

The interface will consist of one output (set to default), one input (set to default) and four buttons. If any of you know Visual Basic, you were probably taught to name your controls after what they simulate. This is entirely up to you, but I tend to agree with this method.

Our four buttons will have the IDs "button_command1", "button_command2", "button_command3" and "button_command4". We'll also want to name them accordingly so we know what button we're pressing. Once you've done that, you'll hopefully have something that looks like this.

Now if you use verbs to control your buttons, chances are you'd edit your buttons command to contain the name of your verb. Here's where we get sneaky, we're not going to do that. All our buttons commands will be assigned in our code.

First things first, we need to assign a few definitions for our program. I don't know how many of you out there do this, but I find it helps for debugging things, and redesigning interface elements based on new windows. It is by no means necessary.

So what we're going to do now, is make definitions for our window ID and our button IDs.
//Our windows ID is default. So that's what we set our MAIN_WINDOW definition to.       
#define MAIN_WINDOW "default"

//Now we define our button IDs.
#define COMMAND1 "button_command1"
#define COMMAND2 "button_command2"
#define COMMAND3 "button_command3"
#define COMMAND4 "button_command4"


Now we've done that, we need to set a client variable, we'll name it "generated_id". This ID is going to be affixed to all the commands, so if a user types our command into the command line, it wont execute.

//So this is our generated_id. We aren't going to set it a value yet, we'll do that when the user logs in.
client/var/generated_id = null


Now we have a generated ID. But the problem is, we have no ID. The ID can be whatever you desire, but if we want to try and make it as secure as possible, I figure we'll use a hash. Who's likely to guess a randomly generated 32 character string?

So in order to use a hash, we'll have to write a proc to generate one.
proc
generate_hash(client/C)
/*
We want the hash to be fairly random. So we'll put the users ckey in there,
then the current time stamp, and just for fun, we'll add a random number between 1 and 100.
This should hopefully runin them when it comes time to guess our hash.
*/

C.generated_id = md5("[C.ckey][world.timeofday][rand(1,100)]")


Righto, no we have a generated_id variable for our client, and a proc to generate a hash for the user. Now it's time to put it all into action.

This will require another proc, what we're going to do now is, assign all our buttons their rightful commands. To keep in tone with the tutorial, we'll name their commands "command1" - "command4".

proc        
assign_commands(client/C)
/*
This is where our definitions come into play. In case we want to change a command ID later on
or rebuild a window without deleting our current. We can just change the definition value to match.

The first thing we want to do is generate our client's hash. You'll see why we do this here
instead of client/New() in a bit.
*/

generate_hash(C) //Generate Client Hash.
winset(C, "[MAIN_WINDOW].[COMMAND1]", "command=\"[C.generated_id].command1\"") //Assign a command for button 1
winset(C, "[MAIN_WINDOW].[COMMAND2]", "command=\"[C.generated_id].command2\"") //Assign a command for button 2
winset(C, "[MAIN_WINDOW].[COMMAND3]", "command=\"[C.generated_id].command3\"") //Assign a command for button 3
winset(C, "[MAIN_WINDOW].[COMMAND4]", "command=\"[C.generated_id].command4\"") //Assign a command for button 4


Right. To summarise what we've done so far: We've made our interface with a default output and input, and we've added four buttons with the ID's button_command1 - button_command4. Afterwards, we made a client variable called "generated_id" and wrote two procs, one to assign a hash to the clients id variable and another to call the generate_hash() proc and assign the commands to the buttons.

As you can see from the code above, we put the hash in the command before the actual command ".command1". This means, if a user was to try and execute the command, they'd have to know the hash first.

Now what we need to do to make that flourish, is call the assign_command() proc in client/New(). Let's do that now:
client
New()
assign_commands(src)
..()


This means the buttons will now have commands assigned to them. Because we didn't define the commands in the interface, the user doesn't know what those commands are, and can't execute them when we don't want them too. And if they do manage to discover the commands, they need to know their hash (which at the moment, will change every time they login), which is security step number two.

Now we need to make the commands actually do something. We're going to do this using client/Command(), as stated above. So let's write our client/Command() code.

client
Command(C as command_text)
/*
First thing we're going to do is check to see if the first thirty-two characters of the command they executed
is equal to our hash.
if(first 32 characters are equal to clients ID) execute this code
else echo the command.
That's how it's going to work.

Although a hash is always thirty-two characters, if you really want to keep them on their toes.
You could use several methods that don't sometimes include hashes, sometimes phrases.
If we use length() on the generated_id, we know its size. Making the possibilities limitless.
*/

if(copytext(C, 1, length(src.generated_id)+1) == src.generated_id)
/*
Now we're sure the command is a button, because the hash matches. Now we're going to switch()
the remainder of the string to execute the desired command.
If they did manage to guess the hash but not the command, nothing will happen.
We added two to the id length because in our assign proc, we separated the hash and the command with a period.
We will have to take this into account.
*/

switch(copytext(C, length(src.generated_id)+2))
if("command1") world << "You executed Command 1"
if("command2") world << "You executed Command 2"
if("command3") world << "You executed Command 3"
if("command4") world << "You executed Command 4"

else world << "[src]: [C]"
..()


Now let's look at the full script in its completed form.
//Window Definitions.
#define MAIN_WINDOW "default"

//Button Definitions.
#define COMMAND1 "button_command1"
#define COMMAND2 "button_command2"
#define COMMAND3 "button_command3"
#define COMMAND4 "button_command4"

client/var/generated_id = null

proc
generate_hash(client/C)
C.generated_id = md5("[C.ckey][world.timeofday][rand(1,100)]")

assign_commands(client/C)
generate_hash(C) //Generate Client Hash.
winset(C, "[MAIN_WINDOW].[COMMAND1]", "command=\"[C.generated_id].command1\"")
winset(C, "[MAIN_WINDOW].[COMMAND2]", "command=\"[C.generated_id].command2\"")
winset(C, "[MAIN_WINDOW].[COMMAND3]", "command=\"[C.generated_id].command3\"")
winset(C, "[MAIN_WINDOW].[COMMAND4]", "command=\"[C.generated_id].command4\"")

client
New()
assign_commands(src)
..()

Command(C as command_text)
if(copytext(C, 1, length(src.generated_id)+1) == src.generated_id)
switch(copytext(C, length(src.generated_id)+2))
if("command1") world << "You executed Command 1"
if("command2") world << "You executed Command 2"
if("command3") world << "You executed Command 3"
if("command4") world << "You executed Command 4"
else world << "[src]: [C]"
..()


That's lookin' mighty pretty. Now we have functional buttons, that are hashed and nearing the impossible mark to successfully guess. This means users can't execute our commands when we don't want them too. Bonus for us!

Now this script as is will work just how we want it to. But Alathon mentioned something to me previously when he and I had a discussion on this technique, and he suggested that we should regenerate the hash every time a command is executed. This will make it absolutely impossible for the user to break our security.

This part isn't necessary unless you really are that paranoid about people executing commands when you don't want them too.

This is the reason we don't generate the hash when the client logs in. Because where it's placed now, is where it'll have to be placed for the rest of this script to work out. So we just saved ourselves a meaningless proc call. Go us!

Anyway, to regenerate the hash every time the user executes a command, we simply recall the assign_commands() proc under the switch() block in client command. This will force a new hash to generate, and apply it to all the commands.

//Window Definitions.
#define MAIN_WINDOW "default"

//Button Definitions.
#define COMMAND1 "button_command1"
#define COMMAND2 "button_command2"
#define COMMAND3 "button_command3"
#define COMMAND4 "button_command4"

client/var/generated_id = null

proc
generate_hash(client/C)
C.generated_id = md5("[C.ckey][world.timeofday][rand(1,100)]")

assign_commands(client/C)
generate_hash(C) //Generate Client Hash.
winset(C, "[MAIN_WINDOW].[COMMAND1]", "command=\"[C.generated_id].command1\"")
winset(C, "[MAIN_WINDOW].[COMMAND2]", "command=\"[C.generated_id].command2\"")
winset(C, "[MAIN_WINDOW].[COMMAND3]", "command=\"[C.generated_id].command3\"")
winset(C, "[MAIN_WINDOW].[COMMAND4]", "command=\"[C.generated_id].command4\"")

client
New()
assign_commands(src)
..()

Command(C as command_text)
if(copytext(C, 1, length(src.generated_id)+1) == src.generated_id)
switch(copytext(C, length(src.generated_id)+2))
if("command1") world << "You executed Command 1"
if("command2") world << "You executed Command 2"
if("command3") world << "You executed Command 3"
if("command4") world << "You executed Command 4"
assign_commands(src) //Force another hash to generate when the command is finished executing.
else world << "[src]: [C]"
..()


You can download the full script for this tutorial: Here.
Excellent article; I never thought of using client/Command to secure the interface! Too bad .winset calls can't be stopped like this =/
"However, until recently, there was an undocumented and relatively unknown function known as client/Command(). "

I know what you're trying to say, but it sounds like the function doesn't exist anymore. It reads as "However, until recently, there was a... function known as client/Command".

Maybe Guy can fix it?
I see what you mean, that never occurred to me when I read over it.

But with the link to client/Command() there, I don't think it's that big of a deal.

If it does need editing, probably just something like: "However, there is a relatively unknown and up until recently, undocumented function known as client/Command()."
Jeff8500 wrote:
Excellent article; I never thought of using client/Command to secure the interface! Too bad .winset calls can't be stopped like this =/

I have no way to test this theory myself, because my computer is having a hissy fit and I can't use it for a while.

But IIRC, my script above appeared to not allow me to execute ".options" by typing it in the command line. If that is the case, I'm fairly sure that would mean it would kill .winset() as well.

If it doesn't, fear not, I think I have a method to do so, that'll deserve it's own article. (Incidentally, wouldn't client/control_freak disable .winset()? And if it does, but you still want your users to be able to edit the interface some what, there's always another method I can think of to do the job.)
Is this article about not allowing for custom interfaces per client?
CaptFalcon33035 wrote:
Is this article about not allowing for custom interfaces per client?

Not at all. The user is more than welcome to edit the interface. They'll just have to keep the button ID's the same (which they would have to do when editing an interface without this kind of security).
I hope you excuse the late input, but I just stumbled upon this article recently.

You are right that noone is likely to guess an md5 hash, though, as far as I understood it, you are wrong that this would leave the user with no means to break your security.
I confess (to my shame), that I have never sniffed on the TCP/IP packages of a single BYOND application yet, but given a normal client-server structure, it would only be logic to assume that upon (win)setting the buttons "command", the server sends the client an however encrypted string.

Sniff it, use the hardwired command and you're where the code tries to prevent you to go.

As for everything I know, client sided security can never work, since it is only security by obscurity and interfaces are handled client sided in DM (though they offer serversided setting).

I found your article and attempt to be excellent work though and it got me to think if a slightly different attempt would not save some precious bandwidth while even increasing security.

Assign the client a "state" variable for the server that precisely tells you where in the process each client is.

Now, set a simple hidden verb for each of the buttons and have a switch check the usr's state variable.

If the user is in the right point of your game for the verb/button to work, have it proceed, else just output "Unrecognized or inaccessible verb: <verb's name>"

By setting it to hidden, you ensure it does not expand in the input.
By checking server sided if the client may use it, you add a new level of security, since only a person with direct access to the server can now manipulate the progression of the button's effect.
And you save yourself (and the server) some winset calls.

Unless I'm completely dumb, this should work ;)

I hope you don't mind my two cent.
Using a packet sniffer in my example wouldn't do anyone much good, because they would have to execute a command in order to get the hash, correct?

The thing is, once they execute the command, the hash is changed. So they now have the hash for the previous command, but any new commands wont work with it.

Not to mention, people can get sneaky with their command input. I for instance like to use the prefix "/." to commands that are executed. If they that is missing, it's assumed to be just in putted text.
Tiberath wrote:
Using a packet sniffer in my example wouldn't do anyone much good, because they would have to execute a command in order to get the hash, correct?

Not as far as I understood how it works, no.

client
New()
assign_commands(src)
..()

Command(C as command_text)
(...)
switch(...)
if("command1") world << "You executed Command 1"
(...)
assign_commands(src) //Force another hash to generate when the command is finished executing.
else world << "[src]: [C]"
..()

You do have to set the command value on the button before it can be pressed, else pressing it would just trigger your "bad boy event".
In the present code, this is solved by assigning the command to the button on the clients New() event hook and after a button is successfully used.

So, as soon as a client connects, he can sniff and use the command, which is fast enough, as before he connected he could not make good use of it anyway ;)

Only way to prevent this would be to set it on the fly just before the client needs to be able to execute it, but that is exactly what my suggestion would do, only not by sending an hash, but by checking internally.


Tiberath wrote:
Not to mention, people can get sneaky with their command input. I for instance like to use the prefix "/." to commands that are executed. If they that is missing, it's assumed to be just in putted text.

If someone is smart enough to sniff a 256 bit hash, I'm sure she'd be smart enough to siff the additional pre and suffix special characters as well, or, well, you never know, but better calculate with the worst case.
If your variable is a client variable, which I would imagine it would have to be in order for clients to use it at different stages of the game, could they not use a Memory Editor to discover the value and change it manually? I hear stories of people altering the amount of gold they have in games with Memory Editors and what not. I don't know much about them, mainly because I never bothered to research it.

Also: If someone is smart enough to sniff a 256 bit hash, I'm sure she'd be smart enough to siff the additional pre and suffix special characters as well, or, well, you never know, but better calculate with the worst case.

That's all of ten people on BYOND. =)
Tiberath wrote:
If your variable is a client variable, which I would imagine it would have to be in order for clients to use it at different stages of the game, could they not use a Memory Editor to discover the value and change it manually? I hear stories of people altering the amount of gold they have in games with Memory Editors and what not. I don't know much about them, mainly because I never bothered to research it.

I hope you won't condem me now, but, when I was younger (and I use that as excuse here :p) I actually did play around with what was called Hex Editors back then.

They could edit the variable, or well, they could alter the RAM that is supposed to hold it. But (and here is the trick), that is no use for them.
If they are connected to a server as client, they can alter their own RAM, but not that of the server.
And if BYOND is only coded half decent (which I'm sure of), this will leave the "evil" player with no effect.

Only if they are hosting the server themself, then they can cause havoc.
But if they have the server at their fingertips, there is absolutely no means to grant absolute security.
What I have locally running in my CPU, using my RAM, I can edit, no matter what (given enough time and knowledge).

Tiberath wrote:
That's all of ten people on BYOND. =)

Hehe, maybe you'd be astonished *teases and smiles*
Astonished, possibly not. Anyone with the knowledge to do it, probably wouldn't bother doing so on BYOND Games. It's the same boat as the DMB Decompiler. Anyone with the knowledge to make one, wont release it.

With that said, stopping the average end-user from messing up your game is always a must. This method is one of an infinite plus one amount of methods people can use.

I'm not that concerned with the security of my game that it'll require absolute security system. If someone does decide to find a way to give themselves infinite ability via the interface. Then I'll simply remove that structure of the interface so the ability isn't reliant on it, ban the user and update the game.

Besides, anyone who uses the interface as a means to store user information, should probably be shot. The interface should be nothing more than a display tool, not a game dependency.

Besides, I have other methods up my sleeve as well, just none I've bothered to write a tutorial about. This is merely one of a planned series. =)

But I thank you for your input. Learning of faults in ones design is always a good thing, because it helps clean up that fault. But still, there's no way I'm ever going to use verbs for interface commands, ever. =)

(Actually, I very rarely use verbs at all. I just don't like them.)
If it were better for security one could probably make a similar interface onto their map or a second_hud.
Tiberath wrote:
CaptFalcon33035 wrote:
Is this article about not allowing for custom interfaces per client?

Not at all. The user is more than welcome to edit the interface. They'll just have to keep the button ID's the same (which they would have to do when editing an interface without this kind of security).