Mitigating Code Repurposing Attacks
As I mentioned in a previous
blog, there are some pretty creative (and destructive) things people can do
with your code if you're not careful. Just as a kitchen knife can be used to
cut cheese or to kill someone, so your code can be used to increase
productivity or wreak digital havoc.
It's not all bad news
though. There are several things you can do to help mitigate attacks based on
code repurposing, although unfortunately there is no panacea and many of these
techniques will not be applicable to all given scenarios. A much shorter
article that mentions some key mitigating factors can be found in the VSTO
documentation online. But if you've got a spare chunk of time, by all means
read on.
Thanks to Siew Moi, Mike
Howard, and the Office security folks for giving this a once-over before
posting :-)
Root Cause
The root cause of
repurposing comes from trusting input
data. Michael Howard has a lot to say about this in Chapter 10 of Writing Secure Code 2nd
Edition (aptly titled All Input is
Evil), but in repurposing attacks it's not just your code that is trusting
potentially bad data, it's the end-user, too!
With a typical
web-based application, checking your inputs isn't so hard since you mostly need
to worry about things like form fields, query strings, and HTTP headers. (You
may also need to worry about any external systems you connect to, such as a
database, if that figures into your threat model). In most cases, you can
assume that the server your application is being hosted on is trustworthy,
along with any other data on it such as configuration files, web page content,
and so on.
The problem with
Office-based development is that your code is hosted inside a very dynamic,
very mobile container (the document), and in general you cannot assume that any part of that document's content is
trustworthy.
Not the warning text
you put in BIG BOLD WARNING TEXT at
the top of the document.
Not the names or
positions of the controls on the document.
Not the hidden
worksheets where you store database connection strings.
Not the
configuration settings you put inside a hidden region with white text on a
white background in 1 point WingDings font.
Nothing.
So our goal is to do
one or more -- always think of "defence in depth" --Â of the following things:
- Eliminate our dependence on information
inside the document
- Ensure that the information in the
document is trustworthy
- Check that the information inside the
document falls within a "reasonable" range
- Provide cues to the user about the
purpose of the code they are running
- Elicit explicit user consent before
performing potentially harmful actions
- Probably some more things here!
So let's look at
these in turn.
Eliminate dependence on untrustworthy information
In my previous blog,
I used the example of a "Format my Drive" document that contained
instructions for formatting your hard drive, along with Yes and No buttons that
invoked the appropriate code. In this sample, the code doesn't make any decisions based on inputs (it has none), but
the user does. The code assumes that
the user will always be presented with adequate contextual information (eg,
"clicking this button will format your drive!"), but this is not the
case. The user has no idea that there is no
link between the text surrounding the buttons and the actions the buttons
take; they think if the text says "Download unlimited free MP3s now!"
then that's what the button will do.
Oops!
So even in this
case, the code implicitly trusts its "inputs" (the user invoking the
code) and it should not be. Something as dangerous as a "Format my
Drive" control should provide additional feedback to the user about the
actions they are about to perform, as noted in one of the next sections of this
blog.
It's also a good
idea to think about the things you can
depend on. Resources on the local machine, such as files or registry keys,
should in general be trustworthy (see note below!). So should other servers
that your code communicates with, as long as they are under your control. And
of course you can trust your own code not to have been tampered with, as long
as it is signed or living in a secure location.
NOTE: I said above that you can trust the local machine. This assumes we
are trying to mitigate against repurposing attacks where one user sends a
malformed document to another user in an attempt to get them to do something harmful to themselves. You
cannot trust resources on the client
computer in other scenarios, such as when you are building a server-side
solution, because then a hostile client could be used to subvert your security
system. Servers need to protect themselves from malicious clients, and clients
need to protect themselves from malicious servers. Different scenarios call for
different threat models and different mitigation strategies.
So if your solution
needs to persist data such as user preferences or other snippets of information
that you come to rely on, you can write it to the registry or to a config file
(not in %ProgramFiles%, but in %UserProfile%) or use some other
mechanism to persist it. Just don't persist it in the document, because then
you'll never be able to read it out again without worrying about the contents.
Contacting servers
is a bit trickier. You might want to contact, say, a trusted web service on http://myserver/ to download information, but if
you hard-code that URL into your solution then you will have trouble when you
need to move the service to another machine. The obvious answer is to stick the
URL of the server inside the document... but you can't do that because it's not
trustworthy! This is where you could do something like an Active Directory
lookup to figure out which server to contact. I've been told it's possible, but
I'm not an AD expert so you'll have to figure that one out on your own ;-).
Ensure the information is trustworthy
Let's say you
absolutely have to rely on the information inside the document, for whatever
reason. Now you have to make sure the content in the document is as trustworthy
as your code itself, which means severely limiting what can be done with the
document.
Two properties of
code that makes it "easy" to secure in the CLR are that:
i)
You can
sign the code, which can be used to detect any modifications made to it
ii)
You can
keep it in a fixed location, which can be used to ensure nobody can modify /
overwrite it
Unfortunately,
neither of these two properties apply to documents in the general sense. The
whole point of having document-based solutions is so that you can modify them
(thereby making signatures pretty useless) and that you can send them around to
people, copy them to your hard drive, and so on (thereby making location pretty
useless). Nevertheless, depending on the scenario you may be able to do one of
these things in your solutions.
Signing a document
in Office 2003 is pretty easy -- go to Tools
-> Options -> Security and click on the Digital Signatures button, where you
can select one of your certificates and add your signature to the document.
Once the document is signed, no-one can tamper with it in any way without
breaking the signature, but as I mentioned in my Old
Fashioned Security blog, there's a big problem with signing content:
- Unless the recipient expects the
document to be signed (and knows how to verify it), it doesn't really help
you. An attacker will just remove the signature altogether and go on their
wicked way
So either you have
to educate all your users about how to inspect digital signatures, or you can
do something about it yourself.
In Word, you can use
the object model inside your code to check if the active document contains a
signature, and if so if it is from the right person (ie, you) and if it is
valid. Inside your startup code, you can do something similar to the following
to ensure that your code is running inside a genuine document that you created
(if Rob had finished the
WordML to HTML transform by now, this would be in glorious Technicolor,
but alas he hasn't so it's not):
Private Sub
ThisDocument_Open() Handles ThisDocument.Open
Dim signature As Office.Signature
Dim foundSignature As Boolean = False
If
(ThisDocument.Signatures.Count < 1) Then
MessageBox.Show("This document
is not signed.")
' Take
appropriate action here...
ThisDocument.Close()
End If
For Each signature In
ThisDocument.Signatures
' Bail out early
on invalid signatures
If
((signature.IsValid = False) Or _
(signature.IsCertificateRevoked
= True)) Then
MessageBox.Show("This
document's signature is bad.")
ThisDocument.Close()
End If
' Add the
appropriate strings here
If
((signature.Signer = "ACME Corp") And _
(signature.Issuer =
"Verisign")) Then
'
You could also check the sign date if you want
MessageBox.Show("This
document is signed correctly.")
foundSignature = True
Exit
For
End If
Next
If
(foundSignature = False) Then
MessageBox.Show("This
document's signature is missing.")
' Take
appropriate action here...
ThisDocument.Close()
End If
End Sub
There's a bit of a
chicken-and-egg problem here for both VBA and VSTO developers here, but for
different reasons. With VBA, because the code is included in the document's
signature you have to sign the document after
you have written and tested your code. This could require you to build, test,
and re-sign your document many times, or it could require you to have some kind
of mode where the document ignores invalid signatures while in development
(remember, this cannot be a flag
stored in the document itself, but it could be a registry key you use to
disable signature checking on your dev box). With VSTO, the assembly is built
and signed independently from the document, but since the custom properties
which link to the assembly form part of the signature, it is not possible to
move the assembly after the document is signed, so that must be done as the
final step before officially releasing the document.
This
"problem" of having the code (or the link to the code) be part of the
signature actually helps us prevent some attacks as well. Imagine, as outlined
in my previous blog, that you have a Budget document and an HR document. Both documents
are signed, and both assemblies are signed, but by some horribly bad
coincidence, it's possible to point the HR document at the Budget code (or
vice-versa) and do some damage to the document recipient. This will not work,
since swapping out the code (or the link to the code) will break the signature.
The only real attack possible here is if you can take over the URL at which the
linked code lives, in which case you could replace the HR assembly with the
Budget assembly. So you need to make sure your servers are protected from
having unauthorised people uploading content!
One potential
problem here is that the Signature
object in Word only tells you the name of the signer and the authority that
issues that certificate, but not the rest of the trust chain up to the root
authority. It is possible (but unlikely) to have two signatures, both created
by "Bob Smith" and issued by "ACME Corp Authority", but for
them to be two different Bob Smiths issued from two different ACME Corp Authorities.
(Thankfully you still have to trust the root authority, so it's not as if any
old hacker could create such a hierarchy in order to fool your code, but it's
something to keep in mind). Siew Moi has written a great
article on Word signatures, and it includes a download that has some more
code to play with.
Unfortunately, there
is no programmatic support for checking digital signatures in Excel, so you
cannot use this technique. Also, you may not have the money to buy a
certificate from Verisign or Thawte or one of the other big vendors, and
you may not have your own PKI servers with which to issue your own
certificates. In this case, you can "tie" your document to a specific
location, such as a trusted (and read-only) share on a server, or to a specific
"install" location on the user's machine. (This is similar to how the
SiteLock
feature works for ActiveX controls). At startup, you can check that the
document's location is where it is supposed to be, and take appropriate actions
(such as closing the document) if the document comes from a suspicious
location. This has the problem of baking URLs or other locations into your
code, which means it's impossible to ever move the solution if you need to, so
you may want to use a Registry key or Active Directory property or some other
external (but still trusted) mechanism to figure out if the document is hosted
in a secure location:
Private Sub
ThisDocument_Open() Handles ThisDocument.Open
If (ThisDocument.FullName
<> _
"\\appserver\secure\myapp.doc") Then
MessageBox.Show("This document
is not secure")
ThisDocument.Close()
End If
End Sub
Obviously if you
require your document to be signed, that means the user can never actually
modify it and then save it. And if you require it to be in a specific location,
that means the user can't put it on their desktop or mail it to a friend.
That's kind of a problem for most documents, since they are designed to be
modified and saved and moved around. (I show a slightly less draconian version
of this in the next section).
One way around this
is to have your "document" act like a mini application hosted inside
Word or Excel, and have it load and save its own "documents". This is
made super-easy by the XML support in Word and Excel 2003, where you can import
or export the XML content of a document to a separate file without actually
saving all the markup and other stuff that makes up your
"application". As long as your solution lends itself well to having only
the data saved and loaded into your static template, you should be good to go
-- just provide your own "Open" and "Save" mechanisms
inside your document that load and save XML files that are pumped right into
the mapped XML structure of your document.
Here's some sample
code for doing it in Excel -- Word is harder since you can't automatically
import XML into a Word document; you need to load the DOM yourself and insert
text into each node of the document. For this simple solution, you can create a
spreadsheet with pretty formatting, etc. and then do the following:
1)
Map an
XML Schema into the document (a really basic one follows if you need it)
2)
Add a
single Command Button from the Controls
Toolbox toolbar. Open the Properties window for it and give it a name of cmdLoad. Delete the Caption so it is blank.
3)
(Optional)
Digitally sign the document with a certificate to prove that you can do this
without modifying the content
Here's a simple schema you can map to a spreadsheet. Save it
with an extension of .XSD
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="simple"
targetNamespace="http://tempuri.org/simple.xsd"
elementFormDefault="qualified"
xmlns="http://tempuri.org/simple.xsd"
xmlns:mstns="http://tempuri.org/simple.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="SimpleTest">
<xs:complexType>
<xs:sequence>
<xs:element name="FirstName" type="xs:string" />
<xs:element name="LastName" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
And here's the code you can use - just dump it into the default codespit
of a VSTO solution, nuking the original ThisWorkbook_Open
handler:
Private WithEvents
cmdLoad As MSForms.CommandButton
' Called when the workbook is opened.
Private Sub ThisWorkbook_Open() Handles
ThisWorkbook.Open
InitialiseUI()
End Sub
Private Sub InitialiseUI()
cmdLoad = FindControl("cmdLoad")
If (cmdLoad Is Nothing) Then
MsgBox("Document is
corrupt!")
End If
cmdLoad.Caption = "Load XML"
cmdLoad.AutoSize = True
End Sub
Private Sub cmdLoad_Click() Handles
cmdLoad.Click
LoadXml()
End Sub
Private Sub LoadXml()
' Gather the filename through
a custom dialog, etc.
Dim filename As String =
"C:\temp\export.xml"
ThisWorkbook.XmlMaps(1).Import(filename)
' Don't prompt user to save
ThisWorkbook.Saved = True
End Sub
Private Sub SaveXml()
' Gather the filename through
a custom dialog, etc.
Dim filename As String =
"C:\temp\export.xml"
ThisWorkbook.XmlMaps(1).Export(filename, True)
' Don't prompt user to save
ThisWorkbook.Saved = True
End Sub
' Handle the Save event to just
save the data
Private Sub ThisWorkbook_BeforeSave( _
ByVal SaveAsUI As Boolean, ByRef Cancel As Boolean) _
Handles
ThisWorkbook.BeforeSave
SaveXml()
Cancel = False
End Sub
Another way to
ensure that the information in the document is trustworthy without resorting to
signatures or site-locking your document is to ensure that you create it all
yourself, from within your code, and that you make it easily understandable by
the user. For example, if you have a button on a Word document that will format
the hard drive when it is clicked, don't just give the button a caption of Yes and rely on the surrounding text to
explain what it does. A more descriptive caption, such as Format Drive C: will be much better.
And don't just set
the Caption of the button using the
property grid in Word or Excel, since that value can be changed by the attacker
and is not stored as part of your VBA or .NET assembly. Even if your code is
signed, the attacker can change that property and not invalidate the signature
(it will invalidate the signature of
the document, if it has one, but it won't
invalidate the signature of the code). Instead, you should explicitly set the
caption of your button during your initialisation code, so that it always says
what you want it to say. This is what I did above in the sample Excel code.
Don't rely on
automatically generating warning text into the document though (eg, injecting
"This will format your drive!"
in big red letters at the start of the document), since no matter what you do
the bad guys will probably figure out a way of obscuring it (hiding the region,
placing a white bitmap over the top of the text, etc.).
If you need more UI
than you can get by putting captions on your buttons, then you need to
implement some kind of form (see below) or <gasp> build an ActiveX
control where you can display all your information in a reliable manner. If you
don't have to have your control hosted directly on the document surface, a
SmartDocument solution offers a really good alternative because the attacker
has no way to influence what appears in the Task Pane. You can be sure that any
warning text or other UI you place in the Task Pane will be there when the user
runs the solution.
Another way to avoid
"untrustworthy" callers in VBA is to make your methods Private (VSTO does not have this
problem since there is no way through the Office UI to invoke managed code). If
you are placing controls on the document surface, make sure you use ActiveX
controls (from the "Control Toolbox" toolbar), not Forms controls
(from the "Forms" toolbar). Adding an event handler to an ActiveX
control creates a private method that cannot be invoked by any means other than
firing the event on the named control, whereas the macros you assign to Forms
controls are just public subroutines that can be hooked up to any old event
source. Also, since Forms controls aren't programmable, you can't
programmatically change their content (as described above).
If you are building CommandBars or menu items, don't use
the Office UI to hookup event handlers, since again they must be public and
hence repurposable (swap the "Save" event handler for the
"Delete" one...). Instead you should build up the CommandBar programmatically (so you can
set the text and images on each control as outlined above) and declare all your
controls inside your solution WithEvents
so you can handle the events with a private handler, not a public sub. There is
some sample code for VSTO in this
article, and the sample solutions that ship with the product also show how
to create menus and toolbars. Some sample VBA code is below:
Private WithEvents
cmdSave As CommandBarButton
Private WithEvents
cmdDelete As CommandBarButton
Private Sub
Document_Open()
CreateCommandBar
End Sub
Private Sub CreateCommandBar()
Dim bar As CommandBar
Dim i As Integer
Dim oldContext As
Object
On Error GoTo Handler
' Delete any existing instances of the bar
' Must loop backwards since we're deleting items
For i = CommandBars.Count
To 1 Step -1
Set bar = CommandBars(i)
If
((bar.BuiltIn = False) And _
(bar.Name = "My
Bar")) Then
bar.Delete
End If
Next
' Create our own temporary bar
Set bar =
CommandBars.Add("My Bar", , , True)
bar.Visible = True
' Add new buttons
Set cmdSave =
bar.Controls.Add(msoControlButton)
cmdSave.Caption = "Save"
cmdSave.Style = msoButtonCaption
Set cmdDelete =
bar.Controls.Add(msoControlButton)
cmdDelete.Caption = "Delete"
cmdDelete.Style = msoButtonCaption
' Exit the sub
Return
Handler:
MsgBox "CommandBar not created:" & _
vbNewLine & _
Err.Description, _
vbOKOnly Or vbInformation,
"Error"
End Sub
Private Sub cmdSave_Click( _
ByVal Ctrl As Office.CommandBarButton, _
CancelDefault As Boolean)
MsgBox "Save clicked!"
End Sub
Private Sub cmdDelete_Click( _
ByVal Ctrl As Office.CommandBarButton, _
CancelDefault As Boolean)
MsgBox "Delete Clicked!"
End Sub
Check the information for "reasonableness"
Sometimes you really
do need to rely on the information in the document. In WordBlogX, for example,
I persist all the information about
a blog entry (content, unique ID, creation date, etc) into a document, and then
next time you load that document I suck it all back out again.
Oh No! I'm breaking all my own rules!
Well, here's where you need to take a step back and consider
how the data is used (what's the worst thing that could happen?) and what kinds
of reasonable limits you can place on the data inside the document. For
example, if you have a budget spreadsheet that accepts a value for the current
year's maximum budget, it probably shouldn't be negative and it probably
shouldn't be more than ONE
MILLION DOLLARS (or whatever is appropriate for your organisation). If
there's simply no valid reason for such values, you should check for them and
reject them -- building correct, robust software gets you half way to building
secure software.
Same goes for (eg) embedded URLs -- if you have to embed a URL
in the document, but it should only be one of 3 known servers, make sure it
really is one of those servers. One common mistake in web programming is to use
a <select> object to provide
the user with a "fixed" list of options in the browser, and then to
blindly accept whatever value the client returns in the server code. This is
broken because users can send ANY values to your web server; they're not limited
to the options shown by the browser UI. Office applications are no different
(depending on the scenarios) -- just because you provide the user the
convenience of 3 or 4 different options, it doesn't mean they can't inject
their own.
Do you store dates in the document? Does it make sense to have
a date outside of a relatively small range, such as today +/- a month? If not,
then check for it and throw away the bad values. Are you expecting a range of
10 cells in Excel? Then don't just "loop until done" -- explicitly
check for the 10 cells and if there are more or less then take some defensive
action. Unfortunately in these cases developers (including Microsoft
developers) often try and "help" the user by trying really hard to
work-around all the errors the user makes, inferring things from partial
information, and so on. But whilst this can help users in some situations, it
usually comes back around to bite you when an attacker figures out how to fool
your oh-so-smart algorithms into doing something unexpected.
In the case of WordBlogX, there's no real validation I can do
on the document content (it's just arbitrary text) but I do check that the GUID
is in a valid format and that the persisted date and time values are in a valid
format. I could do extra work to check the GUID against some trusted store (eg,
the Registry) but that complicates the solution and then I'd have a bunch of
essentially useless GUIDs hanging around. Even if Rob gets the WordBlogX
setup working, he could in theory delete the registry data when the program
was uninstalled, but since it's probably considered "user data" we
couldn't even do that. So the registry keys would live forever, and it
would be impossible to move documents from one machine to another without also
moving the regkeys. I should probably make sure that the Modified date is on or
after the Created date, and that neither of them are in the future or the
distant past.
I take the approach that I can be quite lax with WordBlogX in
this instance because it's not protecting anything too valuable, other than my
credentials to the web server. About the worst thing someone could do (other
than steal my login) would be to send me a document with the GUID and creation
date of an existing entry, and fool me into "updating" (ie,
clobbering) the old content instead of creating a new entry. Or they could put
some nasty text in the Description
field and hide it, hoping I won't see it. Neither of those are likely to happen
since I don't accept blog entries from anyone else, and even if I did there's a
confirmation step before posting where I would get to double-check the content.
But your solutions will have different threat models and thus require different
levels of attention.
Regular expressions are a really cool way of validating input,
and Michael has some things to say about them in his book <plug
notagain="oh yes">Writing Secure Code</plug>.
You can weed out all kinds of nasty things using a few expressions, and that
will help you stop not only security bugs, but general code quality issues,
too.
One other really cool thing that you get for "free"
with VSTO is that the
document itself is subject to security checks. I don't worry too much about
WordBlogX because it's simply not possible under a default security policy for
someone to send me an e-mail attachment that links to the code, or for someone
to point me to a web site that opens a document and links to the code. Unless
the document is copied to my local machine, it cannot access any assemblies,
even if they are trusted.
Unfortunately VBA does not provide this degree of protection,
but you could attempt to mimic it by parsing the document's location during
your startup routines to see if the URL is in an expected location or not. This
is a slightly different approach from locking the document to a very specific
location as mentioned above (which lets you "guarantee" that the
content is trustworthy). Instead this enables you to flag "suspect"
uses of the document (eg, hosted off external web sites) while still enabling
users some degree of freedom about where they save the files.
Rather than looking for "http:" at the start of the
string (or some other "black list" approach), you should use a
"white list" approach where you know the "good" locations
and look explicitly for them. Why should you do this? Well, for one thing, if
the attacker learns what "bad" patterns you are looking for, they'll
just pick a new pattern that isn't caught by your code. There's only a small,
finite number of "good" locations that the average document should be
in, but there are an infinite number of "bad" ones and you can't
check for them all. Also if you just do the naive thing and check for a
protocol like http:// you'll get
confused by the Temporary Internet Files folder and treat things that really
came from the web as if they came from the local machine (bad bad bad!)
As an example, if budget spreadsheets should only ever be
stored in the Budgets folder, then
you should make sure that the path to the document begins with that text. Of
course this means that the code won't work outside of an e-mail attachment, but
maybe that's not such a bad thing.
'
Note this is VBA, not VB .NET :-)
' Makes sure the file is in the right folder
Sub CheckDocumentFolder()
Dim path As String
Dim expectedPath As String
expectedPath = Application.DefaultFilePath & _
"\Budgets\"
path = ThisWorkbook.path
If (InStr(path,
expectedPath) <> 1) Then
MsgBox "This is a Budget document. It
should be " & _
"stored in the 'Budgets'
folder."
ThisWorkbook.Close
End If
End Sub
NOTE: If it were
possible to call MapUrlToZone
from VBA I'd also recommend you do that, because you should never really attempt
canonicalisation or parsing yourself, as outlined in <plug shouldgetkickback="true">Writing Secure Code</plug>,
chapter 11. Nevertheless, using a white list is better than nothing. If someone
knows of a way to get MapUrlToZone
to work from VBA, that would be really cool info to share!
Provide cues to the user
A strategy that you can use to help alert the user of
impending doom is to provide them with some (subtle) cues that they are using
your application and not some innocuous document. Returning to the mis-matched
Budget vs. HR spreadsheet scenario, if the HR code displayed some kind of a
splash-screen upon loading then a user who believed they were opening a Budget
spreadsheet would become suspicious and presumably shutdown the document and contact
their administrator.
Of course relying on a splash-screen alone is not a good idea,
since the user may never see it depending on how they are interacting with the
computer. It's just something else you can throw out there to make it even
harder for people to accidentally shoot themselves in the foot.
If you have other forms of non-spoofable UI, they would be
good places to post some warning messages or other cues as to the purpose of
your application, but as I mentioned above this is hard to do unless you are
building a SmartDocument or you have a custom ActiveX control which you
completely own.
If you show a form or some other UI as part of your solution,
ensure that the user can garner enough context from the information you show to
make good decisions, even in the face of malformed document content. Returning
to our favourite "format hard drive example", let's say there was a
confirmation dialog that popped up (see next section) asking the user if they
wanted to continue or not. If the message box simply asks "Continue? [Yes]
[No]" then the user has no context other than what is presented in the
document itself, which could be anything at all (especially if you didn't use
the other strategies, such as using descriptive captions and initialising them
through code at load time). So make sure you give the user enough information
to make an informed choice, but don't overload them too much such that their
eyes glaze over and they learn to ignore your warnings.
Get consent from the user
You've done everything
you can to help prevent your code from being repurposed, but at the end of the
day you have to send some sensitive information to a URL stored in the document
or perform some other kind of operation that you're not entirely comfortable
with. Whaddayoudo? (Yes that
link will get old eventually).
Well, it's probably
not a bad idea to ask the user. Try not to ask them in a really obscure,
technical manner -- unless your user base is technical, of course -- but in
plain English (or the language of your choice :-)). Describe the badness that
could happen if this code were being used incorrectly, taking into account the
other mitigations you have employed. Speak in general terms if the specifics
are too technical, but don't over-simplify the gravity of the situation. Having
professional staff on hand to help with error messages, confirmation messages,
and other UI text is very helpful.
This section is really
quite general and doesn't apply only to code repurposing mitigation; it's just
good application design. If users are empowered to make informed choices,
they're more likely to make good choices.
One of my pet peeves
in this area is people who use MessageBox
calls with the "[OK] [Cancel]" buttons, but then they ask a Yes / No
question. Or even worse, they ask an ambiguous question, or put more than one
decision point in the dialog, or use double negatives. What's up with that?! I
can partially forgive it in DHTML programming, where window.confirm
only allows for OK and Cancel, but even there all you need to do is re-phrase
the prompt so it makes sense. I guess that in many cases they're just trying to
side-step the need to build their own dialog and try to hack their requirements
into MessageBox, when really they should just crack open the forms designer and
do their users a favour.
Users are generally
confused enough about computers and security already; you don't need to make it
worse by asking silly things like "Do you want to upload data or rollback
the transaction? [OK]
[Cancel]". Picking a bad default button is also a sign of poor design; you
should generally pick the "safe" default (usually No, Abort, or Cancel) so
that if the user blindly hits <Enter>
they aren't toast. And don't skimp on the Cancel
button either, if it makes sense to cancel the entire operation in addition to
assenting or dissenting to a particular question. You can set the buttons, the
icon, and the default button for both MsgBox
in VBA and MesssageBox.Show
in .NET, so there's no excuse for not doing it.
Finally, be consistent
with the way you word dialogs -- don't have one prompt saying:
- "Would
you like to save changes before closing? [Yes] [No] [Cancel]"
and another one
asking:
- "Would
you like to close without saving? [Yes] [No] [Cancel]"
Don't laugh, I've seen
applications like this.
Closing thoughts
It's not easy to build complex solutions that are both
easy-to-use and hard-to-repurpose, especially when you factor in user ignorance
or error. But as I mentioned earlier, it's a trade-off you have to make based
on the types of threats you think your solution might come up against, and the
consequences of someone successfully executing one of those threats.
If you're writing a macro that bolds the active paragraph in
Word, you probably don't have much to worry about. It's kind of hard to make it
bold anything other than the active paragraph, and even if you could get it to
do that, what's the damage? Nothing that can't be fixed with an Alt+Backspace (that's the old-fashioned
equivalent to Ctrl+Z, for those who
don't know).
But if you're building a spreadsheet for managing
multi-million-dollar portfolio accounts, or you deal with sensitive material
like HR records or legal contracts, you'd better be thinking of security.