Export (0) Print
Expand All

Implementing Message Validation in WSE 3.0

 
Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Implementing Message Validation in WSE 3.0

patterns & practices Developer Center

Web Service Security: Scenarios, Patterns, and Implementation Guidance for Web Services Enhancements (WSE) 3.0

Microsoft Corporation

patterns & practices Developer Center
Web Service Security: Home
December 2005

DownloadDownload this guide in PDF format
QuickStartsDownload the implementation pattern QuickStarts [Content link no longer available, original URL:http://go.microsoft.com/fwlink/?LinkId=57044]
CommunityWeb Service Security Community Workspace [Content link no longer available, original URL:http://go.microsoft.com/fwlink/?LinkId=57044]

Contents

Context
Objectives
Implementation Strategy
Implementation Approach
Resulting Context
More Information

Context

You are implementing a Web service that uses Web Service Enhancements (WSE) 3.0. The Web service must validate request messages received from clients to make sure that they are not malformed and do not contain malicious content.

Objectives

This implementation of the Message Validator pattern has the following objectives:

  • Prevent the service from processing request messages that are larger than a specified size.
  • Prevent the service from processing messages that are not well-formed or that do not conform to an expected XML schema.
  • Validate input messages before deserializing them into .NET data types so that they can be interpreted as regular expressions.
  • Demonstrate how to use WSE 3.0 custom assertion to implement message validation.
  • Use ASP.NET and WSE 3.0 configuration settings to limit usage of system resources such as CPU.
Note   The code examples in this pattern are also available as executable QuickStarts on the Web Service Security community workspace [Content link no longer available, original URL:http://go.microsoft.com/fwlink/?LinkId=57044].

Implementation Strategy

To implement message validation on a Web service, you use a combination of application configuration, code implementation, and filtering in WSE 3.0. Use one or more of the following methods to perform message validation:

  • Set the maximum request size in the service's configuration file to limit the size of messages that the service will process.
  • Validate each incoming request message to ensure that it is well-formed XML, that it contains all of the parts required by the service, and that the contents of the message conforms to an expected structure as defined by an XML Schema (XSD).
  • Use regular expression checking to ensure that input contains only valid data and does not contain malicious SQL, HTML, or JavaScript code that could lead to code injection attacks.
  • Use regular expressions to ensure that complex data types (such as social security numbers and telephone numbers) are received in a format that the service can process.
Note   You should conduct a thorough threat analysis of your service application to determine where in the code you should perform message validation and to determine which methods of message validation you should use.

To fully understand this pattern, you must have some experience with the .NET Framework, WSE 3.0, and Web service development.

Participants

This implementation pattern requires the following participants:

  • Client. The client accesses the Web service.
  • Service. The service is the Web service that processes requests received from clients. The service implements the message validation logic.

Process

The Message Validator pattern describes the message validation process at a high level. This implementation pattern provides a refined description of that process specific to the WSE 3.0 implementation.

Figure 1 illustrates the process by which message validation logic intercepts request messages and verifies that they are acceptable for processing by the service.

Ff647829.ch5_impmsgval_f01(en-us,PandP.10).gif

Figure 1. Validating a request message

The process uses the following steps:

  1. The client sends a request message to the service.
  2. The service validates the message. The service uses a number of different validation checks to prevent malicious input. These include:
    • Comparing the size of the request to the value established for the maxRequestLength attribute of the <httpRuntime> element in the application's configuration file, which is specified in kilobytes. maxRequestLength specifies the maximum allowable size for request messages. If the message exceeds this value, the service does not process the message, and it returns an error.
      Note   You can set other values in the <httpRuntime> element to control response, resource usage for handling requests, and timeouts. For more information about <httpRuntime>, see <httpRuntime> Element in the .NET Framework General Reference on MSDN.
    • Checking the format of the request message to ensure that the message is formed correctly and that all of the required message parts are present. The service uses WSE policy assertions to make sure that all required message parts are present. The service can use the requireActionHeader policy assertion to verify that the message contains a WS-Addressing action header. The service can use the requireSoapHeader policy assertion to verify that the message contains other SOAP header elements, such as an addressing header and a message ID. For more information about WSE 3.0 policy assertions, see Policy Assertions in the WSE 3.0 product documentation on MSDN.
    • Verifying that the XML in the message payload is well-formed and that it conforms to a predefined schema with acceptable data types and ranges of values. The service uses an XML Schema (XSD) to validate the contents of the message body. If a specific schema is not required for validation, it can use an XML parser to validate the request body. The service can use an XML Schema (XSD) to perform structural validation, data type validation, cardinality of child elements to parent elements, numeric value ranges, and regular expression validation for character patterns and ranges.
    • Parsing the request message for malicious content. The service can use regular expressions to ensure that the messages contain only valid data. Regular expression validation can be implemented either in the XML Schema (XSD) or in code. Also, the service can use parameterized SQL queries to access and modify data in databases to mitigate the risk of SQL injection.
  3. The service processes the request and responds to the client. If the request passes all validation checks performed by the message validator, the service processes the message.

Implementation Approach

This section describes how to implement the pattern. The section is broken into two major tasks:

  • Configure the client. This section describes the steps required to configure policy and code for the client.
  • Configure the service. This section describes the steps required to configure policy and code for the service.

This pattern does not specifically address other security requirements for authentication and securing the communication channel. For more information about authentication and securing the communication channel, see the following patterns:

  • Direct Authentication in Chapter 1, "Authentication Patterns."
  • Brokered Authentication in Chapter 1, "Authentication Patterns."
  • Data Confidentiality in Chapter 2, "Message Protection Patterns."
  • Data Origin Authentication in Chapter 2, "Message Protection Patterns."
Note    For the code examples included in this pattern, an ellipsis (...) is used where segments of code, such as class declarations and designer-generated code, have been omitted. You must name variables, methods, and return values and ensure that they are of the appropriate type for the client application.

Configure the Client

The client requires no special configuration for message validation. The client should be able to recognize and properly handle validation exceptions thrown by the service.

Configure the Service

If you use policy to implement authentication and message protection for your service, you should configure it before you attempt to use the custom policy assertion provided in this implementation. For policy-based authentication and message protection examples, see one of the following implementation patterns in Chapter 3, "Implementing Transport and Message Layer Security":

If you do not use policy to implement authentication and/or message protection for your service, you must enable support for WSE 3.0 and add a text file for the policy cache to your service project in Visual Studio 2005 before using the custom policy assertion provided in this pattern.

To enable the service project to support WSE 3.0

  1. In Visual Studio 2005, right-click the application project, and then click WSE Settings 3.0.
  2. On the General tab, select the Enable this project for Web Services Enhancements check box, select the Enable Microsoft Web Services Enhancement SOAP Protocol Factory check box, and then click OK.

To add a policy cache file to the service project in Visual Studio

  1. In Visual Studio, right-click the application project, and then click Add New Item.
  2. Click Text File.
  3. In the Name field, type a name for the file, such as wse3policyCache.config.
  4. Click Add.

This section is divided into subsections; each subsection describes a message validation technique. You do not always have to implement all the message validation techniques. You should complete a thorough threat analysis of the service to determine which techniques to use.

Whether you implement some or all of the message validation techniques, you should implement them in the order that they are described. The order in which the message validation techniques occur depends on where they are implemented in the platform. In this pattern, the request size must be checked before any other step. The custom policy assertion must be applied in the pipeline after the message is decrypted but before the request is processed by the service. Regular expression checking, if implemented in the XML Schema (XSD), occurs when the request is validated against the message schema in the policy assertion. Otherwise, regular expression checking occurs where the code is implemented, most likely in the service code. Parameterization of SQL queries occurs when the query is created, prior to execution on the database server.

The point at which the body validator assertion is specified does not matter relative to other assertions defined to protect the message, because decryption and signature verification is applied further up the communication pipeline from assertions applied for message validation.

Configure Maximum Request Length

To limit the size (in kilobytes) of messages that the service will process, you should specify a value for the maxRequestLength attribute of the <httpRuntime> element in the service's Web.config file. This value should be set according to the largest request message that you can reasonably expect the service to process. If you do not specify a value for this setting, the default value is 4096 KB. The following XML example shows a maximum request length set to 300 KB.

<configuration>
      ...
  <system.web>
     <httpRuntime maxRequestLength="300"/>
      ...
  </system.web> 
   ...
</configuration>

If your service uses a protocol other than HTTP (such as TCP), the WSE <maxMessageLength> setting can be used to limit the size (in kilobytes) of incoming requests, assuming that you are using the SoapClient/SoapService model for your service. The default value for the length attribute of the <maxMessageLength> element is 4096 KB. The following configuration example shows the <MaxMessageLength> set to 1024 KB for a service that uses the SoapClient/SoapService model.

<configuration>
...
  <microsoft.web.services3>
  ...
    <messaging>
      <maxMessageLength value="1024" />
    </messaging>
    ...
  </microsoft.web.services3>
  ...
</configuration>

For more information about using the SoapClient/SoapService classes for messaging, see How To: Send and Receive a SOAP Message by Using the SoapClient and SoapService Classes in the WSE 3.0 product documentation on MSDN.

Required Message Part/Schema Validation

This implementation pattern uses policy assertions to check for required message parts and to validate the message schema. The following example policy file provides an example of policy assertions for the service. Other polices that would be present to sign, encrypt, and provide authentication capabilities have been omitted for brevity.

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
 <extensions>
 ...
 
 <extension name="bodyValidator" type="Microsoft.Practices.WSSP.WSE3.QuickStart.MessageValidation.CustomAssertions.BodyValidatorAssertion, Microsoft.Practices.WSSP.WSE3.QuickStart.MessageValidation.CustomAssertions"/>
 </extensions>
 <policy name="MessageValidationService">
       <bodyValidator xsdPath="Configuration\GetCustomers.xsd" />
       ...
       <requireSoapHeader name="MessageID" namespace="http://schemas.xmlsoap.org/ws/2004/08/addressing"/>
    <requireSoapHeader name="To" namespace="http://schemas.xmlsoap.org/ws/2004/08/addressing"/>
    <requireActionHeader />
 </policy>
...
</policies>

In this policy file example, the <Action>, <MessageID>, and <To> elements are required on all incoming request messages. A custom policy assertion, bodyValidator, is specified in the <extensions> section (see the section, "Custom Policy Assertion–Message Body Validation," for sample code). You should indicate the namespace as appropriate for your project.

The type attribute for the bodyValidator extension declared in the preceding policy code example is formatted as the fully qualified class name (namespace + class name) followed by a comma and then the name of the assembly that contains the assertion class.

If you are not using policy to implement authentication and/or message protection for your service as previously described in this section, you must now enable the service to support WSE and enable policy support. WSE does not recognize custom policy assertions when it parses the policy cache file, and it will disable policy support if you attempt to configure it using the WSE Settings tool. If you have to enable policy support after you have added a custom policy assertion to your policy cache, you must add a <policy> element to the service's Web.config file to enable policy support.

<microsoft.web.services3>
...
   <policy fileName="wse3policyCache.config" />
...
</microsoft.web.services3>

Replace the value specified for the fileName attribute with the file path and name of your policy cache file.

Custom Policy Assertion–Message Body Validation

The following code example shows the custom policy assertion used to check the message body against an XML Schema (XSD).

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.Schema;
using System.Web;
using System.Configuration;

using Microsoft.Web.Services3;
using Microsoft.Web.Services3.Security;
using Microsoft.Web.Services3.Design;

namespace Microsoft.Practices.WSSP.WSE3.QuickStart.MessageValidation.CustomAssertions
{
    /// <summary>
    /// This Custom PolicyAssertion class validates the received SOAP body
    /// against an XML Schema (XSD) document whose path is configured in the policy document.
    /// </summary>
    public class BodyValidatorAssertion : PolicyAssertion
    {
        private string xsdPath;
        
        public override SoapFilter CreateClientInputFilter(FilterCreationContext context)
        {
            return null;
        }

        public override SoapFilter CreateClientOutputFilter(FilterCreationContext context)
        {
            return null;
        }

        public override SoapFilter CreateServiceInputFilter(FilterCreationContext context)
        {
            return new BodyValidatorAssertion.ServiceInputFilter(this);
        }

        public override SoapFilter CreateServiceOutputFilter(FilterCreationContext context)
        {
            return null;
        }

        public override void ReadXml(System.Xml.XmlReader reader, IDictionary<string, Type> extensions)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");
            if (extensions == null)
                throw new ArgumentNullException("extensions");

            bool isEmpty = reader.IsEmptyElement;
 
            string xsdPath = reader.GetAttribute("xsdPath");
            if (!string.IsNullOrEmpty(xsdPath))
            {
                this.xsdPath = xsdPath;
            }
            else
            {
                throw new ConfigurationErrorsException(Messages.MissingXsdPath);  
            }
            
            reader.ReadStartElement("bodyValidator");
            
            if(!isEmpty)
                reader.ReadEndElement();
        }

        public override void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("bodyValidator");
            writer.WriteAttributeString("xsdPath", this.xsdPath);
            writer.WriteEndElement();
        }
                
        protected class ServiceInputFilter : SoapFilter
        {
            #region Custom Fields
            
            private XmlSchema schema;
            
            #endregion

            #region Constructors
            public ServiceInputFilter(BodyValidatorAssertion assertion)
            {
                string xsdPath = assertion.xsdPath;
                if (!Path.IsPathRooted(xsdPath))
                {
                   xsdPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, xsdPath);
                }

                using (StreamReader streamReader = new StreamReader(xsdPath))
                {
                    this.schema = XmlSchema.Read(streamReader, ValidationHandler);
                    streamReader.Close();
                }
            }
            #endregion

            #region SoapFilter Methods
            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
            {
                ValidationResults results = new ValidationResults();
                SoapContext.Current.MessageState.Set(results);

                ValidateSchema(envelope.Body.InnerXml);

                if (results.ErrorsCount > 0)
                {
                    throw new ApplicationException(string.Format(Messages.ValidationError, results.ErrorMessage));
                }

                return SoapFilterResult.Continue; 
            }
            #endregion

            #region Custom Methods
            /// <summary>
            /// Performs the validation of the SOAP body against the specified XML Schema (XSD) document.
            /// </summary>
            /// <param name="xmlDoc">SOAP message's body (XML)</param>
            public void ValidateSchema(string xmlDoc)
            {
                try
                {
                    XmlReaderSettings settings = new XmlReaderSettings();
                    settings.Schemas.Add(this.schema);
                    settings.ValidationType = ValidationType.Schema;
                    
                    XmlReader reader = XmlReader.Create(new StringReader(xmlDoc), settings);
                              
                    // Validate the document.
                    while (reader.Read()) ;

                    reader.Close();
                }
                catch(Exception ex)
                {
                    throw new ApplicationException(string.Format(Messages.SchemaValidationException, ex.Message));
                }
            }
            
            /// <summary>
            /// Callback method that stores the error messages.
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="args"></param>
            public void ValidationHandler(object sender, ValidationEventArgs args)
            {
                if (args.Severity == XmlSeverityType.Error)
                {
                    ValidationResults results = SoapContext.Current.MessageState.Get<ValidationResults>();
                    
                    results.ErrorMessage.Append(args.Message + "\r\n");
                    results.ErrorsCount++;
                }
            }
            #endregion

            private class ValidationResults
            {
                public StringBuilder ErrorMessage = new StringBuilder();
                public int ErrorsCount;
            }
          
        }       
        
    }
} 

