Return to HomePage



Validate User Input with Regular Expressions (VB.NET)


Applies To

* .NET Framework 2.0
* VB.NET


Summary

The purpose of this code snippet is to illustrate the use of regular expressions to perform validation of user input within an application. Many common attack vectors make use of malicious input data and rely on poor or nonexistent validation of user-supplied input in order to successfully exploit aplication vulnerabilities.

These following validation routines make use of regular expressions to ensure that input accepted by an application conforms to predetermined and expected character syntax requirements. This practice is sometimes called "whitelist" validation and is ideally suited to the use of regular expressions.


Objectives

* Defend against common security attacks by validating that user input data meets known-valid syntax requirements
* Define a set of reusable code-based validation methods that can be reused within different applications or application components

Scenarios

* Application makes use of user input in any form, whether in processing operations, storage or database queries.
* Application makes use of data that comes from any other untrusted source, such as a third-party data feed or file upload.
* Application does not already make use a validation scheme for input data.


Solution Example

The solution example defines 16 regular expression validation functions and a helper method that address a common set of application input data types. The regular expression validation functions are:

IsValidUserName IsValidCCNumber
IsValidPassword IsValidSSN
IsValidName IsValidEmailAddress
IsValidStreetAddress IsValidURL
IsValidCity IsValidIPAddress
IsValidUSState IsValidAlphaText
IsValidZIPCode IsValidAlphaNumericText
IsValidUSPhoneNumber IsValidNumericText

		    ' Decomposed method which actually created the pattern object and determines the match.
		    ' Used by all of the other functions. 
		    Function [MatchString(ByVal] str As String, [ByVal] regexstr As String) As Boolean
		        str = str.Trim()
		        Dim pattern As New [System.Text.RegularExpressions.Regex(regexstr)]
	

		        Return [pattern.IsMatch(str)]
		    End Function ['MatchString]
	

		    Function [IsValidUserName(ByVal] strUsername As String) As Boolean
		        ' Allows word characters [A-Za-z0-9_], single quote, dash and period
		        ' must be at least two characters long and less then 128
		        Dim [regExPattern] As String = "^[\w-'\.]{2,128}$"
	

		        ' We also permit email address characters in user name. Set to false
		        ' if you don't permit email addresses as usernames. 
		        Dim [allowEmailUsernames] As Boolean = True
	

		        If [allowEmailUsernames] Then
		            Return [MatchString(strUsername,] [regExPattern)] Or [IsValidEmailAddress(strUsername)]
		        Else
		            Return [MatchString(strUsername,] [regExPattern)]
		        End If
		    End Function ['IsValidUserName]
	

		    Function [IsValidPassword(ByVal] strPassword As String) As Boolean
		        ' allows any type of character
		        ' If complexity is enabled, the password must be longer
		        ' and contain at least one uppercase, one lowercase, 
		        ' one numeric and one symbolic character. Set to false 
		        ' if your requirements differ.
		        Dim passwordComplexity As Boolean = True
	

		        ' These are some proposed minimum password lengths. If
		        ' complexity is enabled (above), the stronger (longer) 
		        ' minimum password rule applies. 
		        Dim [minPasswordLen] As Integer = 6
		        Dim [strongPasswordLen] As Integer = 8
	

		        If passwordComplexity Then
		            Dim [regExPattern] As String = "^.*(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[`~!\$%\^\&\*\(\)-_\=\+\[\{\]\}\\\|;:',<\.>/?]).*$"
		            Return strPassword.Length >= [strongPasswordLen] And [MatchString(strPassword,] [regExPattern)]
		        Else
		            Return strPassword.Length >= [minPasswordLen]
		        End If
		    End Function ['IsValidPassword]
	

		    Function [IsValidName(ByVal] strName As String) As Boolean
		        ' Allows alphabetical chars, single quote, dash and space
		        ' must be at least two characters long and caps out at 128 (database size)
		        Dim [regExPattern] As String = "^[a-zA-Z-'\.\s]{2,128}$"
		        Return [MatchString(strName,] [regExPattern)]
		    End Function ['IsValidName]
	

		    Function [IsValidStreetAddress(ByVal] strAddress As String) As Boolean
		        ' Since so many different types of address formats we're just going to swing the bat at 
		        ' this one for now and do a match against a series of digits (potentially containing
		        ' punctuation, followed by a series of characters representing the street name and then
		        ' potentially a type of street and unit number
		        Dim [regExPattern] As String = "\d{1,3}.?\d{0,3}\s[a-zA-Z]{2,30}(\s[a-zA-Z]{2,15})?([#\.0-9a-zA-Z]*)?"
		        Return [MatchString(strAddress,] [regExPattern)]
		    End Function ['IsValidStreetAddress]
	

		    Function [IsValidCity(ByVal] strCity As String) As Boolean
		        ' Here we simply treat city names like people names and defer to our valid name function. 
		        ' The same types of characters should be admissible. 
		        Return [IsValidName(strCity)]
		    End Function ['IsValidCity]
	

		    Function [IsValidUSState(ByVal] strState As String) As Boolean
		        ' Names of 50 US States
		        Dim stateNames As String() = {"ALABAMA", "ALASKA", "ARIZONA", "ARKANSAS", "CALIFORNIA", "COLORADO", "CONNECTICUT", "DELAWARE", "FLORIDA", "GEORGIA", "HAWAII", "IDAHO", "ILLINOIS", "INDIANA", "IOWA", "KANSAS", "KENTUCKY", "LOUISIANA", "MAINE", "MARYLAND", "MASSACHUSETTS", "MICHIGAN", "MINNESOTA", "MISSISSIPPI", "MISSOURI", "MONTANA", "NEBRASKA", "NEVADA", "NEW HAMPSHIRE", "NEW JERSEY", "NEW MEXICO", "NEW YORK", "NORTH CAROLINA", "NORTH DAKOTA", "OHIO", "OKLAHOMA", "OREGON", "PENNSYLVANIA", "RHODE ISLAND", "SOUTH CAROLINA", "SOUTHDAKOTA", "TENNESSEE", "TEXAS", "UTAH", "VERMONT", "VIRGINIA", "WASHINGTON", "WEST VIRGINIA", "WISCONSIN", "WYOMING"}
		        ' Postal codes of 50 US States
		        Dim stateCodes As String() = {"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FL", "GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY"}
	


		        ' This one is somewhat easier because we have a finite set of values to check against.
		        ' We simply uppercase our value anc check against our list.
		        strState = [strState.ToUpper()]
	

		        Dim [stateCodesArray] As New [ArrayList(stateCodes)]
		        Dim [stateNamesArray] As New [ArrayList(stateNames)]
	

		        Return [stateCodesArray.Contains(strState)] Or [stateNamesArray.Contains(strState)]
		    End Function ['IsValidUSState]
	

		    Function [IsValidZIPCode(ByVal] strZIP As String) As Boolean
		        ' Allows 5 digit, 5+4 digit and 9 digit zip codes
		        ' must be at least two characters long and caps out at 128 (database size)
		        Dim [regExPattern] As String = "^(\d{5}-\d{4}|\d{5}|\d{9})$"
		        Return [MatchString(strZIP,] [regExPattern)]
		    End Function ['IsValidZIPCode]
	

		    Function [IsValidUSPhoneNumber(ByVal] strPhone As String) As Boolean
		        ' Allows phone number of the format: NPA = [2-9][0-8][0-9] Nxx = [2-9][0-9][0-9] Station = [0-9][0-9][0-9][0-9]
		        Dim [regExPattern] As String = "^[01]?[- .]?(\([2-9]\d{2}\)|[2-9]\d{2})[- .]?\d{3}[- .]?\d{4}$"
		        Return [MatchString(strPhone,] [regExPattern)]
		    End Function ['IsValidUSPhoneNumber]
	

		    Function [IsValidCCNumber(ByVal] [strCCNumber] As String) As Boolean
		        ' This expression is basically looking for series of numbers confirming to the standards
		        ' for Visa, MC, Discover and American Express with optional dashes between groups of numbers
		        Dim [regExPattern] As String = "^((4\d{3})|(5[1-5]\d{2})|(6011))-?\d{4}-?\d{4}-?\d{4}|3[4,7][\d\s-]{15}$"
		        Return [MatchString(strCCNumber,] [regExPattern)]
		    End Function ['IsValidCCNumber]
	

		    Function [IsValidSSN(ByVal] strSSN As String) As Boolean
		        ' Allows SSN's of the format 123-456-7890 and accepts hyphen delimited [SSNs] or those which omit hyphens
		        Dim [regExPattern] As String = "^\d{3}[-]?\d{2}[-]?\d{4}$"
		        Return [MatchString(strSSN,] [regExPattern)]
		    End Function ['IsValidSSN]
	

		    Function [IsValidEmailAddress(ByVal] strEmail As String) As Boolean
		        ' Allows common email address that can start with a alphanumeric char and contain word, dash and period characters
		        ' followed by a domain name meeting the same criteria followed by a alpha suffix between 2 and 9 character lone
		        Dim [regExPattern] As String = "^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$"
		        Return [MatchString(strEmail,] [regExPattern)]
		    End Function ['IsValidEmailAddress]
	

		    Function [IsValidURL(ByVal] strURL As String) As Boolean
		        ' Allows HTTP and FTP URL's, domain name must start with alphanumeric and can contain a port number
		        ' followed by a path containing a standard path character and ending in common file suffixies found in URL's
		        ' and accounting for potential CGI GET data
		        Dim [regExPattern] As String = "^^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_=]*)?$"
		        Return [MatchString(strURL,] [regExPattern)]
		    End Function ['IsValidURL]
	

		    Function [IsValidIPAddress(ByVal] strIP As String) As Boolean
		        ' Allows four octets of numbers that contain values between 4 numbers in the IP address to 0-255 and are separated by periods
		        Dim [regExPattern] As String = "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
		        Return [MatchString(strIP,] [regExPattern)]
		    End Function ['IsValidIPAddress]
	

		    Function [IsValidAlphaText(ByVal] strAlpha As String) As Boolean
		        ' Allows one or more alphabetical characters. This is a more generic validation function.
		        Dim [regExPattern] As String = "^[A-Za-z]+$"
		        Return [MatchString(strAlpha,] [regExPattern)]
		    End Function ['IsValidAlphaText]
	

		    Function [IsValidAlphaNumericText(ByVal] [strAlphaNum] As String) As Boolean
		        ' Allows one or more alphabetical and/or numeric characters. This is a more generic validation function.
		        Dim [regExPattern] As String = "^[A-Za-z0-9]+$"
		        Return [MatchString(strAlphaNum,] [regExPattern)]
		    End Function ['IsValidAlphaNumericText]
	

		    Function [IsValidNumericText(ByVal] strNumeric As String) As Boolean
		        ' Allows one or more positive or negative, integer or decimal numbers. This is a more generic validation function.
		        Dim [regExPattern] As String = "/[+-]?\d+(\.\d+)?$"
		        Return [MatchString(strNumeric,] [regExPattern)]
		    End Function ['IsValidNumericText]
	


Problem Example

The following example demonstrates show the use of unvalidated ASP.NET user input in a dynamic-generated SQL query. This code is vulnerable to SQL injection.

Dim sParam As String = Request("first_name")

' Create a new SQL Command object with our query
' Dynamically generate SQL query using passed parameter
Dim sqlCommand As SqlCommand = New SqlCommand("SELECT first, last FROM people WHERE first = '" + sParam + "'")

* If the user inserts SQL metacharacters into the 'first_name' argument, SQL commands can be executed
* User data is accessed and processed by the application without any kind of validation. This is a very common practice amongst application developers and results in many of the security vulnerabilities discovered.

Test Case

The following classes must be included in any project making use of the sample code provided above:

Imports System.Collections
Imports System.Collections.Generic
Imports System.Text
Imports System.Text.RegularExpressions

The test case illustrates the user of all 16 of the defined regular expression validation functions:


Sub Main(ByVal args() As String)
		   		' Validate some usernames
		   		Console.WriteLine("Jonathan is a valid username: " + IsValidUserName("Jonathan").ToString())
		   		Console.WriteLine("Jonathan!'+ is a valid state: " + IsValidUserName("Jonathan!'+").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some passwords
		   		Console.WriteLine("J0na+han is a valid password: " + IsValidPassword("J0na+han!").ToString())
		   		Console.WriteLine("jb is a valid password: " + IsValidPassword("jb").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some names
		   		Console.WriteLine("Jonathan is a valid name: " + IsValidName("Jonathan").ToString())
		   		Console.WriteLine("Jonathan Bailey is a valid name: " + IsValidName("Jonathan Bailey").ToString())
		   		Console.WriteLine("Jonathan GT Bailey is a valid name: " + IsValidName("Jonathan GT Bailey").ToString())
		   		Console.WriteLine("Shawn O'Brien is a valid name: " + IsValidName("Shawn O'Brien").ToString())
		   		Console.WriteLine("Steve Holt! is a valid name: " + IsValidName("Steve Holt!").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some street addresses
		   		Console.WriteLine("123 Main St. is a valid street address: " + IsValidStreetAddress("123 Main St.").ToString())
		   		Console.WriteLine("45-50 South Lake Road is a valid street address: " + IsValidStreetAddress("45-50 South Lake Road").ToString())
		   		Console.WriteLine("45-50 South Lake Road #8 is a valid street address: " + IsValidStreetAddress("45-50 South Lake Road #8").ToString())
		   		Console.WriteLine("45-50!!! South Lake Road is a valid street address: " + IsValidStreetAddress("45-50!! South Lake Road").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some cities
		   		Console.WriteLine("Chicago is a valid name: " + IsValidCity("Chicago").ToString())
		   		Console.WriteLine("Los Angeles is a valid city name: " + IsValidCity("Los Angeles").ToString())
		   		Console.WriteLine("Jones-Parker Corner is a valid city name: " + IsValidCity("Jones-Parker Corner").ToString())
		   		Console.WriteLine("(*&(*&ville is a valid name: " + IsValidCity("(*&(*&ville").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some states
		   		Console.WriteLine("Massachusetts is a valid state: " + IsValidUSState("Massachusetts").ToString())
		   		Console.WriteLine("Foobar is a valid state: " + IsValidUSState("Foobar").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some ZIP Codes
		   		Console.WriteLine("90034 is a valid zip code: " + IsValidUSState("90034").ToString())
		   		Console.WriteLine("90034-1234 is a valid zip code: " + IsValidZIPCode("90034-1234").ToString())
		   		Console.WriteLine("900345 is a valid zip code: " + IsValidUSState("900345").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some US Phone Numbers
		   		Console.WriteLine("1-617-555-1212 is a valid US phone number: " + IsValidUSPhoneNumber("1-617-555-1212").ToString())
		   		Console.WriteLine("(617) 555-1212 is a valid US phone number: " + IsValidUSPhoneNumber("(617) 555-1212").ToString())
		   		Console.WriteLine("617.555.1212 is a valid US phone number: " + IsValidUSPhoneNumber("617.555.1212").ToString())
		   		Console.WriteLine("6175551212 is a valid US phone number: " + IsValidUSPhoneNumber("6175551212").ToString())
		   		Console.WriteLine("617/555/1212 is a valid US phone number: " + IsValidUSPhoneNumber("617/5551212").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some credit card numbers
		   		Console.WriteLine("4444-1234-5678-9900 is a valid credit card number: " + IsValidCCNumber("4444-1234-5678-9900").ToString())
		   		Console.WriteLine("3718 123456 78900 is a valid credit card number: " + IsValidCCNumber("3718 123456 78900").ToString())
		   		Console.WriteLine("1234567890 is a valid credit card number: " + IsValidCCNumber("1234567890").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some SSN's
		   		Console.WriteLine("123-45-6789 is a valid Social Security Number: " + IsValidSSN("123-45-6789").ToString())
		   		Console.WriteLine("987654321 is a valid Social Security Number: " + IsValidSSN("987654321").ToString())
		   		Console.WriteLine("123-456 is a valid Social Security Number: " + IsValidSSN("123-456").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some email addresses
		   		Console.WriteLine("pentest@msn.com is a valid email address: " + IsValidEmailAddress("pentest@msn.com").ToString())
		   		Console.WriteLine("Jonathan_Bailey-01@mail.msn.com is a valid email address: " + IsValidEmailAddress("Jonathan_Bailey-01@mail.msn.com").ToString())
		   		Console.WriteLine("Jonathan_Bailey@msn is a valid email address: " + IsValidEmailAddress("Jonathan_Bailey@msn").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some URL's
		   		Console.WriteLine("http://www.microsoft.com is a valid URL: " + IsValidURL("http://www.microsoft.com").ToString())
		   		Console.WriteLine("https://secure.microsoft.com:443/Default.aspx is a valid URL: " + IsValidURL("https://secure.microsoft.com:443/Default.aspx").ToString())
		   		Console.WriteLine("ftp://ftp.microsoft.com/download.html#tools is a valid URL: " + IsValidURL("ftp://ftp.microsoft.com/download.html#tools").ToString())
		   		Console.WriteLine("http://100?arg=val is a valid URL: " + IsValidURL("http://100?arg=val").ToString())
		   		Console.WriteLine("http://= is a valid URL: " + IsValidURL("http://=").ToString())
		   		Console.WriteLine("")
	

		   		' Validate some IP Addresses
		   		Console.WriteLine("192.168.0.1 is a valid IP Address: " + IsValidIPAddress("192.168.0.1").ToString())
		   		Console.WriteLine("0.0.0.0 is a valid IP Address: " + IsValidIPAddress("192.168.0.1").ToString())
		   		Console.WriteLine("300.300.300.300 is a valid IP Address: " + IsValidIPAddress("300.300.300.300").ToString())
		   		Console.WriteLine("")
	
End Sub


Expected Result

Jonathan is a valid username: True
Jonathan!'+ is a valid state: False

J0na+han is a valid password: True
jb is a valid password: False

Jonathan is a valid name: True
Jonathan Bailey is a valid name: True
Jonathan GT Bailey is a valid name: True
Shawn O'Brien is a valid name: True
Steve Holt! is a valid name: False

123 Main St. is a valid street address: True
45-50 South Lake Road is a valid street address: True
45-50 South Lake Road #8 is a valid street address: True
45-50!!! South Lake Road is a valid street address: False

Chicago is a valid name: True
Los Angeles is a valid city name: True
Jones-Parker Corner is a valid city name: True
(&(&ville is a valid name: False

Massachusetts is a valid state: True
Foobar is a valid state: False

90034 is a valid zip code: False
90034-1234 is a valid zip code: True
900345 is a valid zip code: False

1-617-555-1212 is a valid US phone number: True
(617) 555-1212 is a valid US phone number: True
617.555.1212 is a valid US phone number: True
6175551212 is a valid US phone number: True
617/555/1212 is a valid US phone number: False

4444-1234-5678-9900 is a valid credit card number: True
3718 123456 78900 is a valid credit card number: True
1234567890 is a valid credit card number: False

123-45-6789 is a valid Social Security Number: True
987654321 is a valid Social Security Number: True
123-456 is a valid Social Security Number: False

pentest@msn.com is a valid email address: True
Jonathan_Bailey-01@mail.msn.com is a valid email address: True
Jonathan_Bailey@msn is a valid email address: False

http://www.microsoft.com is a valid URL: True
https://secure.microsoft.com:443/Default.aspx is a valid URL: True
ftp://ftp.microsoft.com/download.html#tools is a valid URL: True
http://100?arg=val is a valid URL: True
http://= is a valid URL: False

192.168.0.1 is a valid IP Address: True
0.0.0.0 is a valid IP Address: True
300.300.300.300 is a valid IP Address: False


More Information

* As previously stated, this example makes use of "whitelist" validation practices, in which user data is evaluated against a set of known valid characters. "Black list" validation, which evaluates input against a set of known malicious characters or strings, is also a common validation practice. We recommend the use of whitelist validation because new attacks are continually created and may make use
of character combinations not publicly known at present.
* In addition to input validation, we recommend the use of data output validation to add additional protection against malicious input attacks that execute during presentation.


Additional Resources

* Regular Expressions for Constraining Input (ASP.NET): http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/paght000001.asp
* Input/Data Validation (ASP.NET): http://msdn.microsoft.com/library/en-us/dnpag2/html/PAGGuidelines0001.asp?frame=true#pagguidelines0001_inputdatavalidation


Attributes

* Applies To: .NET Framework 2.0, VB
* Category: Data Validation
* Author: Jonathan Bailey




Return to HomePage
Microsoft Communities