Fri 4 Jul 2008

Web Service Security using SOAP Extension

 

Yesterday I was trying to secure my webserice from unauthorized use.

Although there are many ways to secure webservices. Using WSE, WS-security, SOAP Extension, restricting through IP access, implementing certificates etc etc etc.

 

WSE would be the comfortable way but in my case it was time consuming to implement at both server and client end as there were hundreds of services. Restricting IP throu IIS or http handler / modules would be a good choice. I tried to do it with SOAP Extension that could be plug and play with a single entry in web.config. It works like httpmodule that reside in pipeline of communication between client and server.

 

Here after enough googling I have a very simple SOAP Extension that would help anyone if just want to secure service that request is coming from authenticated source. Although there would be still many security breaches but here you go.

 

 

What this SOAP extension will do?

Before passing request to webserver, this extension will add a string or any unique token to header of SOAP message then will pass it over network. Before server entertain the request it will look for that token or string in the header of incoming SOAP message request. If exsit then will allow to consume service else will throw an exception or whatever the action depending upon scenario.

 

 

SOAP Message Lifecycle.

 

Client = Going to accessing service

Server = Where service exists and entertaining client request.

 

 

SOAPExtension Life.gif

 

Client side

1.    A client invokes a method on the proxy class.

2.    A new instance of the SOAP extension is created on the client.

3.    If this is the first time this SOAP extension has executed with this XML Web service on the client, then the GetInitializer method is invoked on the SOAP extension running on the client.

4.    The Initialize method is invoked.

5.    The ChainStream method is invoked.

6.    The ProcessMessage method is invoked with the SoapMessageStage set to BeforeSerialize.

7.    ASP.NET on the client computer serializes the arguments of the XML Web service method into XML.

8.    The ProcessMessage method is invoked with the SoapMessageStage set to AfterSerialize.

9.    ASP.NET on the client computer sends the SOAP message over the network to the Web server hosting the XML Web service.

 

Server side

1.    ASP.NET on the Web server receives the SOAP message.

2.    A new instance of the SOAP extension is created on the Web server.

3.    On the Web server, if this is the first time this SOAP extension has executed with this XML Web service on the server side, the GetInitializer method is invoked on the SOAP extension running on the server.

4.    The Initialize method is invoked.

5.    The ChainStream method is invoked.

6.    The ProcessMessage method is invoked with the SoapMessageStage set to BeforeDeserialize.

7.    ASP.NET deserializes the arguments within the XML.

8.    The ProcessMessage method is invoked with the SoapMessageStage set to AfterDeserialize.

9.    ASP.NET creates a new instance of the class implementing the XML Web service and invokes the XML Web service method, passing in the deserialized arguments. This object resides on the same computer as the Web server.

10.  The XML Web service method executes its code, eventually setting the return value and any out parameters.

11.  The ProcessMessage method is invoked with the SoapMessageStage set to BeforeSerialize.

12.  ASP.NET on the Web server serializes the return value and out parameters into XML.

13.  The ProcessMessage method is invoked with the SoapMessageStage set to AfterSerialize.

14.  ASP.NET sends the SOAP response message over the network back to the XML Web service client.

 

Client side

1.    ASP.NET on the client computer receives the SOAP message.

2.    The ProcessMessage method is invoked with the SoapMessageStage set to BeforeDeserialize.

3.    ASP.NET deserializes the XML into the return value and any out parameters.

4.    The ProcessMessage method is invoked with the SoapMessageStage set to AfterDeserialize.

5.    ASP.NET passes the return value and any out parameters to the instance of the proxy class.

6.    The client receives the return value and any out parameters.

 

 

Here what exactly the code contains.

There are three classes.

MyHeader.cs

That will contain our security token or string that server will be looking for.

 

using System;

using System.Collections.Generic;

using System.Text;

using System.Web.Services.Protocols;

using System.Xml.Serialization;

 

    [Serializable]

    public class MyHeader : SoapHeader

    {

        private string _MyHeaderValue;

 

        public string MyHeaderValue

        {

            get { return _MyHeaderValue; }

            set { _MyHeaderValue = value; }

        }

    }

 

 

MySOAPExtensionClient.cs