In the preceding example, the Messages.MissingXsdPath refers to a resource string that provides a message for the ConfigurationErrorsException that is being thrown. As appropriate, you should substitute this and other resource strings used in the code example with a simple exception message to describe the nature of the exception.

Note   The validator assertion will only validate the structure of XML data in the message that has the same namespace as the schema that is used to validate it. Data with other namespaces is ignored for schema validation.

You should take care when using a policy assertion to validate an XML Schema (XSD) if a party other than the Web service developer will be responsible for configuring the service's policy when it is deployed into production. If the party responsible for configuring policy in production does not add the validation assertion, the validation will not be performed. If Web service development and policy configuration responsibilities are not held by the same individuals, you should consider using a helper class that is called from within the service to perform the validation instead. Alternatively, you can add the schema to the resource file for your project. In this case, the schema does not have to be deployed as a separate file. For more information, see Resolving the Unknown: Building Custom XmlResolvers in the .NET Framework on MSDN.

The policy assertion caches the schema in memory that it uses to validate incoming request messages. If you make changes to the schema, you may have to restart Microsoft Internet Information Services (IIS) to ensure that the updated schema is loaded into memory.

Use Regular Expressions to Parse Input

The following code example shows how to use regular expressions to parse input on the Web service to ensure that only valid characters are used. Place this code where it can be called to validate input, after the message has been decrypted (if message layer security is implemented). For example, the following code can be added to the service to validate each string input parameter.

