There are those in the Metaplace community - those who inhabit the dank, dark avatar-less backwaters - who strive to know as much as possible about inner workings of the system. Those who make clients, who probe for weaknesses in closed-source scripts, who still mourn the loss of
OutputToUser(), and get very grumpy at undocumented changes.
When server update notes are released they don't just look at new features thinking '
how could I use this in a game?' but also '
how could this be abused?'
One such update that started a spate of devious thinking was the inclusion of the UiPickable() function and its pending support in the client. This function would allow a UI element (and any child elements) to ignore mouse-clicks, making them fall through to whatever was underneath. Now this has some obviously helpful uses such as with avatar name tags; these are pieces of UI attached to objects in worldspace that can sometimes get in the way of mouse-clicks intended for movement. But it could also be easily abused, and it didn't take long for Crwth to think of a way to use it, with the intended target being the user-to-user coin gifting.
Coin gifting (which has only recently been removed again in preparation for testing "Real Money" systems) was an in-world functionality that allowed one player to give some of their fake Metaplace play-dough money to another player. This was all safely locked away in the Secure Features module, which relies on closed-source scripts for its security. Closed-source scripts,
if used correctly, can be very good at keeping your code and data locked up tight. Although this hadn't always been the case - there were times when they were not so closed and exposed their data to anyone! - by the time coin gifting came around they were pretty secure.
If you wanted to exploit coin gifting, for example trying to get someone to give you more than they wanted to, then you couldn't do it by trying to change the data within the secured gifting 'session'. But coin gifting did have a weakness; it required user input. The user needed to enter the amount of coins they wished to transfer and then again approve that transfer on a confirmation balance window. The user has to trust that the User Interface displayed to them is accurate and that they will only be transferring the amount they expect to.
Crwth's idea was to exploit this trust by placing his own UI on top of the 'real' UI. The fake UI would use UiPickable() to allow the button clicks to pass down to the real UI, which was using different values than what the user expected. But it wouldn't even have to be similar UI; a user could think they were playing a game in someone's world, but really they were transferring their coins to the world owner.
At the time this was discussed there was still no support for UiPickable in the client, so it couldn't be tested, but that didn't stop me from trying to implement something similar.
This vector of attack relied on the user actually wanting to perform a coin transfer and conning the user into transferring an unwanted amount by making subtle tweaks to the actual coin transfer UI itself.
On to the gory details:
A coin transfer was initiated from the context menu on a user; you would click the user you wanted to give coins to and select the option from the menu that popped up. This started a session from the
mpsec_start_coin_gift trigger. Since it used a trigger you could write a script that also had a version of this trigger, attached to the player template, so you could know when a transfer session had started and which user was the target. This on its own doesn't do you much good; while you could block the actual session from starting by preventing other implementations of that trigger from firing, you couldn't really put your own fake transfer in its place, as only the 'Secure Features' script has the privileges to perform an actual transfer. But it did allow your script to know when to try begin exploiting.
Since my script (or any likely attacking script) was attached after the 'Secure Features' script, my version of the trigger fired first. By this point there was no session and no UI to try and meddle with. So I used a SendTo() to fire my own custom trigger but with a delay of 10ms (it would actually be about 30ms on average in practice due to the time it takes to set up a new execution frame). By the time this trigger actually fires the coin transfer UI will have been built, but in order to do anything to this UI you need to know what its ID numbers are.
Each UI creation function returns a User Interface ID which can be used to manipulate that UI element after creation, such as attaching, deleting or changing values. In order to use these values you have to manually store them as they are not publicly accessible from anywhere else. So how can you get your hands on the IDs for already created UI that you did not make?
The IDs are created sequentially; if an element you just made has an ID of 15 you can be reasonably sure that the element created immediately before it has an ID 14. In our case we are trying to look for the UI almost immediately after it was created, before it has even had a chance to be sent to the client. If you waited too long there could be a chance that some other innocent UI was created in the interval and therefore spoil your attack.
I manually examined the MetaMarkup tags that are sent from the server to the clients and counted how many UI elements had been created for the coin transfer. Then in the first line of my custom trigger I immediately create a new UiElement and get its ID. I then subtracted the expected number elements for the coin transfer UI to get to its parent element. Alternatively you could just count back far enough to the individual child element you want to alter, such as the text field and the button, but going back to the parent demonstrates two weaknesses for the price of one.
I could now use the newly acquired parent ID and use it with the UiFindWindow() function, which takes a parent id and a target element name. So I used that to find the text field
Code: MetaScript
local uiId = UiElement(0,'my_window_finder', 0,0)
uiId = uiId - 9
local text_field = UiFindWindow(uiId,'amount_input')
This is the valid text field where the user enters the amount to be transferred. But instead of allowing the user to input their value here I made it invisible and changed the value to my own
Code: MetaScript
UiVisible(text_field,0)
UiText(text_field, '1337')
This will set up the session to transfer 1337 coins no matter what value the user inputs. But as of now the user has nowhere to enter a value, so I have to create a new text field that looks just like the old one.
Code: MetaScript
local submit = UiFindWindow(ui,'yes_button')
local text_field_bg = UiFindWindow(ui,'amount_bg')
local new_field = UiTextField(text_field_bg,'hack_field',10,7,145,15, 255,255,255, 0,0,0,0, 'mpsec_coin_gift '..user_id..' 1337', 100 )
UiButtonField(submit, new_field)
This finds the background element that was created for the real text field and submit button. The new field is then attached to the background in the correct place and its command has my hacked value appended to it, this is just in case the user tries to submit by hitting the enter key in the field rather than clicking on the submit button.
The new field is also attached to the button (I can't quite remember why I did this, and I can't test it any more to find out. I may have been trying to see what value the user entered to use it later on).
When the
mpsec_coin_gift command was sent, it now used the 1337 value for the transfer, but before doing so it generated a confirmation screen that showed the users current coin balance, the amount to be transferred and then the resultant coin balance. This was a bit tricky, as at this point the displayed transfer value was 1337, which the user would surely notice as not being the value they entered. I would need to again grab that new UI and change it, but to what? At the time (as I recall) intercepting the command with the user entered value proved a failure so you'd just have to wing it, changing the value back to the default 100 and hope the user saw what they wanted to see.
Or this is where a bit of social engineering could come in. You could only ask a user to transfer you the value that would ultimately be displayed on the confirmation screen. So you say "send me 500 coins", they input 500, 500 is then displayed on the confirm window, but they are actually sending you 5,000.
Or there is the
real engineering way. Don't hardcode in a value but set up up a new command that only you use and then use that to prime the value to the used. You agree a value to be transferred, then before they get to sent it you quickly fire off a command with that value so that it is used in the display.
The only wrinkle in these methods is that they both fail to alter the last bit of information, the resultant balance. While you can alter a value in a UI element you can not extract a current value, and a users coin balance is considered privileged information so you can't access it. You just have to hope that no one in their right mind is
actually going to add up the values to see if the balances match.
I tried this attack once and only once... on the Metaplace developer who made the coin transfer code. I got him to try and send me 100 coins from within my world, but the value that popped over his head as the transfer finally went through was 1337!
Some may think that detailing all of this now in public is just going to help the exploiters, giving them a way to con the innocent Metaplace user. But I take the view that you can not know how to create a robust secure application unless you know the potential ways in which it can be exploited. After this was demonstrated, the coin transferring code was altered to help prevent this kind of attack. But it shouldn't be just the Metaplace developers who know how it could happen, everyone on Metaplace is a potential content developer. All builders and scripts should know those kinds of exploits (which is clearly why I am writing it on a site nobody reads!).
My as yet unreleased PayPal module faced the same problem and I used a similar method to combat it. This attack relies on someone else knowing how many UI element IDs to count back to in order to find the correct elements to alter, and also optionally the names of the elements used. This can be combated by casting doubts on those assumptions.
Code: MetaScript
--utility functions
function rand_ui_ele()
local r = math.ceil( ((math.random()*100)/10) )
for 1,r do
UiElement(0,'fake ui',0,0)
end
end
function rand_ui_name()
return MD5(os.time()..':'..math.random())
end
--build our UI
rand_ui_ele()
local uiId = UiElement(0,'myParentUI'..rand_ui_name(),0,0)
rand_ui_ele()
UiLabel(uiId,'myTextLabel'..rand_ui_name(),0,0,'can you find me?!')
rand_ui_ele()
UiAttachUser(self,uiId)
This code creates a random number of UI element IDs before, in between and after each newly created piece of UI, which prevents someone from simply counting back a known number of elements. It also appends random strings to the ends of UI element names so that they can not be accessed that way.
This likely won't prevent all future attacks on UI, but it is a good place to start.
As for UiPickable()... well, we never did get support for that in the client (perhaps due to this demonstration?), which is a shame since there are cases where it could be useful, even safe. For example I see no reason why it shouldn't be allowed for UI elements that are attached to objects, in worldspace. Those will always be behind the screen level UI and so have no chance to interfere. It is also probably where it is more useful.