Mitigating Code Repurposing Attacks

Mitigating Code Repurposing Attacks

Rate This
  • Comments 3

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.


So our goal is to do one or more -- always think of "defence in depth" --  of the following things:

  1. Eliminate our dependence on information inside the document
  2. Ensure that the information in the document is trustworthy
  3. Check that the information inside the document falls within a "reasonable" range
  4. Provide cues to the user about the purpose of the code they are running
  5. Elicit explicit user consent before performing potentially harmful actions
  6. 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.


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...
    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.")
      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

    If (foundSignature = False) Then
      MessageBox.Show("This document's signature is missing.")
      ' Take appropriate action here...
    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")
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"
  <xs:element name="SimpleTest">
        <xs:element name="FirstName" type="xs:string" />
        <xs:element name="LastName" type="xs:string" />

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
  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
  End Sub

  Private Sub LoadXml()
    ' Gather the filename through a custom dialog, etc.
    Dim filename As String = "C:\temp\export.xml"

    ' 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
    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()
  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
      End If
    ' 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
    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 & _
    path = ThisWorkbook.path

    If (InStr(path, expectedPath) <> 1) Then
      MsgBox "This is a Budget document. It should be " & _
        "stored in the 'Budgets' folder."
    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.

  • btw, forgot to mention besides really liking this blog entry -- think it's extremely well written; very useful guidelines and great code repurposing mitigation steps... i also like the shortcut key tips very much. Do please keep them coming. Thanks a lot Peter for all the great blogs and sharing your vast and in-depth knowledge on security, coding, etc... Really appreciate it.
Page 1 of 1 (3 items)