Working with Input Types and Validation Tools


The first step in making sure input does not lead to bad consequences is to identify all sources of input into your application. The types of input range from obvious user input to often-overlooked forms of input such as HTTP header information or application settings stored on disk. Identifying the sources of input is essential to being able to assess the security threat posed by each form of input—a topic that is presented in Chapter 15. Identifying all sources of input will also help you test whether your application effectively defends itself against all possible attacks you’ve identified. Testing for security is a topic presented in Chapter 9.

Direct User Input

Direct user input is the form of input most familiar to Visual Basic developers. From its inception, Visual Basic was designed to allow developers to quickly and easily create forms that solicit direct input from the user by means of controls such as text boxes, list boxes, check boxes, and option buttons.

Free-form input supported by controls such as text boxes, rich text format (RTF) boxes, and grid controls is the type of input, if left unchecked, that can do the most damage. Therefore, you should focus your efforts on validating all input from controls such as these.

Visual Basic .NET provides a set of tools—controls, classes, properties, methods, and events—to help validate your data. The tools available vary depending on whether you’re creating a Windows Forms or ASP.NET Web application. These tools offer a first line of defense against invalid user input. However, the level of protection offered by these tools depends on how the input is used. For example, if the input is not passed outside of the application, checking input at the source as the first line of defense might be sufficient—this is a case where the first line of defense serves as the last line of defense. If the input is stored and later used or is passed outside of the application, these tools might provide little or no protection—this is a case where there is a first line of defense but no last line of defense. You should design your applications to always have a mechanism in place to provide a last line of defense.

Validation Tools Available to Windows Forms Applications

Visual Basic .NET provides the following tools to help validate user input:

  • TextBox PasswordChar property You can use the PasswordChar property to mask user input and thereby prevent people lurking nearby from stealing sensitive input, such as passwords or personal identification (PIN) numbers.

  • TextBox MaxLength property The MaxLength property limits input to a fixed or reasonable size. If, for example, the last name field of a database is limited to 16 characters, set the MaxLength property of the text box representing the last name to 16.

  • TextBox CharacterCasing property CharacterCasing helps to format input. For example, if you store all product IDs in lowercase such as “produce4011” (representing bananas), you should accept only lowercase letters when users input the product ID.

  • Validating event This event helps you validate input for Windows Forms applications. The Validate event allows you to validate user input for a particular control, such as a text box, before allowing focus to move off the control.

  • ErrorProvider You can use this class to help streamline your user interface (UI). The ErrorProvider class signals the user that the input contained in the control is either missing or invalid. When a user hovers her mouse over the warning icon, the error message you assign to the ErrorProvider is displayed.

Validation Tools Available to ASP.NET Web Applications

ASP.NET supports controls—such as text boxes, check boxes, option buttons, drop-down lists, and grids—similar to those found in Windows Forms applications. As mentioned previously in the case for Windows Forms controls, you should focus on validating input for controls that support free-form input, such as the TextBox, HtmlTextBox, and the DataGrid (in edit mode) control.

ASP.NET provides a set of validator controls for validating data that a user enters on a Web page. Table 7-1 summarizes the controls available. The controls support validating data on both the client and the server. Client-side validation is a first-line-of-defense tool provided as an optimization. It allows user input to be validated before being sent to the server. If input is missing or invalid, the client-side code can catch the error, alert the user, and avoid an expensive call to the server that passes data the server will quite certainly reject anyway. Server-side validation validates the data before it’s permitted to be used by your code—provided you ensure server-side validation is running and check the validation results before using the incoming data (more on this later). You should always leave server-side validation enabled because data, although validated on the client, could be intercepted and compromised by an attacker en route to the server. Alternatively (and more likely), an attacker could side- step your client-side validation code by turning off script in the browser or by using a custom tool to post handcrafted HTTP requests—containing any sort of invalid data imaginable—back to your ASP.NET application. Server-side validation (if properly implemented) thwarts all these attempts, whereas client-side validation stops none of them—always use server-side validation.

Table 7-1: Validator Controls Available for ASP.NET

Validation Control

Purpose

RequiredFieldValidator

Checks to see that an input control contains input. The RequiredFieldValidator also allows you to set an InitialValue property so that you can initialize a control with a value such as “<Please enter your name>”. The input is considered invalid until the user changes the input from the initial value to something else.

CompareValidator

Checks to see whether the value in an input control matches a certain fixed value (such as 100), matches a particular type (such as Integer), or matches a value in a separate control.

RangeValidator