SOAP Extension that will add security token in header header to outgoing SOAP message

 

using System;

using System.Web.Services;

using System.Web.Services.Protocols;

using System.IO;

using System.Net;

using System.Diagnostics;

 

    public class MySOAPExtensionClient : System.Web.Services.Protocols.SoapExtension

    {

        Stream oldStream;

        Stream newStream;

 

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)

        {

            return null;

        }

 

        public override object GetInitializer(Type WebServiceType)

        {

            return null;

        }

 

        public override void Initialize(object initializer)

        {

            return;

        }

 

        // Save the Stream representing the SOAP request or SOAP response into

        // a local memory buffer.

        public override Stream ChainStream(Stream stream)

        {

            oldStream = stream;

            newStream = new MemoryStream();

            return newStream;

        }

 

        public override void ProcessMessage(SoapMessage message)

        {

            switch (message.Stage)

            {

                case SoapMessageStage.BeforeSerialize:

                    if (message is SoapClientMessage)

                        AddHeader(message);

                    break;

                case SoapMessageStage.AfterSerialize:

                    newStream.Position = 0;

                    Copy(newStream, oldStream);

                    break;

                case SoapMessageStage.BeforeDeserialize:

                    Copy(oldStream, newStream);

                    newStream.Position = 0;

                    break;

                case SoapMessageStage.AfterDeserialize:

                    break;

            }

        }

 

        private void AddHeader(SoapMessage message)

        {

            MyHeader header = new MyHeader();

            header.MyHeaderValue = "MyValue";

            header.MustUnderstand = false;

            message.Headers.Add(header);

        }

 

        private void Copy(Stream from, Stream to)

        {

            TextReader reader = new StreamReader(from);

            TextWriter writer = new StreamWriter(to);

            writer.WriteLine(reader.ReadToEnd());

            writer.Flush();

        }

    }

 

 

 

 

MySOAPExtensionServer.cs

SOAP Extension that will will expect a security token or string in incoming SOAP request.

 

using System;

using System.Web.Services;

using System.Web.Services.Protocols;

using System.IO;

using System.Net;

using System.Diagnostics;

 

    public class MySOAPExtensionServer : System.Web.Services.Protocols.SoapExtension

    {

        Stream oldStream;

        Stream newStream;

 

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)

        {

            return null;

        }

 

        public override object GetInitializer(Type WebServiceType)

        {

            return null;

        }

 

        public override void Initialize(object initializer)

        {

            return;

        }

 

        // Save the Stream representing the SOAP request or SOAP response into

        // a local memory buffer.

        public override Stream ChainStream(Stream stream)

        {

            oldStream = stream;

            newStream = new MemoryStream();

            return newStream;

        }

 

        public override void ProcessMessage(SoapMessage message)

        {

            switch (message.Stage)

            {

                case SoapMessageStage.BeforeSerialize:

                    break;

                case SoapMessageStage.AfterSerialize:

                    newStream.Position = 0;

                    Copy(newStream, oldStream);

                    break;

                case SoapMessageStage.BeforeDeserialize:

                    Copy(oldStream, newStream);

                    newStream.Position = 0;

                    break;

                case SoapMessageStage.AfterDeserialize:

                    if (message is SoapServerMessage)

                    {

                        if (!IsRequestValid(message))

                        {

                            LoggerSecurity.GetInstance().WriteLog("Invalid Service Call | Action = " + message.Action + " | Url = " + message.Url);

                            throw new SoapException("Invalid Service Call", SoapException.ClientFaultCode);

                        }

                    }

                    break;

            }

        }

 

        private bool IsRequestValid(SoapMessage message)

        {

            bool result = false;

            try

            {

                foreach (SoapHeader header in message.Headers)

                {

                    if (header is MyHeader)

                    {

                        MyHeader head = (MyHeader)header;

                        if (head.MyHeaderValue.Equals("MyValue"))

                            result = true;

                    }

                       

                       

                    else if (header is SoapUnknownHeader)

                    {

                        System.Xml.XmlElement elem = ((SoapUnknownHeader)header).Element;

                        if (elem.Name.Equals("MyHeader"))

                        {

                            System.Xml.XmlNode node = elem.SelectSingleNode("/");

                            if (node != null && node.InnerText.Equals("MyValue"))

                                result = true;

                        }

                    }

                }

            }

            catch (Exception ex)

            {

                LoggerSecurity.GetInstance().WriteLog(ex);

            }

            return result;

        }

 

        private void Copy(Stream from, Stream to)

        {

            TextReader reader = new StreamReader(from);

            TextWriter writer = new StreamWriter(to);

            writer.WriteLine(reader.ReadToEnd());

            writer.Flush();

        }

    }

 

 