...
using System.Text.RegularExpressions;
...
private bool Validate(string searchString)
{
Regex r = new Regex("^[0-9A-Za-z]{1,10}$");
       return r.IsMatch(searchString);
}

The preceding example provides a simple example for regular expression validation that does not allow any non-alphanumeric characters. Consequently, it may not be suitable for use in all applications. You can use more sophisticated checks for complex data, such as social security numbers and telephone numbers. For more information about implementing regular expressions, see How To: Use Regular Expressions to Constrain Input in ASP.NET on MSDN.

Note   Although the custom policy assertion provided in this pattern is applied after the security filters in the pipeline (that is, after the message has been decrypted), the regular expression code is not used in the policy assertion because it would require the policy assertion to have explicit knowledge of input parameters contained in the data.

You can also use regular expressions to validate user input on client applications. The main benefit of validating input from the client's perspective is to save a round trip to the Web service if data validation fails. For this approach to be effective, you must be able to validate data according to the Web service's validation requirements.

However, the service should never depend on the client to perform validation checks. You must always perform validation checks on the server, because an attacker could use a different client that does not perform the check or messages could be altered after a check has been performed at the client.

The following example demonstrates how regular expression validation can be described within an XML Schema (XSD). Regular expression validation that uses an XML Schema (XSD) allows the Web service publisher to indicate to consumers what the Web service expects. However, it does not perform as well as regular expression validation in code.