Checks to see whether the input is in a certain range. It works with a number of data types, including String, Integer, Decimal, Double, and DateTime.

RegularExpressionValidator

Provides general pattern-matching validation, which is quite powerful if you understand how to use regular expressions.

CustomValidator

Allows you to create your own custom validation logic. For example, if you have an input field on your ASP.NET Web page that requires as input a person’s eye color, you can write your own validation control to validate that the color entered is a value such as brown, blue, green, hazel, etc.

ValidationSummary

Provides a summary list of all validation errors found in a Web form. The errors are displayed in a message box, on the same Web page (within the ValidationSummary control), or both. You can choose from a number of list formats—for example, a bulleted list—to present the error information. Note that the validator controls listed previously in this table also can be used to display validation errors.

Note

You can use the validator controls to validate input for a Web form control such as a text box. When server-side validation is enabled, these validator controls serve as the last line of defense only if the only point of entry for the input being validated is through the input control. If input can sidestep the control, the server-side validation serves as an important checkpoint, but it might not be the last line of defense against malformed or malicious input. One example would be when a Web application passes nonvalidated input such as the Text property of a TextBox (whose contents are left unchecked) to another Web page in the same application as a parameter, such as a query string to that Web page. The input (the query string) passed to the second Web page must also be checked. As a defense-in-depth measure, you should add validation to both the first Web page, where the input is initially received, and to the second Web page, where the input is ultimately sent.

The presence of the validator controls listed in Table 7-1 isn’t necessarily sufficient to automatically protect input from being used in a harmful way on the server. You’ll need to add code to check the results of the validation performed by the validator controls—this requires a check of the IsValid property available on the Page object or validator control itself. In addition, you might need to add code to ensure the validation performed by the validator controls is run—this requires calling the Validate method available on the Page object or the validator control. For example, control validation is not run before the Page_Load event fires. If you have code within the Page_Load event that uses the Text property of a TextBox, the code might operate on invalid input, leading to unexpected behavior and possible intrusion by an attacker. To ensure that validation has been run, you can invoke the Validate method of a particular validator control or the Validate method of the Page object (that in turn runs the Validate method of all validator controls on the page). In addition, you must check the IsValid method (available on the Page object and validator control) to verify the data in the control (posted from the client) is valid before you can safely use the data. The following code demonstrates how to ensure within the Page_Load event the validation performed by the validator controls is run and the data is valid:

Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load

If Me.IsPostBack Then
’WARNING: The default value of the IsValid property is True even if
’ no validation has been done. Force validation by executing
’ the Validate method.
Me.Page.Validate()
If Me.Page.IsValid Then
Dim strValidatedText As String = TextBox1.Text
Else
’TODO: Show error page warning the user the input is invalid
End If
Else
’TODO: Page shown for first time (not post back). Check to see data
’ for all controls such as the Text property of TextBox is set to
’ its expected initial value.
End If
End Sub

Use the RegularExpressionValidator control

You can use the ASP.NET RegularExpressionValidator control to validate the input on the page where it is entered. Follow the steps below to learn how to use the RegularExpressionValidator control. The exercise uses the ScriptAttack sample application introduced in the previous chapter. You can find the project in the CH07_ValidatingInput\ScriptAttack\Start sample application directory.

  1. Open the ScriptAttack.Aspx form in design view.

  2. Add a RegularExpressionValidator control to the form.

  3. Set the ControlToValidate property to txtUserName.

  4. Set the ValidationExpression property to ^[A-Za-z]+$.

  5. Set the ErrorMessage property to “The name you entered is invalid”. The layout of the form should appear as follows:

    click to expand

  6. Press F5 to run, and enter Bogus’ OR UserName Like ‘%% for the user name to simulate an attempted SQL-injection attack, as demonstrated in the previous chapter.

The error will be displayed in red on the page as shown in Figure 7-1.

click to expand
Figure 7-1: The error displayed by the RegularExpressionValidator control

start sidebar
Web-Based Input Attacks and SSL

Don’t fall victim to the illusion that data sent over an SSL connection is completely safe. SSL (as presented in Chapter 5) does an outstanding job of protecting data sent from the client to a Web server over an HTTPS connection, ensuring the data doesn’t change and can’t be viewed en route. However, SSL is not designed to prevent potentially damaging data from reaching the server. SSL simply ensures that data, whether it be harmless or harmful to the server, reaches the Web server unchanged.