In Client Web.Config before ending system.Web section add following

 

        <webServices>

              <protocols>

                    <remove name="HttpGet" />

                    <remove name="HttpPost" />

                    <remove name="HttpPostLocalhost" />

                    <remove name="Documentation" />

              </protocols>

              <soapExtensionTypes>

                    <add type="CustSoapExtension.MySOAPExtensionClient, CustSoapExtension" priority="1" group="High"/>

              </soapExtensionTypes>

        </webServices>

 

  </system.web>

 

 

 

In Server Web.Config before ending system.Web section add following

 

        <webServices>

              <protocols>

                    <remove name="HttpGet" />

                    <remove name="HttpPost" />

                    <remove name="HttpPostLocalhost" />

                    <remove name="Documentation" />

              </protocols>

              <soapExtensionTypes>

                    <add type="CustSoapExtension.MySOAPExtensionClient, CustSoapExtension" priority="1" group="High"/>

              </soapExtensionTypes>

        </webServices>

 

  </system.web>

 

 

Why these?

                    <remove name="HttpGet" />

                    <remove name="HttpPost" />

                    <remove name="HttpPostLocalhost" />

                    <remove name="Documentation" />

 

On production environment, if there is required only to consume webservices using only SOAP then don’t allow aceess to service using these protocoals. Only allow SOAP protocol.

 

 

Hope this sample code will help you to improve your security concern.

 

Comments (3)
Tue 24 Jun 2008


Configuration File Tampering

If Configuration files are not protected then you should use the file system access control list (ACL) to protect them.


System Registry Tampering

If registry entries are not protected then you should use the registry ACL to protect them.


Repudiation / Logging

Security exceptions should be logged for auditing purposes; therefore, you should define and implement logging and auditing strategies in the code. Push security exceptions–related information to the event log.


Assembly Tampering

To prevent assembly tampering, consider implementing Authenticode signatures for these assemblies.


Authentication and Authorization

You should consider various Internet, intranet, and extranet-based deployment for Web servers and database servers, and then implement appropriate authentication mechanisms.


Message Protection

You should implement transport-level security (secure socket layer [SSL]) to further strengthen the communication channel. You can also implement IPsec to secure communication channel between services and the database. To implement message-level security, use either WSE 3.0 or WCF to sign and encrypt messages. Choose appropriate certificates and encryption algorithms to enforce security without compromising business operations and performance.


HTTP Replay
Attacks

You can prevent these attacks by providing a secure end-to-end communication channel between server and client (for example, SSL). You should also uniquely authenticate each request (for example, use a timestamp and digital signature), by implementing message-level security. Implement IP lockout policies if required.


Denial of Service

You can prevent denial of service attacks by implementing strong authentication, authorization, and request validation mechanisms. Also, you should uniquely authenticate each request (for example, use a timestamp and digital signature) by implementing message-level security.


Repudiation

You can prevent repudiation attacks by implementing strong authentication, authorization, and request validation mechanisms. Also, you should implement the history and auditing feature for any database operations. You should not permanently delete the records from the database.


Dictionary Attack or Password Brute Force
Attack

You should try to prevent dictionary attacks or password brute force attacks. Implement strong password policies to prevent password hijacking. Implement a maximum retries policy, and disable the account if the number of attempts exceeds the maximum number. Also, implement an IP lockout policy, if required. Implement auditing and logging for service contracts / Web server / service host access.


Spoofing

You can prevent spoofing attacks by implementing strong authentication, authorization, and request validation mechanisms.


Database Security Access Controls

Use an account that has restricted permissions in the database. Ideally, you should grant execute permissions only to selected stored procedures. Consider using database role and application role database security concepts to access a different set of database objects. For example, consider using different sets of database roles and application roles for read-only operations and read-write operations.