...
<xsd:simpleType name="CustomerReferenceType">
  <xsd:restriction base="xsd:normalizedString">
    <xsd:maxLength value="20"/>
<xsd:pattern value="[A-D][0-9]{5}-[0-9A-Z]{7}-[a-z]{3}#*"/>
</xsd:restriction>
</xsd:simpleType> 
...

For more information about using regular expressions in XSD schemas, see XML Schema Regular Expressions on MSDN.

Parameterize SQL Queries

Web services often use a database to store and retrieve data. Web service request messages could contain malicious input to inject SQL commands into database queries. The following example provides an example of how to parameterize SQL queries. Whenever possible, you should use stored procedures for both performance and security reasons. Stored procedures accept input through parameters, and they generally work best to enforce minimum privilege for data retrieval and modification. The example shows how to parameterize dynamic SQL if your application must use it.

Note   The example assumes that a regular expression has already been used to validate the searchString parameter. For more information, see the previous section, "Use Regular Expressions to Parse Input."
...
using System.Data.SqlClient;
using System.Configuration;
...
private Customer[] GetCustomerList(string country, string searchString)
{
CustomerCollection customerCollection = new CustomerCollection();
Customer customer = new Customer();
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Northwind"].ToString()))
{
string selectString = "SELECT * FROM Customers WHERE Country = @Country AND (CompanyName LIKE '%' + @SearchString + '%' OR ContactName LIKE '%' + @SearchString + '%' OR @SearchString IS NULL)";
    conn.Open();
    SqlCommand cmd = new SqlCommand(selectString, conn);
    cmd.Parameters.Add("@Country", SqlDbType.VarChar).Value = country;
    cmd.Parameters.Add("@SearchString", SqlDbType.VarChar, 10).Value = searchString;
    SqlDataReader reader = cmd.ExecuteReader();
    while (reader.Read())
    {
     customer.CustomerID = reader["CustomerID"].ToString();
     customer.CompanyName = reader["CompanyName"].ToString();
     customer.ContactName = reader["ContactName"].ToString();
     customer.ContactTitle = reader["ContactTitle"].ToString();
     customer.Address = reader["Address"].ToString();
     customer.City = reader["City"].ToString();
     customer.Region = reader["Region"].ToString();
     customer.PostalCode = reader["PostalCode"].ToString();
     customer.Country = reader["Country"].ToString();
     customer.Phone = reader["Phone"].ToString();
     customer.Fax = reader["Fax"].ToString();
     customerCollection.Add(customer);
    }
    reader.Close();
    conn.Close();
   }
   return (Customer[])customerCollection.ToArray(typeof(Customer));
  }