The way SSL protects data is analogous to the way a container ship protects containers being transported from one port to another. A container ship ensures, for the most part, that the containers (and their contents) will reach their intended destination undamaged. However, the personnel aboard the container ship might not know exactly what is being transported in each of the containers and might not be allowed to view the contents of the containers. Some containers might carry new cars, while other containers might contain stolen cars. Upon reaching the destination, the containers are offloaded from the ship at Customs before being sent to their final destination. The stolen cars within some of the containers might be discovered if a thorough inspection is conducted by the Customs agents (or they get lucky when performing a spot check), and the agents are able to identify the cars as being stolen.

The server application, like the job of the Customs agents just described, has the responsibility of interpreting the data and separating the good from the bad, the legitimate from the illegitimate, and the harmful from the harmless. Only the server application itself can determine what input is valid and what is not. If you fail to write code to validate the data received by the server application, you might be making a costly assumption that all input is well-formed and harmless. You must validate all input that arrives. You must especially validate input that comes in through indirect means, such as data that is obtained by your code through the Form, QueryString, Cookies, or Params collections of the Request object.

end sidebar

General Language Validation Tools

Visual Basic .NET provides you with a couple of tools that go beyond just validating direct user input: regular expressions and the Parse method used to convert from one data type to another.

Regular Expressions

If you are versed in Awk, Sed, or Perl, regular expressions are second nature to you. In fact, anytime you’re faced with a problem that requires validating, searching for, or replacing text, your first inclination is to whip out a regular expression to satisfy the task.

If you’re new to regular expressions, you might find the syntax terse, unreadable, and downright intimidating—with lots of punctuation and letters all crammed together. However, investing a bit of time to learn the basic operators can help you to write compact code that validates that string data is properly formed. For example, a specialty of regular expressions is its ability to validate—without line upon line of text parsing code—that a Social Security number, phone number, or person’s name is properly formatted. Table 7-2 lists regular expressions you might find useful in your code.

Table 7-2: Examples of Regular Expressions

Expression

Format

Regular Expression

Person’s full name

Alphabetic characters with only limited support for spaces, commas, periods, apostrophes, and hyphens