Configuration Files Clear Text Secrets

To protect your connection strings, secret app settings, consider using DPAPI to encrypt them and store clear text secrets in a restricted registry. Use file ACLs to control access to configuration files.


Database Clear Text Secrets

The database contains secrets in clear text. For a production application, you should consider encrypting sensitive data.


Web Service Documentation Protocol

The Web.config file allows a malicious user to see the Web service documentation (wsdl file) by using documentation protocol. Using this information, the malicious user can get information about all data contracts and service contract details. The malicious user can then use the details to launch brute force attacks or false request attacks. You should configure the Web.config file to disable the documentation protocol.


Debug Attribute

The host program configuration file allows debugging. The Web.config file describes the debug = true attribute, which can allow the malicious user to debug the service implementation. This opens extra surface area, which allows a malicious user to explore injection threats. To prevent this type of attack, configure debug = false in the Web.config file.


CustomErrors Mode Attribute

The host program configuration file allows debugging. The Web.config file describes the CustomErrors Mode = off attribute, which can allow the malicious user to see the complete debug information in case of errors or exceptions. A malicious user can get the call stack information, which can launch injection or code malfunction attacks. To prevent this type of attack, configure CustomErrors Mode = on and make sure that the defaultUrl is appropriately configured in the Web.config file.

 

PersistSecurityInfo Attribute

The database connection string in the Web.config file does not contain a definition for the PersistSecurityInfo attribute. This attribute should be set to false. When set to false, sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state. Resetting the connection string resets all connection string values, including the password. Set the PersistSecurityInfo attribute to false in the connection string.


SqlClientPermission Attribute

The database access assembly does not define the code access security attribute SqlClientPermission.

The CustomerRepository assembly should request minimum security permissions for SqlClientPermission.


Unrestricted Base Classes

When developing classes that will be deployed to a production environment, you should consider using sealed attributes for classes and methods.

 

Comments (1)
Thu 24 Apr 2008

Would be old one but just a refreshing

 

Contents

What ASP.NET Developers Should Always Do
Where the Threats Come From
ViewStateUserKey
Cookies and Authentication
Session Hijacking
EnableViewStateMac
ValidateRequest
Database Perspective
Hidden Fields
E-mails and Spam
Summary
Related Resources

What ASP.NET Developers Should Always Do

If you're reading this article, you probably don't need to be lectured about the growing importance of security in Web applications. You're likely looking for some practical advice on how to implement security in ASP.NET applications. The bad news is that no development platform—including ASP.NET—can guarantee you'll be writing 100-percent secure code once you adopt it—who tells that, just lies. The good news, as far as ASP.NET is concerned, is that ASP.NET, especially version 1.1 and the coming version 2.0, integrates a number of built-in defensive barriers, ready to use.

The application of all these features alone is not sufficient to protect a Web application against all possible and foreseeable attacks. However, combined with other defensive techniques and security strategies, the built-in ASP.NET features form a powerful toolkit to help ensure that applications operate in a secure environment.

Web security is the sum of various factors and the result of a strategy that goes well beyond the boundaries of the individual application to involve database administration, network configuration, and also social engineering and phishing.

The goal of this article is to illustrate what ASP.NET developers should always do in order to keep the security bar reasonably high. That's what security is mostly about—keep the guard up, never feel entirely secure, and make it harder and harder for the bad guys to hack.

Let's see what ASP.NET has to offer to simplify the job.

Where the Threats Come From

In Table 1, I've summarized the most common types of Web attacks and flaws in the application that can make them succeed.

AttackMade possible by . . .
Cross-site scripting (XSS) Untrusted user input echoed to the page
SQL injection Concatenation of user input to form SQL commands
Session hijacking Session ID guessing and stolen session ID cookies
One-click Unaware HTTP posts sent via script
Hidden field tampering Unchecked (and trusted) hidden field stuffed with sensitive data

 

 

 

More at ....

 

Take Advantage of ASP.NET Built-in Features to Fend Off Web Attacks

http://msdn2.microsoft.com/en-us/library/ms972969.aspx

 

 

Comments (1)