In this example, the Customer and CustomerCollection classes are custom data objects. As appropriate, replace the data objects and SQL query for your application. The important point is to parameterize the query instead of directly concatenating input into the SQL query.

Resulting Context

This section describes some of the more significant benefits, liabilities, and security considerations of using this implementation pattern.

Note   The information in this section is not intended to be comprehensive. However, it does discuss many of the issues that are most commonly encountered for this pattern.

Benefits

The majority of attacks that result from malformed messages, invalid characters, or SQL injection are mitigated with the approach outlined in this implementation pattern.

Liabilities

The liabilities associated with the Implementing Message Validation in WSE 3.0 pattern include the following:

  • Validating messages against very large schemas can affect system performance. Typically, the cost of parsing is multiplied two to four times when the schema validation is performed on an XML message. For more information about XML performance guidance in the .NET Framework, see Chapter 9, Improving XML Performance in Improving .NET Application Performance and Scalability on MSDN.

    If message schema validation is causing performance problems, you should consider the following optimizations:

    • Make sure that you are reading your schemas only once from the schema file, and cache them in memory to minimize I/O.
    • Reduce the message schema to essential elements that are required for a particular Web service or Web service operation. Another option is to use regular expression validation in code to validate structural elements.
    • Incorporate more sophisticated regular expression checking. The regular expression validation example provided in this application is very strict and does not account for validation requirements specific to your service. A thorough threat analysis of your application should reveal any need for a specific form of regular expression checking. For more information about validating input with regular expressions, see How To: Use Regular Expressions to Constrain Input in ASP.NET on MSDN.