^[a-zA-Z]+ ([a-zA-Z])?(\')?([a-zA-Z]+)?(\-)?[a- zA-Z]+(\,)?(\ )?([a-zA-Z]+)?(\.)?$

Social Security number

###-##-####

\d{3}-\d{2}-\d{4}

U.S. phone number

(###) ###-####

((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}

U.S. Zip code

#####-####

\d{5}(-\d{4})? 

Parse Method

The Parse method is a general-purpose method available on most data types that enables you to convert data from one type to any other type. In the process of conversion, the Parse method validates that the source data can be converted to the destination data type. This section focuses on how you can use the Parse method to validate that a string contains a formatted number in the format— such as currency format—you expect. If, for example, you allow the user to input (into a free-form input control such as a text box) formatted data such as a date or monetary amount that includes symbols—such as the dollar sign ($), comma (,), and period (.)—you need a convenient way to convert that formatted string to a number. As a simple example, you can use the following code to convert a string containing $100 to the integer value of 100—and in the process, validate that the string format contains only a dollar sign ($) and integer amount (and no decimal or thousands separator).

‘Add Imports System.Globalization to the top of the code module
Dim intValue As Integer = _
Integer.Parse("$100", NumberStyles.AllowCurrencySymbol)

To parse the formatted string value into a numeric data type, you need to pick a data type that can best hold the type of value you want to parse in. For example, the data type best suited to representing monetary amounts is the Decimal data type because it was designed specifically for that purpose. Table 7-3 lists the Parse methods and associated data types you can use to verify formatted numeric and date/time input.

Table 7-3: Parse Methods for Numeric and Date/Time Formatted Strings

Parse Methods

Description

Byte.Parse, Decimal.Parse, Double.Parse, Integer.Parse, Long.Parse, Short.Parse, Single.Parse

Allows digits 0 through 9 only. Use the NumberStyles parameter to specify general number formats such as currency or flags that tell which symbols—such as dollar sign, thousands separator, and plus or minus sign—are permitted.

DateTime.Parse, DateTime.ParseExact

Validates a date/time string against a standard set or exact set of allowable formats (depending on which method you use) for a given region.

An additional benefit of using the Parse method is that it works particularly well for handling regional formats, which vary widely (from the U.S. to Europe to Asia, etc.) in terms of symbols and formats used to represent date, time, and monetary amounts. For example, imagine having to write the code to validate that $20,346,758.34 is a well-formatted monetary number and to make the number work across all regional formats. To give you some idea of the complexity, in Austria the same number based on the Euro is represented as € 20.346.758,34. The following code demonstrates how to convert the formatted amount € 20.346.758,34 contained in a string to a decimal value, while at the same time confirming the string is in an acceptable format for the given culture.

‘de-AT is the locale id for Austria
Dim AustrianCultureInfo As New CultureInfo("de-AT")
Dim c As Decimal = _
Decimal.Parse("€20.346.758,34", NumberStyles.Currency, _
AustrianCultureInfo)

The following code demonstrates how you can create a general-purpose function that uses the Parse method for validating strings containing formatted monetary amounts. The IsValidMoneyFormat function validates that the monetary string passed in via the strFormattedMoneyAmount parameter is in an acceptable format, and upon validation returns the converted amount as a Decimal value (in a ByRef parameter).

Public Function IsValidMoneyFormat(ByVal strFormattedMoneyAmount As String, _
ByRef DecimalAmount As Decimal) _
As Boolean
Dim fIsValidFormat As Boolean = True
DecimalAmount = 0
Try
’TODO: Add Imports System.Globalization to the top of the code module
DecimalAmount = Decimal.Parse(strFormattedMoneyAmount, _
NumberStyles.Currency)
Catch ex As Exception
’Take any exception as a signal that the format of the passed
’in string is invalid
fIsValidFormat = False
End Try
Return fIsValidFormat
End Function

Validate input to your application

Bob is an avid bowler and president of the bowling league at the local bowling center. Bob decides that he wants to create an application to track bowling scores for all league bowlers over the course of the league season. Bob decides to start by creating a Windows application that allows the league scorekeeper to enter the scores for each bowler after each game. The following list shows the steps Bob takes to create the application:

  1. Run Visual Basic .NET, and select a new Windows Forms project named BowlingClient. Rename Form1.vb to BowlingScores.vb.

  2. Add a Listbox named lstBowlingScores and a command button named btnAddScore to Form1. Set the Text property of btnAddScore to Add Score. Add a Button named btnEraseAllScores to Form1, and set the Text property of btnEraseAllScores to Erase all scores. Lay out the form as shown in the following illustration.

    click to expand

  3. Double-click btnAddScore on Form1, and add the following code to the btnAddScore click event:

    Private Sub btnAddScore_Click(ByVal sender As System.Object,_
    ByVal e As System.EventArgs) Handles btnAddScore.Click
    Dim frmBowlerScore As New frmBowlerScore()
    frmBowlerScore.ShowDialog(Me)
    If Not frmBowlerScore.Cancelled Then
    lstBowlingScores.Items.Add(frmBowlerScore.BowlerName & vbTab & _
    frmBowlerScore.BowlerScore)
    End If
    End Sub

    This code shows a dialog box (added in the following steps) requesting the bowler name and score for a particular game and, if the information is entered, it adds the information to a list.

  4. Open Form1 in design view, double-click btnEraseAllScores, and add the following code to the btnEraseAllScores_Click event:

    Private Sub btnEraseAllScores_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles btnEraseAllScores.Click
    lstBowlingScores.Items.Clear()
    End Sub

  5. Right-click the project name listed in Solution Explorer, select Add Windows Form, and add a form named frmBowlerScore.

  6. Add a Label named lblName, and add a Textbox named txtName to frmBowlerScore. Set the Text property of lblName to Bowler Name. Set the Text property of txtName to an empty string.

  7. Set the MaxLength property of txtName to 50. (Bob determined from looking at the list of bowler names that 50 characters for the combination of first and last names was a comfortable limit.)

  8. Add a Label named lblScore, and a Textbox named txtScore to frmBowlerScore. Set the Text property of lblScore to Bowler Score. Set the Text property of txtScore to an empty string.

  9. Set the MaxLength property of txtScore to 3 to limit user input to a three-digit number (because bowling scores are limited to the range 0 to 300).

  10. Add two command buttons, named cmdOK and cmdCancel, to frmBowlerScore. Set the Text property for each to OK and Cancel, respectively.

  11. Add an ErrorProvider object, located on the toolbox, to frmBowlerScore, and keep the default name of ErrorProvider1. Lay out the form as shown here:

    click to expand

  12. Add the following Imports statements to the top of frmBowlerScore.vb.

    Imports System.Text.RegularExpressions
  13. Double-click frmBowlerScore to bring up the code window. Add the following code after the Windows Forms Designer generated code block.

    Public Cancelled As Boolean = True
    Public BowlerName As String = ""
    Public BowlerScore As Integer = 0

  14. Add the following ValidateName and ValidateScore validating functions to frmBowlerScore, after the variable declarations added in the previous step. These functions validate that the bowler name is set to a name in the form of FirstName(space)LastName or (word)(space)(word), the first name contains alphabetic characters only, the last name contains alphabetic characters and possibly a limited set of symbols (in a certain order), and the bowling score is a number between 0 and 300:

    Private Function ValidateName() As Boolean
    Const MAX_NAME_LENGTH = 50

    If txtName.Text.Trim() = "" Then
    ErrorProvider1.SetError(txtName, _
    "You must enter the bowler’s name.")
    Return False
    End If

    If txtName.Text.Length > MAX_NAME_LENGTH Then
    ErrorProvider1.SetError(txtName, _
    "The bowler’s name is too long.")
    End If

    ’A valid name is of the form
    ‘FirstName LastName or (Word)(Space)(Word).
    ’The last name can contain
    ‘symbols such as commas, periods, apostrophes,
    ’dashes and spaces (used in limited ways).
    If Not Regex.IsMatch(txtName.Text, _
    "^[a-zA-Z]+ ([a-zA-Z])?(\’)?([a-zA-Z]+)?(\-)?" & _
    "[a-zA-Z]+(\,)?(\ )?([a-zA-Z]+)?(\.)?$") Then
    ErrorProvider1.SetError(txtName, _
    "The bowler’s name contains invalid characters.")
    Return False
    End If

    Return True

    End Function

    Private Function ValidateScore() As Boolean

    If Not IsNumeric(txtScore.Text) OrElse _
    Integer.Parse(txtScore.Text) < 0 OrElse _
    Integer.Parse(txtScore.Text) > 300 Then
    ErrorProvider1.SetError(txtScore, _
    "You must enter a bowling score between 0 and 300.")
    Return False
    End If

    Return True

    End Function

  15. Open frmBowlingScore in design view, double-click the OK button, and enter the following code for the btnOK click event:

    Private Sub btnOK_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles btnOK.Click
    ’ Note: Do not use AndAlso here since both ’Validate’
    ‘ functions need to
    ’ execute in order for validation errors to display for both
    ’ the bowler name and score if both are in error.
    If ValidateName() And ValidateScore() Then
    Me.BowlerName = txtName.Text
    Me.BowlerScore = txtScore.Text
    Me.Cancelled = False
    Me.Close()
    Else
    Me.Cancelled = True
    End If
    End Sub

  16. Open frmBowlingScore in design view, double-click the Cancel button, and enter the following code for the btnCancel click event:

    Private Sub btnCancel_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles btnCancel.Click
    Me.Cancelled = True
    Me.Close()
    End Sub

  17. Enter the following code in the Validated events for txtScore and txtName:

    Private Sub txtScore_Validated(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles txtScore.Validated
    ErrorProvider1.SetError(txtScore, "")
    End Sub

    Private Sub txtName_Validated(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles txtName.Validated
    ErrorProvider1.SetError(txtName, "")
    End Sub

If you run the application, you should find that it does a good job of validating bowler names and scores as you enter them. The preceding steps demonstrate the following principles:

  • Use the MaxLength property to limit input to a reasonable size.

  • Use the ErrorProvider class to signal invalid input.

  • Use the RegularExpression.Regex class to validate the name.

  • Add Validation routines for each input parameter, such as ValidateName and ValidateScore, to check for both valid length (using String.Length) and content.

Later in the chapter, you’ll see how the data validation added to the application here provides little defense against attack or misuse when the code to add bowlers is moved to a server component.

Web Application Input

Like Windows Forms applications, Web applications need to include validation to check user input. Unlike Windows Forms applications, Web applications can get input from a variety of sources, so you must use additional tools to validate the input. There are a number of inputs to a Web application, and all are accessible via the Request object. The types of input to the Request object include:

  • The QueryString collection, containing all query string parameters.

  • The Form collection contains values for all input controls on the Web form, such as text boxes, check boxes, or drop-down lists. These values include values such as the Text property of a text box or the Value property of a check box. If you’re using Web Form controls, you should obtain the value directly from the control rather than using the Form collection.

  • The Cookies collection, containing all cookies sent from the client Web browser.

  • The ServerVariables collection, containing all variables defined by the Web server.

  • Params, providing access to all Request object inputs named previously through a single, convenient-to-use collection.

As demonstrated in the previous chapter, you need to check all input parameters to the Request object to ensure that the input is of reasonable length and contains expected input. Parameters whose contents you display to the user, either as part of an error message or a normal Web page, can be protected from a cross-site scripting attack (presented in Chapter 6) by passing the input through the Server.HtmlEncode method. For example, you should check all places in your code where you use any of the Request object inputs listed previously, such as Request.QueryString, as in the following example:

lblWelcome.Text = “Welcome “ & Request.QueryString(“UserName”)

And at a minimum, you should use Server.HtmlEncode to ensure that none of the content gets processed as HTML when displayed by the client Internet browser. The code shown previously should be changed to:

lblWelcome.Text = Server.HtmlEncode(Request.QueryString("UserName"))

Note

As discussed in the previous chapter, Visual Basic .NET 2003 will throw an exception if any Request object input value contains HTML. Despite this automatic protection provided by Visual Basic .NET 2003, as a defense-in-depth measure, you should still add a call to Server.HtmlEncode for all input that should never contain HTML. If at some point you or someone else sets the ValidateRequest page directive to false, your code is still protected by the call to Server.HtmlEncode.

Don’t Rely on Data Sent to the Client

Some Internet Web sites fall into a trap that lends itself to cyber shoplifting. Never design your Web application to store important information such as product price in the data that is sent to the client. For example, suppose you use ASP.NET to create an on-line shopping application that displays product information, including the price of specific products. When the user clicks your Buy Now button, if you use the price that is sent to your Web application as input (expecting it to be the price you displayed to the user), you might be in for a big surprise.

A user can quite easily post back a Web page containing altered values, including values that are meant to be display-only or invisible to the user. HTML is quite pliable, which can make it easy for someone to use a tool to alter HTML data and post the altered values back to your Web application. For example, suppose you use the following code to initialize the product Web page to display a lawn mower priced at $199.99.

lblProductName.Text = "Lawn mower"
lblPrice.Text = "199.99"
‘Create a hidden field containing the price
txtPrice.Visible = False
txtPrice.Text = "199.99"

And suppose that in your Buy Now button click event you rely on the value contained in txtPrice:

Private Sub btnBuyNow_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnBuyNow.Click
’Charge the customer a single quantity of the product they have chosen
ChargeToCustomer(Decimal.Parse(txtPrice.Text, NumberStyles.Currency))
End Sub

A devious user could, for example, set the txtPrice.Text value as represented in the HTML to a value of $1.00, giving him a $198.99 discount on the lawn mower.

To protect against this sort of problem, you should design your Web applications to look up or validate important information on the server. Instead of relying on the hidden price field posted by the user, the application should always look up the product price in the database, as shown here:

Private Sub btnBuyNow_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnBuyNow.Click
’Charge the customer a single quantity of the product they have chosen
ChargeToCustomer(LookupPriceInDatabase(lblProductName.Text))
End Sub

This code will help to protect against fraud because it uses the price you have stored on the server for the given product. This will work as long as you recognize that the customer could still change the product to an item different than the one originally presented. For example, the user might attempt to change the lawn mower to a stapler so that he’ll receive a lawn mower for the price of a stapler. You need to make sure your system either validates that this type of change to the input has been made and prevents it from happening or responds in a coherent manner to the change by sending a stapler to the customer for the price of a stapler.

Nonuser Input

Nonuser input includes all input to your application that either your application solicits directly (for example, by reading a file from disk) or receives passively (such as receiving data from a communications port). Data stored in files or a database might have at one time been entered by a user and validated at that time. However, such data represents a form of input to your application and can be compromised by various means, including by an attacker gaining access to a shared folder and changing its contents. The data represents a risk to your application. For this reason, you must identify all sources of nonuser input to your application and assess the risk posed by each source. Examples of nonuser input include:

  • Data read from disk

  • Data retrieved from a database

  • Data passed in from a communications port or socket

  • Requests sent to your ASP.NET Web application

As a simple example, suppose you create an application for a hospital that reads patient information from a text file. You use the following code to read in a patient’s age as a numeric value:

Dim hFile As Integer
Dim PatientAge As Integer
hFile = FreeFile()
FileOpen(hFile, "PatientRecord.txt", OpenMode.Input)
PatientAge = Integer.Parse(LineInput(hFile))
FileClose(hFile)

The information contained in PatientRecord.txt represents input to your application. As such, the data being read in should be validated just as you would validate any other form of input. For example, a number of errors can result from the following statement:

PatientAge = Integer.Parse(LineInput(hFile))

This line of code will fail with an untrapped exception for any of the following reasons:

  • The line is blank.

  • The line contains non-numeric characters, such as any letter from A to Z.

  • The number read in exceeds either the minimum or maximum value for an integer. For example, an overflow exception will occur if the line contained the value 123456789123456789, which is larger than the maximum value allowed for an Integer of 2,147,483,647.

In addition to the potential problems represented by the line of code, which inputs data from the file, the code makes a number of assumptions, including:

  • The file PatientRecord.txt exists.

  • The patient’s age is represented by a number.

  • If the patient’s age is a number, it is a realistic number.

  • If the patient’s age is a realistic number, it is the actual age of the patient.

If the PatientRecord.txt file or its contents were ever compromised by an attacker, the code shown previously would fail in a number of ways. For example, if the file name was changed or the file was deleted or moved, the call to FileOpen would fail with an untrapped exception that shows the name of the file the application needs—which is possibly useful information for an attacker who wants to mount other forms of attack. (See Chapter 8 for more details.) If the contents of the file are modified in any way—such as lines being added or deleted or the patient’s age being changed to a string such as “hacked”—the call to Integer.Parse(LineInput(hFile)) would fail with an untrapped exception, as shown previously.

Finally, a particularly insidious change an attacker could make to the PatientRecord.txt would be to change the patient’s age to a different value. This would not lead to an error, but it could have far-reaching effects on how the patient is treated. For example, if the patient’s age was changed from 22 to 85, the patient’s insurance company might automatically deny the patient coverage for a medical procedure that is considered to be extremely low risk at age 22 but quite risky at age 85.

To prevent most of these problems, you can add error handling to your application. For example, at a minimum, adding a Try…Catch statement around the entire block of code to catch and recover from any exceptions would help prevent the application from crashing unexpectedly. However, you need to be careful about how errors are reported to the user. This will be explained in Chapter 8.

To prevent unauthorized changes to data—such as an unauthorized age change from 22 to 85—you could add deeper checks to validate the input or add an error-reporting mechanism to your application that logs all unusual changes to the data. For example, if the read-in age value did not correlate with the patient’s calculated age based on the patient’s birth date, or the age value were significantly different than the age your database application has on record, the application could log an error noting that the data looks suspicious.

Input to Subroutines

Any subroutine that can be called directly or indirectly by an external application (or component) can serve as an access point for an attacker. For example, in the case of a client-server application where a person inputs the data in a client application and the data is passed to a server application, validating the data in the client application does no good if:

  • The data was never sufficiently validated by the client application

  • The data is compromised en route to the server

  • A different application is used to pass invalid data to the server application

A subroutine that is going to take action based on a parameter passed into it should always validate the input parameter before taking action. Let’s go back to the example of Bob and his bowling application. If Bob decided to expand his application by adding a class library containing a Public subroutine for logging the bowling scores to a file, the error handling he added to the client application might do no good if a different client application is used to call the subroutine. If he fails to add checks to verify the input to the subroutine, either an attacker or uninformed user could pass in values that could prevent the application from working properly.

Add validation to Bob’s bowling server class

Suppose Bob extends his application by performing the following steps:

  1. Right-click the BowlingApplication in Solution Explorer, select Add New Project, and add a Visual Basic .NET Class Library project named Bowling Server.

  2. Change the name of Class1, both the file and class name, to BowlingScores.

  3. Add the following code to the body of class BowlingScores:

    Private Const BOWLING_SCORES As String = "BowlingScores.txt"

    Public Sub AddBowlingScore(ByVal BowlerName As String, _
    ByVal score As Integer)

    Dim hFile As Integer = FreeFile()
    FileOpen(hFile, BOWLING_SCORES, OpenMode.Append, _
    OpenAccess.Write, OpenShare.LockWrite)
    PrintLine(hFile, BowlerName & "," & score)
    FileClose(hFile)
    End Sub

  4. Right-click the References list in the Solution Explorer for the BowlingClient project, and select Add Reference. Select BowlingServer from the Projects tab as shown here:

    click to expand

  5. Open BowlingScores.vb, located within the BowlingClient project, double-click btnAddScore, and change the code in the click event to the following:

    Private Sub btnAddScore_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles btnAddScore.Click

    Dim BowlingServ As New BowlingServer.BowlingScores()
    Dim frmBowlerScore As New frmBowlerScore()
    frmBowlerScore.ShowDialog(Me)

    If Not frmBowlerScore.Cancelled Then
    BowlingServ.AddBowlingScore(frmBowlerScore.BowlerName, _
    frmBowlerScore.BowlerScore)
    End If

    End Sub

The code has been changed to call the BowlingScores.AddBowlingScore method, which adds the bowler name and score to the file BowlingScores.txt. Do you notice anything missing from the BowlingScores.AddBowlingScore method?

There is no validation being performed on the input parameters. Because Bob designed the server application specifically to work with the client, he feels that the validation performed in the client application is sufficient to avoid passing empty or invalid values to BowlingScores.AddBowlingScore. What Bob has not considered is that the BowlingScores.AddBowlingScore method can be called from any client application. For example, you can easily write code like the following code to instantiate the server and pass invalid values:

Dim BowlingServ As New BowlingServer.BowlingScores()
BowlingServ.AddBowlingScore("Earl Anthony", Integer.MaxValue)

The AddBowlingScore method will blindly write the invalid score of 2,147,483,647 to the BowlingScores.txt file. Bob’s buddy Earl is good, but he’s not that good! Suppose Bob adds the following function to the BowlingServer class, which reads in all the scores contained within the file to compute the bowling average for the entire league:

Public Function GetLeagueBowlingAverage() As Decimal
Dim hFile As Integer = FreeFile()
Dim BowlerName As String
Dim score As Integer
Dim TotalScore As Integer = 0
Dim ctScores As Integer = 0
’TODO: Add ’Imports System.IO’ to the top of the code module
If File.Exists(BOWLING_SCORES) Then
FileOpen(hFile, BOWLING_SCORES, OpenMode.Input, _
OpenAccess.Read, OpenShare.Shared)
ctScores = 0
Do While Not EOF(hFile)
Input(hFile, BowlerName)
Input(hFile, score)
ctScores += 1
TotalScore += score
Loop
FileClose(hFile)
End If
If ctScores > 0 Then
Return TotalScore / CDec(ctScores)
Else
Return 0
End If
End Function

The introduction of the maximum value to the BowlingScores.txt file will cause the running total kept in the variable TotalScore to overflow in the following statement:

TotalScore += score

The GetLeagueBowlingAverage function is also subject to failure if a bad user name is passed to AddBowlingScore and stored to the file. Suppose that an attacker or a developer who is re-using the BowlingScores object created a client application or component that called AddBowlingScore with a bowler name such as Earl Anthony, Jr. The GetLeagueBowlingAverage would fail to read in the data because the Input(hFile, BowlerName) statement reads in comma-delimited data. The Input statement would read up to Earl Anthony, and the subsequent Input(hFile, score) statement would attempt to convert Jr. to an integer bowling score, which would result in an InvalidCastException. This is an example where the data contained in the file represents input to the application. If the data goes unchecked, it can lead to some interesting failures. In this case, unchecked input to both the AddBowlingScore and GetLeagueBowlingAverage functions can lead to failures, which would prevent all members of the bowling league from obtaining the bowling league average. This is a case where service is denied to anyone attempting to get the information.

The problem shown here is a form of a denial of service (DoS) attack, which was covered in the previous chapter. Although this might be an irritation to the members of the bowling league, imagine if the same type of problem were to happen for a high-profile, Web-based stock market application that computed the Dow Jones Industrial Average.

Clearly Bob needs to apply the same level of input validation to his server functions as he did for his client application. Specifically, Bob can add the following checks to each function. This will make his server component much more resilient to unexpected or malicious input:

  • Apply the regular expression (“^[a-zA-Z]+ ([a-zA-Z])?(\’)?([a-zA-Z]+)?(\-)?[a- zA-Z]+(\,)?(\ )?([a-zA-Z]+)?(\.)?$”) to verify the BowlerName argument passed to AddBowlingScore. The same regular expression should be used within the GetLeagueBowlingAverage function to check the BowlerName variable after it’s read from the text file.

  • Verify that the BowlingScore passed to AddBowlingScore is between 0 and 300. Within GetBowlingLeagueAverage, Bob should read the score in as a string value and use the Integer.Parse method to both validate that it’s a numeric value and convert it to an Integer. If successfully converted, he should check that the value is between 0 and 300.

  • Add some checks to GetBowlingLeagueAverage to make sure the values of ctScore and TotalScore don’t exceed Integer.MaxValue. If an attacker were to launch a repetitive-input attack that flooded AddBowlingScore with millions of legal but fictitious bowling scores, the sum when computed by GetBowlingLeagueAverage would overflow the maximum Integer value. On the other hand, if Bob expects his server application to work for a league of thousands or millions of bowlers, he should consider changing the type of TotalScore and ctScore to a Long integer, which has a maximum value of 9,223,372,036,854,775,807.




Security for Microsoft Visual Basic  .NET
Security for Microsoft Visual Basic .NET
ISBN: 735619190
EAN: N/A
Year: 2003
Pages: 168

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net