Security Considerations

Security considerations associated with the Implementing Message Validation in WSE 3.0 pattern include the following:

  • Attackers may attempt to work around message validation. You should be aware of known attempts to work around message validation and adjust your validation code accordingly. Keep your platform up to date with the latest security updates to mitigate issues with built-in security features.
  • Schema validation validates only basic data types, such as integers, dates, and structures; it should always be supplemented with regular expression validation. You can directly implement regular expression validation in the XML Schema (XSD) or in code to validate more complex data, such as social security numbers and telephone numbers. Regular expression validation directly in the XML Schema (XSD) is useful to communicate what the service requires as valid input to client applications, but it does not perform as well as regular expressions implemented in code.

More Information

For more information about <httpRuntime>, see "<httpRuntime> Element" in the .NET Framework General Reference on MSDN.

For more information about WSE 3.0 policy assertions, see "Policy Assertions" on MSDN.

For more information about using the SoapClient/SoapService classes for messaging, see "How To: Send and Receive a SOAP Message by Using the SoapClient and SoapService Classes," in the WSE 3.0 documentation on MSDN.

For more information about adding a schema to a resource file see "Resolving the Unknown: Building Custom XmlResolvers in the .NET Framework," on MSDN.

For more information about implementing regular expressions, see "How To: Use Regular Expressions to Constrain Input in ASP.NET" on MSDN.

For more information about using regular expressions in XML Schemas, see "XML Schema Regular Expressions" on MSDN.

For more information about XML performance guidance in the .NET Framework, see Chapter 9, "Improving XML Performance," in Improving .NET Application Performance and Scalability on MSDN.

patterns & practices Developer Center

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Show:
© 2014 Microsoft